1use std::collections::{BTreeMap, BTreeSet};
14use std::path::{Path, PathBuf};
15use std::time::UNIX_EPOCH;
16
17use cadvm_store::ObjectId;
18use serde::{Deserialize, Serialize};
19
20use crate::error::{CoreError, Result};
21use crate::repo::Repository;
22use crate::worktree;
23
24const INDEX_VERSION: u32 = 1;
25
26#[derive(Debug, Clone, Serialize, Deserialize)]
27struct CacheEntry {
28 size: u64,
29 mtime_ns: i64,
30 hash: ObjectId,
31}
32
33#[derive(Debug, Clone, Serialize, Deserialize)]
35pub struct HashCache {
36 version: u32,
37 entries: BTreeMap<PathBuf, CacheEntry>,
38}
39
40impl Default for HashCache {
41 fn default() -> Self {
42 HashCache {
43 version: INDEX_VERSION,
44 entries: BTreeMap::new(),
45 }
46 }
47}
48
49fn mtime_ns(meta: &std::fs::Metadata) -> i64 {
50 meta.modified()
51 .ok()
52 .and_then(|t| t.duration_since(UNIX_EPOCH).ok())
53 .map(|d| d.as_nanos() as i64)
54 .unwrap_or(0)
55}
56
57impl HashCache {
58 pub fn load(repo: &Repository) -> HashCache {
61 let path = repo.cadvm_dir().join("index.json");
62 match std::fs::read(&path) {
63 Ok(bytes) => serde_json::from_slice(&bytes).unwrap_or_default(),
64 Err(_) => HashCache::default(),
65 }
66 }
67
68 pub fn save(&self, repo: &Repository) -> Result<()> {
70 let path = repo.cadvm_dir().join("index.json");
71 let mut bytes = serde_json::to_vec_pretty(self)?;
72 bytes.push(b'\n');
73 std::fs::write(&path, bytes).map_err(|e| CoreError::io(&path, e))
74 }
75
76 pub fn hash(&mut self, repo: &Repository, rel: &Path) -> Result<ObjectId> {
79 let path = worktree::abs_path(repo, rel);
80 let meta = std::fs::metadata(&path).map_err(|e| CoreError::io(&path, e))?;
81 let (size, mtime) = (meta.len(), mtime_ns(&meta));
82
83 if let Some(e) = self.entries.get(rel) {
84 if e.size == size && e.mtime_ns == mtime {
85 return Ok(e.hash.clone());
86 }
87 }
88
89 let hash = worktree::hash_working_file(repo, rel)?;
90 self.entries.insert(
91 rel.to_path_buf(),
92 CacheEntry {
93 size,
94 mtime_ns: mtime,
95 hash: hash.clone(),
96 },
97 );
98 Ok(hash)
99 }
100
101 pub fn record(&mut self, repo: &Repository, rel: &Path, hash: ObjectId) -> Result<()> {
104 let path = worktree::abs_path(repo, rel);
105 let meta = std::fs::metadata(&path).map_err(|e| CoreError::io(&path, e))?;
106 self.entries.insert(
107 rel.to_path_buf(),
108 CacheEntry {
109 size: meta.len(),
110 mtime_ns: mtime_ns(&meta),
111 hash,
112 },
113 );
114 Ok(())
115 }
116
117 pub fn retain(&mut self, keep: &BTreeSet<PathBuf>) {
119 self.entries.retain(|p, _| keep.contains(p));
120 }
121}