i3status_rs/blocks/packages/
apt.rs

1use std::env;
2use std::process::Stdio;
3
4use tokio::fs::{File, create_dir_all};
5use tokio::process::Command;
6
7use super::*;
8
9#[derive(Default)]
10pub struct Apt {
11    pub(super) config_file: String,
12    pub(super) ignore_phased_updates: bool,
13}
14
15impl Apt {
16    pub async fn new(ignore_phased_updates: bool) -> Result<Self> {
17        let mut apt = Apt {
18            config_file: String::new(),
19            ignore_phased_updates,
20        };
21
22        apt.setup().await?;
23
24        Ok(apt)
25    }
26
27    async fn is_phased_update(&self, package_line: &str) -> Result<bool> {
28        let package_name_regex = regex!(r#"(.*)/.*"#);
29        let package_name = &package_name_regex
30            .captures(package_line)
31            .error("Couldn't find package name")?[1];
32
33        let output = String::from_utf8(
34            Command::new("apt-cache")
35                .args(["-c", &self.config_file, "policy", package_name])
36                .output()
37                .await
38                .error("Problem running apt-cache command")?
39                .stdout,
40        )
41        .error("Problem capturing apt-cache command output")?;
42
43        let phased_regex = regex!(r".*\(phased (\d+)%\).*");
44        Ok(match phased_regex.captures(&output) {
45            Some(matches) => &matches[1] != "100",
46            None => false,
47        })
48    }
49
50    async fn setup(&mut self) -> Result<()> {
51        let mut cache_dir = env::temp_dir();
52        cache_dir.push("i3rs-apt");
53        if !cache_dir.exists() {
54            create_dir_all(&cache_dir)
55                .await
56                .error("Failed to create temp dir")?;
57        }
58
59        let apt_config = format!(
60            "Dir::State \"{}\";\n
61         Dir::State::lists \"lists\";\n
62         Dir::Cache \"{}\";\n
63         Dir::Cache::srcpkgcache \"srcpkgcache.bin\";\n
64         Dir::Cache::pkgcache \"pkgcache.bin\";",
65            cache_dir.display(),
66            cache_dir.display(),
67        );
68
69        let mut config_file = cache_dir;
70        config_file.push("apt.conf");
71        let config_file = config_file.to_str().unwrap();
72
73        self.config_file = config_file.to_string();
74
75        let mut file = File::create(&config_file)
76            .await
77            .error("Failed to create config file")?;
78        file.write_all(apt_config.as_bytes())
79            .await
80            .error("Failed to write to config file")?;
81
82        Ok(())
83    }
84}
85
86#[async_trait]
87impl Backend for Apt {
88    fn name(&self) -> Cow<'static, str> {
89        "apt".into()
90    }
91
92    async fn get_updates_list(&self) -> Result<Vec<String>> {
93        Command::new("apt")
94            .env("APT_CONFIG", &self.config_file)
95            .args(["update"])
96            .stdout(Stdio::null())
97            .stdin(Stdio::null())
98            .spawn()
99            .error("Failed to run `apt update`")?
100            .wait()
101            .await
102            .error("Failed to run `apt update`")?;
103        let stdout = Command::new("apt")
104            .env("LANG", "C")
105            .env("APT_CONFIG", &self.config_file)
106            .args(["list", "--upgradable"])
107            .output()
108            .await
109            .error("Problem running apt command")?
110            .stdout;
111
112        let updates = String::from_utf8(stdout).error("apt produced non-UTF8 output")?;
113        let mut updates_list: Vec<String> = Vec::new();
114
115        for update in updates.lines().filter(|line| line.contains("[upgradable")) {
116            if !self.ignore_phased_updates || !self.is_phased_update(update).await? {
117                updates_list.push(update.to_string());
118            }
119        }
120
121        Ok(updates_list)
122    }
123}