pub mod database;
pub mod network;
pub mod storage;
use anyhow::Result;
use chrono::{DateTime, Utc};
use std::path::PathBuf;
#[derive(Debug, Clone)]
pub struct CommitInfo {
pub hash: String,
pub message: String,
pub timestamp: DateTime<Utc>,
pub parent_hash: Option<String>,
}
pub struct FaiProtocol {
storage: storage::StorageManager,
database: database::DatabaseManager,
fai_path: PathBuf,
}
impl FaiProtocol {
pub fn new() -> Result<Self> {
let fai_path = PathBuf::from(".fai");
let storage = storage::StorageManager::new(fai_path.clone())?;
let database = database::DatabaseManager::new(&fai_path.join("db.sqlite"))?;
Ok(Self {
storage,
database,
fai_path,
})
}
pub fn init() -> Result<()> {
let fai_path = PathBuf::from(".fai");
std::fs::create_dir_all(&fai_path)?;
std::fs::create_dir_all(fai_path.join("objects"))?;
let _storage = storage::StorageManager::new(fai_path.clone())?;
let _database = database::DatabaseManager::new(&fai_path.join("db.sqlite"))?;
std::fs::write(fai_path.join("HEAD"), "ref: refs/heads/main")?;
Ok(())
}
pub fn add_file(&self, file_path: &str) -> Result<String> {
if !std::path::Path::new(file_path).exists() {
return Err(anyhow::anyhow!("File not found: {}", file_path));
}
let content = std::fs::read(file_path)?;
println!(
"DEBUG: Read {} bytes from file: {}",
content.len(),
file_path
);
let hash = self.storage.store(&content)?;
println!(
"DEBUG: Stored file content with hash: {} ({} bytes)",
hash,
content.len()
);
let size = std::fs::metadata(file_path)?.len();
self.database.add_to_staging(file_path, &hash, size)?;
println!("DEBUG: Added file to staging: {} -> {}", file_path, hash);
if !self.storage.exists(&hash) {
return Err(anyhow::anyhow!(
"Failed to store file: {} with hash: {}",
file_path,
hash
));
}
println!("DEBUG: Verified file exists in storage: {}", hash);
Ok(hash)
}
pub fn get_status(&self) -> Result<Vec<(String, String, u64)>> {
self.database.get_staged_files()
}
pub fn storage(&self) -> &storage::StorageManager {
&self.storage
}
pub fn database(&self) -> &database::DatabaseManager {
&self.database
}
pub fn commit(&self, message: &str) -> Result<String> {
let staged_files = self.database.get_staged_files()?;
if staged_files.is_empty() {
return Err(anyhow::anyhow!("Nothing to commit"));
}
let parent_hash = self.get_head()?;
let commit_data = format!(
"{}{}{:?}",
Utc::now().timestamp_millis(),
message,
staged_files
);
let mut hasher = blake3::Hasher::new();
hasher.update(commit_data.as_bytes());
let commit_hash = hasher.finalize().to_hex().to_string();
self.database.create_commit(
&commit_hash,
message,
parent_hash.as_deref(),
&staged_files,
)?;
std::fs::write(self.fai_path.join("HEAD"), &commit_hash)?;
self.database.clear_staging()?;
Ok(commit_hash)
}
pub fn get_log(&self) -> Result<Vec<CommitInfo>> {
let commits = self.database.get_commit_history(None)?;
Ok(commits
.into_iter()
.map(|c| CommitInfo {
hash: c.hash,
message: c.message,
timestamp: c.timestamp,
parent_hash: c.parent_hash,
})
.collect())
}
fn get_head(&self) -> Result<Option<String>> {
let head_path = self.fai_path.join("HEAD");
if head_path.exists() {
let content = std::fs::read_to_string(&head_path)?;
if content.starts_with("ref:") {
Ok(None)
} else {
Ok(Some(content.trim().to_string()))
}
} else {
Ok(None)
}
}
}
pub use database::{Commit, DatabaseManager};
pub use storage::{ModelMetadata, StorageManager};