Skip to main content

pacenotes_installer\engine\execute/
install_package.rs

1/// Bytes written so far across every install in the run.
2pub struct Progress {
3    pub written: u64,
4    pub total: u64,
5}
6
7/// Which payload to install and what the ledger says about it, passed in
8/// because the cache index holds only the newest version, while a restore
9/// names an older payload that is still on disk.
10pub struct Installing<'a> {
11    pub key: &'a str,
12    pub sha256: &'a str,
13    pub version: &'a str,
14    pub exclusive_group: Option<&'a str>,
15}
16
17/// Copies out what the package replaces, records it as `installing`, then
18/// extracts, so a run that dies midway leaves files the next run knows are ours.
19pub fn install_package(
20    run: &mut super::run::RunContext,
21    what: &Installing,
22    files: &[crate::zip::FileEntry],
23    point: &str,
24    progress: &mut Progress,
25) -> Result<(), super::ExecuteError> {
26    let key = what.key;
27    let payload = std::fs::read(run.cache.payload_path(what.sha256))
28        .map_err(|e| super::ExecuteError::extract(key, e))?;
29    let archive =
30        crate::zip::Archive::open(&payload).map_err(|e| super::ExecuteError::extract(key, e))?;
31
32    let phase = crate::engine::Phase::BackingUp;
33    run.watcher
34        .phase(phase, key, progress.written, progress.total);
35    let mut entry = crate::ledger::LedgerEntry {
36        sha256: what.sha256.to_string(),
37        version: what.version.to_string(),
38        exclusive_group: what.exclusive_group.map(str::to_string),
39        state: crate::ledger::EntryState::Installing,
40        files: record_priors(run, files, point, &archive)?,
41    };
42    run.ledger
43        .put(key, entry.clone())
44        .map_err(super::ExecuteError::ledger)?;
45
46    let phase = crate::engine::Phase::Installing;
47    run.watcher
48        .phase(phase, key, progress.written, progress.total);
49    for file in files {
50        let recorded = entry.files.get_mut(&crate::ledger::fold(&file.path));
51        // Already these bytes, hashed when they were compared: nothing to write.
52        let identical = recorded
53            .as_ref()
54            .is_some_and(|recorded| recorded.prior == crate::ledger::Prior::Identical);
55        if !identical {
56            let bytes = archive
57                .read(&file.path)
58                .map_err(|e| super::ExecuteError::extract(key, e))?;
59            super::game_files::write_atomic(
60                &crate::relative_path::join(run.game_dir, &file.path),
61                &bytes,
62            )?;
63            if let Some(recorded) = recorded {
64                recorded.sha256 = crate::sha256::hex_digest(&bytes);
65            }
66        }
67        progress.written += file.size;
68        run.watcher
69            .phase(phase, &file.path, progress.written, progress.total);
70    }
71
72    entry.state = crate::ledger::EntryState::Installed;
73    run.ledger
74        .put(key, entry)
75        .map_err(super::ExecuteError::ledger)?;
76    run.watcher.log(&format!("installed {key}"));
77    Ok(())
78}
79
80/// The ledger's files for the package, each hash left empty until it is
81/// written, except an identical file's, which is already known.
82fn record_priors(
83    run: &mut super::run::RunContext,
84    files: &[crate::zip::FileEntry],
85    point: &str,
86    archive: &crate::zip::Archive,
87) -> Result<std::collections::BTreeMap<String, crate::ledger::LedgerFile>, super::ExecuteError> {
88    let mut recorded = std::collections::BTreeMap::new();
89    for file in files {
90        let (prior, sha256) = preserve_prior(run, point, file, archive)?;
91        let ledger_file = crate::ledger::LedgerFile {
92            path: file.path.clone(),
93            sha256,
94            prior,
95        };
96        recorded.insert(crate::ledger::fold(&file.path), ledger_file);
97    }
98    Ok(recorded)
99}
100
101/// Copies the user's file at this path into the restore point, unless there is
102/// none, it is already ours, or it already holds exactly the incoming bytes,
103/// whose hash then comes back with it.
104fn preserve_prior(
105    run: &mut super::run::RunContext,
106    point: &str,
107    file: &crate::zip::FileEntry,
108    archive: &crate::zip::Archive,
109) -> Result<(crate::ledger::Prior, String), super::ExecuteError> {
110    let target = crate::relative_path::join(run.game_dir, &file.path);
111    // An owned file's original was recorded by whichever entry first replaced it.
112    if !target.exists() || run.ledger.owns(&file.path) {
113        return Ok((crate::ledger::Prior::Absent, String::new()));
114    }
115    if let (Ok(existing), Ok(incoming)) = (std::fs::read(&target), archive.read(&file.path)) {
116        if existing == incoming {
117            let sha256 = crate::sha256::hex_digest(&incoming);
118            return Ok((crate::ledger::Prior::Identical, sha256));
119        }
120    }
121
122    let copy = super::restore_point::saved_copy(run.game_dir, point, &file.path);
123    super::game_files::copy_file(&target, &copy)?;
124    run.watcher
125        .log(&format!("saved the existing {}", file.path));
126    let prior = crate::ledger::Prior::Backup {
127        point: point.to_string(),
128    };
129    Ok((prior, String::new()))
130}