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 !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}