pacenotes_installer\ledger/
document.rs1const LEDGER_VERSION: u32 = 1;
3
4#[derive(serde::Serialize, serde::Deserialize, Default)]
5pub(super) struct Document {
6 #[serde(default)]
7 version: u32,
8 #[serde(default)]
9 pub(super) packages: std::collections::BTreeMap<String, super::LedgerEntry>,
10}
11
12#[derive(Clone, Copy, PartialEq, Eq, Debug)]
14pub enum LedgerState {
15 Missing,
16 Unreadable,
18 Loaded,
19}
20
21pub struct Ledger {
22 dir: std::path::PathBuf,
23 pub(super) document: Document,
24 state: LedgerState,
25}
26
27impl Ledger {
28 pub fn open(game_dir: impl AsRef<std::path::Path>) -> Ledger {
29 let dir = game_dir.as_ref().join(super::STATE_DIR);
30 let (document, state) = match std::fs::read(dir.join(super::LEDGER_FILE)) {
31 Err(_) => (Document::default(), LedgerState::Missing),
32 Ok(bytes) => match serde_json::from_slice::<Document>(&bytes) {
33 Ok(document) if document.version <= LEDGER_VERSION => {
34 (document, LedgerState::Loaded)
35 }
36 Ok(_) | Err(_) => (Document::default(), LedgerState::Unreadable),
37 },
38 };
39 Ledger {
40 dir,
41 document,
42 state,
43 }
44 }
45
46 pub fn state(&self) -> LedgerState {
47 self.state
48 }
49
50 pub fn state_dir(&self) -> &std::path::Path {
52 &self.dir
53 }
54
55 pub fn put(&mut self, key: &str, entry: super::LedgerEntry) -> std::io::Result<()> {
56 self.document.packages.insert(key.to_string(), entry);
57 self.save()
58 }
59
60 pub fn mark_installed(&mut self, key: &str) -> std::io::Result<()> {
62 if let Some(entry) = self.document.packages.get_mut(key) {
63 entry.state = super::EntryState::Installed;
64 }
65 self.save()
66 }
67
68 pub fn remove(&mut self, key: &str) -> std::io::Result<()> {
69 self.document.packages.remove(key);
70 self.save()
71 }
72
73 pub fn replace_all(
75 &mut self,
76 packages: std::collections::BTreeMap<String, super::LedgerEntry>,
77 ) -> std::io::Result<()> {
78 self.document.packages = packages;
79 self.save()
80 }
81
82 pub fn save_snapshot(&self, path: &std::path::Path) -> std::io::Result<()> {
84 let json = serde_json::to_vec_pretty(&self.document.packages)
85 .map_err(|e| std::io::Error::new(std::io::ErrorKind::InvalidData, e))?;
86 std::fs::write(path, json)
87 }
88
89 pub fn read_snapshot(
91 path: &std::path::Path,
92 ) -> Option<std::collections::BTreeMap<String, super::LedgerEntry>> {
93 let bytes = std::fs::read(path).ok()?;
94 serde_json::from_slice(&bytes).ok()
95 }
96
97 pub fn save(&mut self) -> std::io::Result<()> {
101 if self.state == LedgerState::Unreadable {
102 return Err(std::io::Error::new(
103 std::io::ErrorKind::InvalidData,
104 "this game folder has an installation record this version cannot read; \
105 it was probably written by a newer installer",
106 ));
107 }
108 std::fs::create_dir_all(&self.dir)?;
109 self.document.version = LEDGER_VERSION;
110 let json = serde_json::to_vec_pretty(&self.document)
111 .map_err(|e| std::io::Error::new(std::io::ErrorKind::InvalidData, e))?;
112
113 let temp = self.dir.join(format!("{}.tmp", super::LEDGER_FILE));
114 std::fs::write(&temp, &json)?;
115 std::fs::rename(&temp, self.dir.join(super::LEDGER_FILE))?;
116 self.state = LedgerState::Loaded;
117 Ok(())
118 }
119}