1use std::collections::hash_map::DefaultHasher;
2use std::collections::HashMap;
3use std::hash::{Hash, Hasher};
4use std::path::{Path, PathBuf};
5
6use serde::{Deserialize, Serialize};
7
8use crate::RagError;
9
10#[derive(Debug, Clone, Serialize, Deserialize)]
11pub struct FileEntry {
12 pub content_hash: u64,
14 pub chunk_ids: Vec<String>,
16}
17
18#[derive(Debug, Default, Serialize, Deserialize)]
20pub struct FileManifest {
21 entries: HashMap<PathBuf, FileEntry>,
22}
23
24impl FileManifest {
25 pub fn load(index_dir: &Path) -> Self {
26 let path = index_dir.join("manifest.json");
27 std::fs::read_to_string(&path)
28 .ok()
29 .and_then(|raw| serde_json::from_str(&raw).ok())
30 .unwrap_or_default()
31 }
32
33 pub fn save(&self, index_dir: &Path) -> Result<(), RagError> {
34 let path = index_dir.join("manifest.json");
35 let data =
36 serde_json::to_string(self).map_err(|e| RagError::Io(std::io::Error::other(e)))?;
37 std::fs::write(path, data)?;
38 Ok(())
39 }
40
41 pub fn is_changed(&self, path: &Path, content: &str) -> bool {
43 let hash = content_hash(content);
44 match self.entries.get(path) {
45 Some(entry) => entry.content_hash != hash,
46 None => true,
47 }
48 }
49
50 pub fn chunk_ids(&self, path: &Path) -> &[String] {
52 self.entries
53 .get(path)
54 .map(|e| e.chunk_ids.as_slice())
55 .unwrap_or(&[])
56 }
57
58 pub fn record(&mut self, path: PathBuf, content: &str, chunk_ids: Vec<String>) {
60 self.entries.insert(
61 path,
62 FileEntry {
63 content_hash: content_hash(content),
64 chunk_ids,
65 },
66 );
67 }
68
69 pub fn remove(&mut self, path: &Path) -> Option<FileEntry> {
71 self.entries.remove(path)
72 }
73
74 pub fn paths(&self) -> impl Iterator<Item = &PathBuf> {
76 self.entries.keys()
77 }
78}
79
80fn content_hash(content: &str) -> u64 {
81 let mut h = DefaultHasher::new();
82 content.hash(&mut h);
83 h.finish()
84}