1pub mod database;
9pub mod network;
10pub mod storage;
11
12use anyhow::Result;
13use chrono::{DateTime, Utc};
14use std::path::PathBuf;
15
16#[derive(Debug, Clone)]
18pub struct CommitInfo {
19 pub hash: String,
21 pub message: String,
23 pub timestamp: DateTime<Utc>,
25 pub parent_hash: Option<String>,
27}
28
29pub struct FaiProtocol {
31 storage: storage::StorageManager,
32 database: database::DatabaseManager,
33 fai_path: PathBuf,
34}
35
36impl FaiProtocol {
37 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 pub fn init() -> Result<()> {
51 let fai_path = PathBuf::from(".fai");
52
53 std::fs::create_dir_all(&fai_path)?;
55 std::fs::create_dir_all(fai_path.join("objects"))?;
56
57 let _storage = storage::StorageManager::new(fai_path.clone())?;
59
60 let _database = database::DatabaseManager::new(&fai_path.join("db.sqlite"))?;
62
63 std::fs::write(fai_path.join("HEAD"), "ref: refs/heads/main")?;
65
66 Ok(())
67 }
68
69 pub fn add_file(&self, file_path: &str) -> Result<String> {
71 if !std::path::Path::new(file_path).exists() {
73 return Err(anyhow::anyhow!("File not found: {}", file_path));
74 }
75
76 let content = std::fs::read(file_path)?;
78 println!(
79 "DEBUG: Read {} bytes from file: {}",
80 content.len(),
81 file_path
82 );
83
84 let hash = self.storage.store(&content)?;
86 println!(
87 "DEBUG: Stored file content with hash: {} ({} bytes)",
88 hash,
89 content.len()
90 );
91
92 let size = std::fs::metadata(file_path)?.len();
94
95 self.database.add_to_staging(file_path, &hash, size)?;
97 println!("DEBUG: Added file to staging: {} -> {}", file_path, hash);
98
99 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 pub fn get_status(&self) -> Result<Vec<(String, String, u64)>> {
114 self.database.get_staged_files()
115 }
116
117 pub fn storage(&self) -> &storage::StorageManager {
119 &self.storage
120 }
121
122 pub fn database(&self) -> &database::DatabaseManager {
124 &self.database
125 }
126
127 pub fn commit(&self, message: &str) -> Result<String> {
129 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 let parent_hash = self.get_head()?;
138
139 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 self.database.create_commit(
152 &commit_hash,
153 message,
154 parent_hash.as_deref(),
155 &staged_files,
156 )?;
157
158 std::fs::write(self.fai_path.join("HEAD"), &commit_hash)?;
160
161 self.database.clear_staging()?;
163
164 Ok(commit_hash)
165 }
166
167 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 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 if content.starts_with("ref:") {
188 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};
200pub use storage::{ModelMetadata, StorageManager};