Skip to main content

chkpt_core/index/
mod.rs

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}