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; 16],
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) => match bitcode::decode::<Vec<FileEntry>>(&data) {
27 Ok(entries_vec) => {
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(_) => {
35 HashMap::new()
42 }
43 },
44 Err(error) if error.kind() == std::io::ErrorKind::NotFound => HashMap::new(),
45 Err(error) => return Err(error.into()),
46 };
47 Ok(Self { path, entries })
48 }
49
50 fn flush(&self) -> Result<()> {
51 let mut entries_vec = Vec::with_capacity(self.entries.len());
52 entries_vec.extend(self.entries.values().cloned());
53 let encoded = bitcode::encode(&entries_vec);
54 let tmp_path = self.path.with_extension("bin.tmp");
55 std::fs::write(&tmp_path, &encoded)?;
56 std::fs::rename(&tmp_path, &self.path)?;
57 Ok(())
58 }
59
60 pub fn upsert(&mut self, entry: &FileEntry) -> Result<()> {
61 self.entries.insert(entry.path.clone(), entry.clone());
62 self.flush()
63 }
64
65 pub fn bulk_upsert(&mut self, entries: &[FileEntry]) -> Result<()> {
66 self.apply_changes(&[], entries)
67 }
68
69 pub fn bulk_remove(&mut self, paths: &[String]) -> Result<()> {
70 self.apply_changes(paths, &[])
71 }
72
73 pub fn apply_changes(
74 &mut self,
75 paths_to_remove: &[String],
76 entries_to_upsert: &[FileEntry],
77 ) -> Result<()> {
78 if paths_to_remove.is_empty() && entries_to_upsert.is_empty() {
79 return Ok(());
80 }
81 for path in paths_to_remove {
82 self.entries.remove(path);
83 }
84 for entry in entries_to_upsert {
85 self.entries.insert(entry.path.clone(), entry.clone());
86 }
87 self.flush()
88 }
89
90 pub fn get(&self, path: &str) -> Result<Option<FileEntry>> {
91 Ok(self.entries.get(path).cloned())
92 }
93
94 pub fn remove(&mut self, path: &str) -> Result<()> {
95 self.entries.remove(path);
96 self.flush()
97 }
98
99 pub fn all_paths(&self) -> Result<Vec<String>> {
100 Ok(self.entries.keys().cloned().collect())
101 }
102
103 pub fn all_entries(&self) -> Result<Vec<FileEntry>> {
104 Ok(self.entries.values().cloned().collect())
105 }
106
107 pub fn entries(&self) -> &HashMap<String, FileEntry> {
108 &self.entries
109 }
110
111 pub fn entries_by_path(&self) -> Result<HashMap<String, FileEntry>> {
112 Ok(self.entries.clone())
113 }
114
115 pub fn clear(&mut self) -> Result<()> {
116 self.entries.clear();
117 self.flush()
118 }
119}