chug_cli/
action_builder.rs1use std::collections::{BTreeMap, BTreeSet};
2
3use rayon::iter::{IntoParallelRefIterator, ParallelIterator};
4
5use crate::{
6 db::models::{Dependency, DownloadedBottle},
7 formulae::Formula,
8};
9
10#[derive(Debug)]
11pub struct BottleForestSnapshot {
12 bottles: BTreeMap<i32, DownloadedBottle>,
13 dependencies: Vec<Dependency>,
14}
15
16#[derive(Debug)]
17pub struct ActionBuilder<'a> {
18 snapshot: &'a BottleForestSnapshot,
19 bottles: BTreeSet<BottleRef<'a>>,
20 dependencies: BTreeSet<(Option<BottleRef<'a>>, BottleRef<'a>)>,
21}
22
23#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, PartialOrd, Ord)]
24struct BottleRef<'a> {
25 name: &'a str,
26 version: &'a str,
27}
28
29impl BottleForestSnapshot {
30 pub fn new() -> anyhow::Result<Self> {
31 let bottles = DownloadedBottle::get_all()?
32 .into_iter()
33 .map(|b| (b.id(), b))
34 .collect();
35 let dependencies = Dependency::get_all()?;
36
37 Ok(Self {
38 bottles,
39 dependencies,
40 })
41 }
42}
43
44impl<'a> ActionBuilder<'a> {
45 pub fn new(snapshot: &'a BottleForestSnapshot) -> Self {
46 let bottles = snapshot.bottles.values().map(BottleRef::from).collect();
47 let dependencies = snapshot
48 .dependencies
49 .iter()
50 .map(|dep| {
51 (
52 dep.dependent_id()
53 .map(|id| snapshot.bottles.get(&id).unwrap().into()),
54 snapshot.bottles.get(&dep.dependency_id()).unwrap().into(),
55 )
56 })
57 .collect();
58
59 Self {
60 snapshot,
61 bottles,
62 dependencies,
63 }
64 }
65
66 pub fn add_bottles(mut self, bottles: &[String]) -> anyhow::Result<Self> {
67 for name in bottles {
68 if self
69 .bottles
70 .range(BottleRef { name, version: "" }..)
71 .take_while(|b| b.name == name)
72 .count()
73 > 0
74 {
75 continue;
76 }
77
78 let formula = Formula::get(name)?;
79 self.bottles.insert(formula.into());
80 self.dependencies.insert((None, formula.into()));
81 }
82
83 Ok(self)
84 }
85
86 pub fn remove_all(mut self) -> Self {
87 self.bottles.clear();
88
89 self
90 }
91
92 pub fn remove_bottles(mut self, bottles: &'a [String]) -> anyhow::Result<Self> {
93 for alias in bottles {
94 let formula = Formula::get(alias);
95 let name = formula.as_ref().map_or(alias.as_str(), |f| &f.name);
96
97 let bottles_with_name = self
98 .bottles
99 .range(BottleRef { name, version: "" }..)
100 .take_while(|b| b.name == name)
101 .copied()
102 .collect::<Vec<BottleRef<'a>>>();
103
104 if bottles_with_name.is_empty() {
105 if formula.is_ok() {
106 anyhow::bail!("Could not remove {name} as it is not installed");
107 } else {
108 anyhow::bail!("No such formula {name}");
109 }
110 }
111
112 for bottle in bottles_with_name {
113 self.bottles.remove(&bottle);
114 }
115 }
116
117 Ok(self)
118 }
119
120 pub fn update(mut self) -> anyhow::Result<Self> {
121 let roots = self
122 .dependencies
123 .iter()
124 .filter(|(a, _)| a.is_none())
125 .map(|(_, b)| Formula::get_exact(b.name))
126 .collect::<Result<Vec<_>, _>>()?;
127
128 self.bottles = roots.iter().copied().map(BottleRef::from).collect();
129 self.dependencies = roots
130 .iter()
131 .copied()
132 .map(|f| (None, BottleRef::from(f)))
133 .collect();
134
135 Ok(self)
136 }
137
138 pub fn run(mut self) -> anyhow::Result<()> {
139 self.fix_dependencies()?;
140
141 let (to_add, to_remove) = diff_bottles(
142 &self
143 .snapshot
144 .bottles
145 .values()
146 .map(BottleRef::from)
147 .collect(),
148 &self.bottles,
149 );
150
151 anyhow::ensure!(
152 !to_add.is_empty() || !to_remove.is_empty(),
153 "No bottles to add or remove",
154 );
155
156 let downloaded_bottles = to_add
158 .par_iter()
159 .map(|bottle_ref| {
160 let formula = Formula::get_exact(bottle_ref.name)?;
161 anyhow::ensure!(
162 formula.versions.stable == bottle_ref.version,
163 "Attempted to install an unavailable version of {}",
164 bottle_ref.name,
165 );
166 anyhow::ensure!(
167 formula.versions.bottle,
168 "Formula {:?} does not have a corresponding bottle",
169 formula.name,
170 );
171
172 let bottle = formula.download_bottle()?;
173
174 Ok(bottle)
175 })
176 .collect::<anyhow::Result<Vec<_>>>()?;
177
178 downloaded_bottles
179 .par_iter()
180 .map(|bottle| {
181 bottle.link()?;
182
183 Ok(())
184 })
185 .collect::<anyhow::Result<Vec<()>>>()?;
186
187 let bottles_by_ref = self
189 .snapshot
190 .bottles
191 .values()
192 .chain(&downloaded_bottles)
193 .map(|b| (BottleRef::from(b), b))
194 .collect::<BTreeMap<_, _>>();
195
196 Dependency::replace_all(
197 self.dependencies
198 .iter()
199 .map(|(a, b)| (a.map(|a| bottles_by_ref[&a]), bottles_by_ref[b])),
200 )?;
201
202 to_remove
204 .par_iter()
205 .map(|bottle_ref| {
206 let bottle = bottles_by_ref[bottle_ref];
207 bottle.unlink()?;
208 bottle.remove()?;
209
210 Ok(())
211 })
212 .collect::<anyhow::Result<Vec<()>>>()?;
213
214 Ok(())
215 }
216
217 fn get_bottle(&self, name: &'a str) -> Option<BottleRef<'a>> {
219 self.bottles
220 .range(BottleRef { name, version: "" }..)
221 .take_while(|b| b.name == name)
222 .cloned()
223 .next()
224 }
225
226 fn get_dependencies(&self, bottle_ref: BottleRef<'a>) -> impl Iterator<Item = BottleRef<'a>> {
227 self.dependencies
228 .range(
229 (
230 Some(bottle_ref),
231 BottleRef {
232 name: "",
233 version: "",
234 },
235 )..,
236 )
237 .take_while(move |(a, _)| a == &Some(bottle_ref))
238 .map(|&(_, b)| b)
239 }
240
241 fn fix_dependencies(&mut self) -> anyhow::Result<()> {
242 self.add_dependencies()?;
243 self.remove_orphans();
244 Ok(())
245 }
246
247 fn add_dependencies(&mut self) -> Result<(), anyhow::Error> {
248 let mut stack = Vec::new();
249 for bottle in self.bottles.iter() {
250 let Ok(formula) = Formula::get_exact(bottle.name) else {
251 continue;
252 };
253 if formula.versions.stable != bottle.version {
254 continue;
255 }
256 stack.push(formula);
257 }
258
259 while let Some(formula) = stack.pop() {
260 let bottle_ref = BottleRef::from(formula);
261 for dependency_name in &formula.dependencies {
262 if let Some(dependency_ref) = self.get_bottle(dependency_name) {
263 self.dependencies.insert((Some(bottle_ref), dependency_ref));
264 continue;
265 }
266
267 let dependency = Formula::get_exact(dependency_name)?;
268 let dependency_ref = BottleRef::from(dependency);
269 self.bottles.insert(dependency_ref);
270 self.dependencies.insert((Some(bottle_ref), dependency_ref));
271 stack.push(dependency);
272 }
273 }
274
275 Ok(())
276 }
277
278 fn remove_orphans(&mut self) {
279 let mut ref_counts = self
280 .bottles
281 .iter()
282 .map(|&b| (b, 0))
283 .collect::<BTreeMap<_, _>>();
284
285 self.dependencies.retain(|&(a, b)| {
286 if let Some(bottle) = a {
287 if !self.bottles.contains(&bottle) {
288 return false;
289 }
290 }
291 if !self.bottles.contains(&b) {
292 return false;
293 }
294
295 let ref_count = ref_counts.get_mut(&b).unwrap();
296 *ref_count += 1;
297
298 true
299 });
300
301 let mut stack = Vec::new();
302 for bottle in self.bottles.iter() {
303 if ref_counts[bottle] == 0 {
304 stack.push(*bottle);
305 }
306 }
307 while let Some(bottle) = stack.pop() {
308 self.bottles.remove(&bottle);
309
310 for dependency in self.get_dependencies(bottle).collect::<Vec<_>>() {
311 self.dependencies.remove(&(Some(bottle), dependency));
312
313 let ref_count = ref_counts.get_mut(&dependency).unwrap();
314 *ref_count -= 1;
315 if *ref_count == 0 {
316 stack.push(dependency);
317 }
318 }
319 }
320 }
321}
322
323fn diff_bottles<'a>(
324 before: &BTreeSet<BottleRef<'a>>,
325 after: &BTreeSet<BottleRef<'a>>,
326) -> (BTreeSet<BottleRef<'a>>, BTreeSet<BottleRef<'a>>) {
327 let added = after.difference(before).cloned().collect();
328 let removed = before.difference(after).cloned().collect();
329 (added, removed)
330}
331
332impl<'a> From<&'a DownloadedBottle> for BottleRef<'a> {
333 fn from(bottle: &'a DownloadedBottle) -> Self {
334 Self {
335 name: bottle.name(),
336 version: bottle.version(),
337 }
338 }
339}
340
341impl<'a> From<&'a Formula> for BottleRef<'a> {
342 fn from(formula: &'a Formula) -> Self {
343 Self {
344 name: &formula.name,
345 version: &formula.versions.stable,
346 }
347 }
348}