use std::collections::HashMap;
use std::collections::hash_map::DefaultHasher;
use std::hash::{Hash, Hasher};
use std::path::{Path, PathBuf};
use serde::{Deserialize, Serialize};
use crate::RagError;
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct FileEntry {
pub content_hash: u64,
pub chunk_ids: Vec<String>,
}
#[derive(Debug, Default, Serialize, Deserialize)]
pub struct FileManifest {
entries: HashMap<PathBuf, FileEntry>,
}
impl FileManifest {
pub fn load(index_dir: &Path) -> Self {
let path = index_dir.join("manifest.json");
std::fs::read_to_string(&path)
.ok()
.and_then(|raw| serde_json::from_str(&raw).ok())
.unwrap_or_default()
}
pub fn save(&self, index_dir: &Path) -> Result<(), RagError> {
let path = index_dir.join("manifest.json");
let data =
serde_json::to_string(self).map_err(|e| RagError::Io(std::io::Error::other(e)))?;
std::fs::write(path, data)?;
Ok(())
}
pub fn is_changed(&self, path: &Path, content: &str) -> bool {
let hash = content_hash(content);
match self.entries.get(path) {
Some(entry) => entry.content_hash != hash,
None => true,
}
}
pub fn chunk_ids(&self, path: &Path) -> &[String] {
self.entries
.get(path)
.map(|e| e.chunk_ids.as_slice())
.unwrap_or(&[])
}
pub fn record(&mut self, path: PathBuf, content: &str, chunk_ids: Vec<String>) {
self.entries.insert(
path,
FileEntry {
content_hash: content_hash(content),
chunk_ids,
},
);
}
pub fn remove(&mut self, path: &Path) -> Option<FileEntry> {
self.entries.remove(path)
}
pub fn paths(&self) -> impl Iterator<Item = &PathBuf> {
self.entries.keys()
}
}
fn content_hash(content: &str) -> u64 {
let mut h = DefaultHasher::new();
content.hash(&mut h);
h.finish()
}