fai_protocol/
lib.rs

1//! FAI Protocol - Distributed version control system for large files
2//!
3//! This library provides the core functionality for managing large file versions
4//! in a decentralized manner. Perfect for game assets, video footage, scientific
5//! datasets, AI models, and any files that are too large for traditional version
6//! control systems.
7
8pub mod database;
9pub mod network;
10pub mod storage;
11
12use anyhow::Result;
13use chrono::{DateTime, Utc};
14use std::path::PathBuf;
15
16/// Information about a commit for display purposes
17#[derive(Debug, Clone)]
18pub struct CommitInfo {
19    /// Commit hash
20    pub hash: String,
21    /// Commit message
22    pub message: String,
23    /// Commit timestamp
24    pub timestamp: DateTime<Utc>,
25    /// Parent commit hash (None for initial commit)
26    pub parent_hash: Option<String>,
27}
28
29/// Main library interface for FAI Protocol
30pub struct FaiProtocol {
31    storage: storage::StorageManager,
32    database: database::DatabaseManager,
33    fai_path: PathBuf,
34}
35
36impl FaiProtocol {
37    /// Create a new FAI Protocol instance
38    pub fn new() -> Result<Self> {
39        let fai_path = PathBuf::from(".fai");
40        let storage = storage::StorageManager::new(fai_path.clone())?;
41        let database = database::DatabaseManager::new(&fai_path.join("db.sqlite"))?;
42        Ok(Self {
43            storage,
44            database,
45            fai_path,
46        })
47    }
48
49    /// Initialize a new FAI repository
50    pub fn init() -> Result<()> {
51        let fai_path = PathBuf::from(".fai");
52
53        // Create .fai directory structure
54        std::fs::create_dir_all(&fai_path)?;
55        std::fs::create_dir_all(fai_path.join("objects"))?;
56
57        // Initialize storage (creates metadata database)
58        let _storage = storage::StorageManager::new(fai_path.clone())?;
59
60        // Initialize main database
61        let _database = database::DatabaseManager::new(&fai_path.join("db.sqlite"))?;
62
63        // Create .fai/HEAD file pointing to main branch
64        std::fs::write(fai_path.join("HEAD"), "ref: refs/heads/main")?;
65
66        Ok(())
67    }
68
69    /// Add a file to the staging area
70    pub fn add_file(&self, file_path: &str) -> Result<String> {
71        // Check if file exists
72        if !std::path::Path::new(file_path).exists() {
73            return Err(anyhow::anyhow!("File not found: {}", file_path));
74        }
75
76        // Read file content
77        let content = std::fs::read(file_path)?;
78        println!(
79            "DEBUG: Read {} bytes from file: {}",
80            content.len(),
81            file_path
82        );
83
84        // Store in storage manager FIRST (this actually writes to .fai/objects/)
85        let hash = self.storage.store(&content)?;
86        println!(
87            "DEBUG: Stored file content with hash: {} ({} bytes)",
88            hash,
89            content.len()
90        );
91
92        // Get file size
93        let size = std::fs::metadata(file_path)?.len();
94
95        // THEN add to staging area with the hash returned by storage
96        self.database.add_to_staging(file_path, &hash, size)?;
97        println!("DEBUG: Added file to staging: {} -> {}", file_path, hash);
98
99        // Debug: verify the file was actually stored
100        if !self.storage.exists(&hash) {
101            return Err(anyhow::anyhow!(
102                "Failed to store file: {} with hash: {}",
103                file_path,
104                hash
105            ));
106        }
107        println!("DEBUG: Verified file exists in storage: {}", hash);
108
109        Ok(hash)
110    }
111
112    /// Get repository status (staged files)
113    pub fn get_status(&self) -> Result<Vec<(String, String, u64)>> {
114        self.database.get_staged_files()
115    }
116
117    /// Get reference to the storage manager
118    pub fn storage(&self) -> &storage::StorageManager {
119        &self.storage
120    }
121
122    /// Get reference to the database manager
123    pub fn database(&self) -> &database::DatabaseManager {
124        &self.database
125    }
126
127    /// Create a commit from staged files
128    pub fn commit(&self, message: &str) -> Result<String> {
129        // Get staged files
130        let staged_files = self.database.get_staged_files()?;
131
132        if staged_files.is_empty() {
133            return Err(anyhow::anyhow!("Nothing to commit"));
134        }
135
136        // Read current HEAD
137        let parent_hash = self.get_head()?;
138
139        // Generate commit hash
140        let commit_data = format!(
141            "{}{}{:?}",
142            Utc::now().timestamp_millis(),
143            message,
144            staged_files
145        );
146        let mut hasher = blake3::Hasher::new();
147        hasher.update(commit_data.as_bytes());
148        let commit_hash = hasher.finalize().to_hex().to_string();
149
150        // Create commit in database
151        self.database.create_commit(
152            &commit_hash,
153            message,
154            parent_hash.as_deref(),
155            &staged_files,
156        )?;
157
158        // Update HEAD file
159        std::fs::write(self.fai_path.join("HEAD"), &commit_hash)?;
160
161        // Clear staging area
162        self.database.clear_staging()?;
163
164        Ok(commit_hash)
165    }
166
167    /// Get commit log
168    pub fn get_log(&self) -> Result<Vec<CommitInfo>> {
169        let commits = self.database.get_commit_history(None)?;
170        Ok(commits
171            .into_iter()
172            .map(|c| CommitInfo {
173                hash: c.hash,
174                message: c.message,
175                timestamp: c.timestamp,
176                parent_hash: c.parent_hash,
177            })
178            .collect())
179    }
180
181    /// Read current HEAD commit hash
182    fn get_head(&self) -> Result<Option<String>> {
183        let head_path = self.fai_path.join("HEAD");
184        if head_path.exists() {
185            let content = std::fs::read_to_string(&head_path)?;
186            // Handle both direct hash and ref: refs/heads/main format
187            if content.starts_with("ref:") {
188                // For now, return None for branch refs (not implemented yet)
189                Ok(None)
190            } else {
191                Ok(Some(content.trim().to_string()))
192            }
193        } else {
194            Ok(None)
195        }
196    }
197}
198
199pub use database::{Commit, DatabaseManager};
200/// Re-export commonly used types
201pub use storage::{ModelMetadata, StorageManager};