1use 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 pub async fn held() -> anyhow::Result<Vec<String>> {
45 scrape_packages(AptMark::new().arg("showhold")).await
46 }
47
48 pub async fn auto_installed() -> anyhow::Result<Vec<String>> {
50 scrape_packages(AptMark::new().arg("showauto")).await
51 }
52
53 pub async fn manually_installed() -> anyhow::Result<Vec<String>> {
55 scrape_packages(AptMark::new().arg("showmanual")).await
56 }
57
58 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}