chug_cli/
action_builder.rs

1use 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        // Add new bottles
157        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        // Save new dependencies to the DB
188        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        // Remove old bottles
203        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    // HACK: Technically `name` does not need to live for 'a, but I can't figure out how to express that
218    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}