pacenotes_installer\worker/
apply_selection.rs1pub fn apply_selection(
4 job: &super::InstallJob,
5 cache: &pacenotes_installer::cache::Cache,
6 reporter: &super::Reporter,
7) -> Result<super::Applied, String> {
8 let mut ledger = pacenotes_installer::ledger::Ledger::open(&job.game_dir);
9 let available = cache
10 .entries()
11 .into_iter()
12 .map(|entry| (entry.key(), entry))
13 .collect();
14 let choice_categories: Vec<&str> = job.choice_categories.iter().map(String::as_str).collect();
15 let plan = pacenotes_installer::engine::plan(&pacenotes_installer::engine::PlanInput {
16 game: job.game.id(),
17 selection: &job.selection,
18 choice_categories: &choice_categories,
19 installed: ledger.packages(),
20 available: &available,
21 })
22 .map_err(|e| e.to_string())?;
23
24 let mut watcher = PostingWatcher { reporter };
25 let prepared = pacenotes_installer::engine::preflight(
26 &plan,
27 &job.game_dir,
28 cache,
29 job.facts,
30 &mut watcher,
31 )
32 .map_err(|e| e.to_string())?;
33 let overlaps = log_overlaps(&plan, &prepared, reporter);
34
35 let outcome = pacenotes_installer::engine::execute(
36 &plan,
37 &prepared,
38 &job.game_dir,
39 cache,
40 &mut ledger,
41 &mut watcher,
42 )
43 .map_err(|e| e.to_string())?;
44 report_overlaps(job, &overlaps, reporter);
45
46 let configured = if plan.is_empty() {
47 Vec::new()
48 } else {
49 configure(
50 job.game,
51 &job.game_dir,
52 &job.picks,
53 &mut ledger,
54 &mut watcher,
55 )?
56 };
57 Ok(super::Applied {
58 installed: outcome.installed,
59 uninstalled: outcome.uninstalled,
60 left_alone: outcome.left_alone,
61 configured,
62 overlaps: overlaps.len(),
63 restore_point: outcome.restore_point,
64 })
65}
66
67fn log_overlaps(
69 plan: &pacenotes_installer::engine::Plan,
70 prepared: &pacenotes_installer::engine::Preflight,
71 reporter: &super::Reporter,
72) -> Vec<pacenotes_installer::engine::Overlap> {
73 let install_order: Vec<String> = plan.installs().iter().map(|key| key.to_string()).collect();
74 let overlaps = pacenotes_installer::engine::overlaps(&install_order, &prepared.paths());
75 for overlap in &overlaps {
76 reporter.log(format!(
77 "{} is written by more than one package ({}); the last one wins",
78 overlap.path,
79 overlap.packages.join(", ")
80 ));
81 }
82 overlaps
83}
84
85fn report_overlaps(
88 job: &super::InstallJob,
89 overlaps: &[pacenotes_installer::engine::Overlap],
90 reporter: &super::Reporter,
91) {
92 if overlaps.is_empty() {
93 return;
94 }
95 let api = crate::api::Api::new(&job.base_url, None);
96 match api.report_conflicts(job.game.id(), overlaps, &job.cancel) {
97 Ok(()) => reporter.log("the overlapping files were reported to the server".into()),
98 Err(e) => reporter.log(format!(
99 "could not report the overlapping files — {}",
100 e.message
101 )),
102 }
103}
104
105pub(super) fn configure(
109 game: pacenotes_installer::gamedir::Game,
110 game_dir: &std::path::Path,
111 picks: &std::collections::BTreeMap<String, String>,
112 ledger: &mut pacenotes_installer::ledger::Ledger,
113 watcher: &mut PostingWatcher,
114) -> Result<Vec<String>, String> {
115 let reporter = watcher.reporter;
116 reporter.send(super::Progress {
117 phase: super::Phase::Configure,
118 item: String::new(),
119 item_done: 0,
120 item_size: 0,
121 done: 0,
122 total: 0,
123 log: None,
124 });
125 let configured = match pacenotes_installer::install::configure(game, game_dir, picks) {
126 Ok(configured) => configured,
127 Err(e) => {
128 reporter.log(format!("configuration skipped — {e}"));
129 return Ok(Vec::new());
130 }
131 };
132 for note in configured.notes {
133 reporter.log(note);
134 }
135 pacenotes_installer::engine::record_configured(game_dir, &configured.files, ledger, watcher)
136 .map_err(|e| e.to_string())?;
137 Ok(configured.files)
138}
139
140pub(super) struct PostingWatcher<'a> {
142 pub(super) reporter: &'a super::Reporter,
143}
144
145impl pacenotes_installer::engine::Watcher for PostingWatcher<'_> {
146 fn phase(
147 &mut self,
148 phase: pacenotes_installer::engine::Phase,
149 item: &str,
150 done: u64,
151 total: u64,
152 ) {
153 self.reporter.send(super::Progress {
154 phase: scene_phase(phase),
155 item: item.to_string(),
156 item_done: 0,
157 item_size: 0,
158 done,
159 total,
160 log: None,
161 });
162 }
163
164 fn log(&mut self, line: &str) {
165 self.reporter.log(line.to_string());
166 }
167}
168
169fn scene_phase(phase: pacenotes_installer::engine::Phase) -> super::Phase {
171 match phase {
172 pacenotes_installer::engine::Phase::Checking => super::Phase::Checking,
173 pacenotes_installer::engine::Phase::Uninstalling => super::Phase::Uninstall,
174 pacenotes_installer::engine::Phase::BackingUp => super::Phase::Backup,
175 pacenotes_installer::engine::Phase::Installing => super::Phase::Install,
176 }
177}