apt_cmd/
apt_mark.rs

1// Copyright 2021-2022 System76 <info@system76.com>
2// SPDX-License-Identifier: MPL-2.0
3
4use anyhow::Context;
5use as_result::IntoResult;
6use std::io;
7use std::process::Stdio;
8use tokio::io::{AsyncBufReadExt, BufReader};
9use tokio::process::Command;
10
11#[derive(AsMut, Deref, DerefMut)]
12#[as_mut(forward)]
13pub struct AptMark(Command);
14
15impl AptMark {
16    #[allow(clippy::new_without_default)]
17    pub fn new() -> Self {
18        let mut cmd = Command::new("apt-mark");
19        cmd.env("LANG", "C");
20        Self(cmd)
21    }
22
23    pub async fn hold<I, S>(mut self, packages: I) -> io::Result<()>
24    where
25        I: IntoIterator<Item = S>,
26        S: AsRef<std::ffi::OsStr>,
27    {
28        self.arg("hold");
29        self.args(packages);
30        self.status().await
31    }
32
33    pub async fn unhold<I, S>(mut self, packages: I) -> io::Result<()>
34    where
35        I: IntoIterator<Item = S>,
36        S: AsRef<std::ffi::OsStr>,
37    {
38        self.arg("unhold");
39        self.args(packages);
40        self.status().await
41    }
42
43    /// Shows packages that have been held.
44    pub async fn held() -> anyhow::Result<Vec<String>> {
45        scrape_packages(AptMark::new().arg("showhold")).await
46    }
47
48    /// Obtains a list of automatically-installed packages.
49    pub async fn auto_installed() -> anyhow::Result<Vec<String>> {
50        scrape_packages(AptMark::new().arg("showauto")).await
51    }
52
53    /// Obtains a list of manually-installed packages.
54    pub async fn manually_installed() -> anyhow::Result<Vec<String>> {
55        scrape_packages(AptMark::new().arg("showmanual")).await
56    }
57
58    /// Obtains list of all installed packages.
59    pub async fn installed() -> anyhow::Result<Vec<String>> {
60        let (mut auto, manual) =
61            futures::future::try_join(AptMark::auto_installed(), AptMark::manually_installed())
62                .await?;
63
64        auto.extend_from_slice(&manual);
65        Ok(auto)
66    }
67
68    pub async fn status(mut self) -> io::Result<()> {
69        self.0.status().await?.into_result()
70    }
71}
72
73async fn scrape_packages(command: &mut tokio::process::Command) -> anyhow::Result<Vec<String>> {
74    let mut child = command
75        .stdout(Stdio::piped())
76        .stderr(Stdio::inherit())
77        .spawn()
78        .context("failed to spawn `apt-mark showmanual` command")?;
79
80    let mut stdout = BufReader::new(child.stdout.take().unwrap());
81
82    let mut packages = Vec::new();
83    let mut buffer = String::new();
84
85    loop {
86        let read = stdout
87            .read_line(&mut buffer)
88            .await
89            .context("failed to read output of `apt-mark showmanual`")?;
90
91        if read == 0 {
92            break;
93        }
94
95        packages.push(buffer.trim_end().to_owned());
96        buffer.clear();
97    }
98
99    let _ = child
100        .wait()
101        .await
102        .context("`apt-mark showmanual` exited with failure status");
103
104    Ok(packages)
105}