Skip to main content

pacenotes_installer\cache/
index.rs

1/// Bumped when the shape changes in a way an older installer must not read.
2const INDEX_VERSION: u32 = 1;
3
4#[derive(serde::Serialize, serde::Deserialize, Default)]
5struct Index {
6    /// Absent in the first builds' files.
7    #[serde(default)]
8    version: u32,
9    #[serde(default)]
10    packages: std::collections::BTreeMap<String, crate::tree::CachedPackage>,
11}
12
13/// How `cache.json` was found. Only `Missing` means the payloads on disk are
14/// really unreferenced; after `Unreadable` every one of them would look orphaned.
15#[derive(Clone, Copy, PartialEq, Eq, Debug)]
16pub enum IndexState {
17    Missing,
18    /// Unparseable, or written by a newer build.
19    Unreadable,
20    Loaded,
21}
22
23pub struct Cache {
24    pub(super) root: std::path::PathBuf,
25    index: Index,
26    pub(super) state: IndexState,
27}
28
29impl Cache {
30    /// An index that is missing or cannot be read opens as an empty cache and
31    /// leaves the payloads on disk; `index_state` says which happened.
32    pub fn open(root: impl Into<std::path::PathBuf>) -> Cache {
33        let root = root.into();
34        let (index, state) = match std::fs::read(root.join(super::INDEX_FILE)) {
35            Err(_) => (Index::default(), IndexState::Missing),
36            Ok(bytes) => match serde_json::from_slice::<Index>(&bytes) {
37                Ok(index) if index.version <= INDEX_VERSION => (index, IndexState::Loaded),
38                Ok(_) | Err(_) => (Index::default(), IndexState::Unreadable),
39            },
40        };
41        Cache { root, index, state }
42    }
43
44    pub fn index_state(&self) -> IndexState {
45        self.state
46    }
47
48    pub fn root(&self) -> &std::path::Path {
49        &self.root
50    }
51
52    pub fn get(&self, key: &str) -> Option<&crate::tree::CachedPackage> {
53        self.index.packages.get(key)
54    }
55
56    /// Every entry whose payload is still on disk, so nothing missing is offered as downloaded.
57    pub fn entries(&self) -> Vec<crate::tree::CachedPackage> {
58        self.index
59            .packages
60            .values()
61            .filter(|entry| self.has_payload(&entry.sha256))
62            .cloned()
63            .collect()
64    }
65
66    /// Whether an index entry names this payload.
67    pub(super) fn names(&self, sha256: &str) -> bool {
68        self.index
69            .packages
70            .values()
71            .any(|entry| entry.sha256 == sha256)
72    }
73
74    pub(super) fn known_digests(&self) -> std::collections::BTreeSet<&str> {
75        self.index
76            .packages
77            .values()
78            .map(|entry| entry.sha256.as_str())
79            .collect()
80    }
81
82    /// Saves at once, so an interrupted run keeps the download that just finished.
83    pub fn insert(&mut self, package: crate::tree::CachedPackage) -> std::io::Result<()> {
84        self.index.packages.insert(package.key(), package);
85        self.save()
86    }
87
88    pub fn remove(&mut self, key: &str) -> std::io::Result<()> {
89        self.index.packages.remove(key);
90        self.save()
91    }
92
93    /// A temp file renamed over the old index, which `std::fs::rename` replaces
94    /// in one step, so a crash never leaves the cache without one.
95    pub fn save(&mut self) -> std::io::Result<()> {
96        self.index.version = INDEX_VERSION;
97        std::fs::create_dir_all(&self.root)?;
98        let json = serde_json::to_vec_pretty(&self.index)
99            .map_err(|e| std::io::Error::new(std::io::ErrorKind::InvalidData, e))?;
100
101        let temp = self.root.join(format!("{}.tmp", super::INDEX_FILE));
102        std::fs::write(&temp, &json)?;
103        std::fs::rename(&temp, self.root.join(super::INDEX_FILE))?;
104        self.state = IndexState::Loaded;
105        Ok(())
106    }
107}