i3status_rs/blocks/packages/
apt.rs1use 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}