1use crate::error::Result;
2use bitcode::{Decode, Encode};
3use std::collections::HashMap;
4use std::path::{Path, PathBuf};
5
6#[derive(Debug, Clone, Encode, Decode)]
7pub struct FileEntry {
8 pub path: String,
9 pub blob_hash: [u8; 32],
10 pub size: u64,
11 pub mtime_secs: i64,
12 pub mtime_nanos: i64,
13 pub inode: Option<u64>,
14 pub mode: u32,
15}
16
17pub struct FileIndex {
18 path: PathBuf,
19 entries: HashMap<String, FileEntry>,
20}
21
22impl FileIndex {
23 pub fn open<P: AsRef<Path>>(path: P) -> Result<Self> {
24 let path = path.as_ref().to_path_buf();
25 let entries = match std::fs::read(&path) {
26 Ok(data) => {
27 let entries_vec: Vec<FileEntry> = bitcode::decode(&data)?;
28 let mut entries = HashMap::with_capacity(entries_vec.len());
29 for entry in entries_vec {
30 entries.insert(entry.path.clone(), entry);
31 }
32 entries
33 }
34 Err(error) if error.kind() == std::io::ErrorKind::NotFound => HashMap::new(),
35 Err(error) => return Err(error.into()),
36 };
37 Ok(Self { path, entries })
38 }
39
40 fn flush(&self) -> Result<()> {
41 let mut entries_vec = Vec::with_capacity(self.entries.len());
42 entries_vec.extend(self.entries.values().cloned());
43 let encoded = bitcode::encode(&entries_vec);
44 let tmp_path = self.path.with_extension("bin.tmp");
45 std::fs::write(&tmp_path, &encoded)?;
46 std::fs::rename(&tmp_path, &self.path)?;
47 Ok(())
48 }
49
50 pub fn upsert(&mut self, entry: &FileEntry) -> Result<()> {
51 self.entries.insert(entry.path.clone(), entry.clone());
52 self.flush()
53 }
54
55 pub fn bulk_upsert(&mut self, entries: &[FileEntry]) -> Result<()> {
56 self.apply_changes(&[], entries)
57 }
58
59 pub fn bulk_remove(&mut self, paths: &[String]) -> Result<()> {
60 self.apply_changes(paths, &[])
61 }
62
63 pub fn apply_changes(
64 &mut self,
65 paths_to_remove: &[String],
66 entries_to_upsert: &[FileEntry],
67 ) -> Result<()> {
68 if paths_to_remove.is_empty() && entries_to_upsert.is_empty() {
69 return Ok(());
70 }
71 for path in paths_to_remove {
72 self.entries.remove(path);
73 }
74 for entry in entries_to_upsert {
75 self.entries.insert(entry.path.clone(), entry.clone());
76 }
77 self.flush()
78 }
79
80 pub fn get(&self, path: &str) -> Result<Option<FileEntry>> {
81 Ok(self.entries.get(path).cloned())
82 }
83
84 pub fn remove(&mut self, path: &str) -> Result<()> {
85 self.entries.remove(path);
86 self.flush()
87 }
88
89 pub fn all_paths(&self) -> Result<Vec<String>> {
90 Ok(self.entries.keys().cloned().collect())
91 }
92
93 pub fn all_entries(&self) -> Result<Vec<FileEntry>> {
94 Ok(self.entries.values().cloned().collect())
95 }
96
97 pub fn entries(&self) -> &HashMap<String, FileEntry> {
98 &self.entries
99 }
100
101 pub fn entries_by_path(&self) -> Result<HashMap<String, FileEntry>> {
102 Ok(self.entries.clone())
103 }
104
105 pub fn clear(&mut self) -> Result<()> {
106 self.entries.clear();
107 self.flush()
108 }
109}