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 !tokio::fs::try_exists(&cache_dir)
54            .await
55            .error("Unable to stat file")?
56        {
57            create_dir_all(&cache_dir)
58                .await
59                .error("Failed to create temp dir")?;
60        }
61
62        let apt_config = format!(
63            "Dir::State \"{}\";\n
64         Dir::State::lists \"lists\";\n
65         Dir::Cache \"{}\";\n
66         Dir::Cache::srcpkgcache \"srcpkgcache.bin\";\n
67         Dir::Cache::pkgcache \"pkgcache.bin\";",
68            cache_dir.display(),
69            cache_dir.display(),
70        );
71
72        let mut config_file = cache_dir;
73        config_file.push("apt.conf");
74        let config_file = config_file.to_str().unwrap();
75
76        self.config_file = config_file.to_string();
77
78        let mut file = File::create(&config_file)
79            .await
80            .error("Failed to create config file")?;
81        file.write_all(apt_config.as_bytes())
82            .await
83            .error("Failed to write to config file")?;
84
85        Ok(())
86    }
87}
88
89#[async_trait]
90impl Backend for Apt {
91    fn name(&self) -> Cow<'static, str> {
92        "apt".into()
93    }
94
95    async fn get_updates_list(&self) -> Result<Vec<String>> {
96        Command::new("apt")
97            .env("APT_CONFIG", &self.config_file)
98            .args(["update"])
99            .stdout(Stdio::null())
100            .stdin(Stdio::null())
101            .spawn()
102            .error("Failed to run `apt update`")?
103            .wait()
104            .await
105            .error("Failed to run `apt update`")?;
106        let stdout = Command::new("apt")
107            .env("LANG", "C")
108            .env("APT_CONFIG", &self.config_file)
109            .args(["list", "--upgradable"])
110            .output()
111            .await
112            .error("Problem running apt command")?
113            .stdout;
114
115        let updates = String::from_utf8(stdout).error("apt produced non-UTF8 output")?;
116        let mut updates_list: Vec<String> = Vec::new();
117
118        for update in updates.lines().filter(|line| line.contains("[upgradable")) {
119            if !self.ignore_phased_updates || !self.is_phased_update(update).await? {
120                updates_list.push(update.to_string());
121            }
122        }
123
124        Ok(updates_list)
125    }
126}