i3status_rs/blocks/packages/
pacman.rs1use std::env;
2use std::path::PathBuf;
3use std::process::Stdio;
4
5use tokio::fs::{create_dir_all, symlink};
6use tokio::process::Command;
7
8use super::*;
9use crate::util::has_command;
10
11make_log_macro!(debug, "pacman");
12
13pub static PACMAN_UPDATES_DB: LazyLock<PathBuf> = LazyLock::new(|| {
14 let path = match env::var_os("CHECKUPDATES_DB") {
15 Some(val) => val.into(),
16 None => {
17 let mut path = env::temp_dir();
18 let user = env::var("USER");
19 path.push(format!(
20 "checkup-db-i3statusrs-{}",
21 user.as_deref().unwrap_or("no-user")
22 ));
23 path
24 }
25 };
26 debug!("Using {} as updates DB path", path.display());
27 path
28});
29
30pub static PACMAN_DB: LazyLock<PathBuf> = LazyLock::new(|| {
31 let path = env::var_os("DBPath")
32 .map(Into::into)
33 .unwrap_or_else(|| PathBuf::from("/var/lib/pacman/"));
34 debug!("Using {} as pacman DB path", path.display());
35 path
36});
37
38pub struct Pacman;
39
40pub struct Aur {
41 aur_command: String,
42}
43
44impl Pacman {
45 pub async fn new() -> Result<Self> {
46 check_fakeroot_command_exists().await?;
47
48 Ok(Self)
49 }
50}
51
52impl Aur {
53 pub fn new(aur_command: String) -> Self {
54 Aur { aur_command }
55 }
56}
57
58#[async_trait]
59impl Backend for Pacman {
60 fn name(&self) -> Cow<'static, str> {
61 "pacman".into()
62 }
63
64 async fn get_updates_list(&self) -> Result<Vec<String>> {
65 create_dir_all(&*PACMAN_UPDATES_DB).await.or_error(|| {
67 format!(
68 "Failed to create checkup-db directory at '{}'",
69 PACMAN_UPDATES_DB.display()
70 )
71 })?;
72
73 let local_cache = PACMAN_UPDATES_DB.join("local");
75 if !tokio::fs::try_exists(&local_cache)
76 .await
77 .error("Unable to stat file")?
78 {
79 symlink(PACMAN_DB.join("local"), local_cache)
80 .await
81 .error("Failed to created required symlink")?;
82 }
83
84 let status = Command::new("fakeroot")
86 .env("LC_ALL", "C")
87 .args([
88 "--".as_ref(),
89 "pacman".as_ref(),
90 "-Sy".as_ref(),
91 "--disable-sandbox-filesystem".as_ref(),
92 "--dbpath".as_ref(),
93 PACMAN_UPDATES_DB.as_os_str(),
94 "--logfile".as_ref(),
95 "/dev/null".as_ref(),
96 ])
97 .stdout(Stdio::null())
98 .status()
99 .await
100 .error("Failed to run command")?;
101 if !status.success() {
102 debug!("{}", status);
103 return Err(Error::new("pacman -Sy exited with non zero exit status"));
104 }
105
106 let stdout = Command::new("fakeroot")
107 .env("LC_ALL", "C")
108 .args([
109 "--".as_ref(),
110 "pacman".as_ref(),
111 "-Qu".as_ref(),
112 "--dbpath".as_ref(),
113 PACMAN_UPDATES_DB.as_os_str(),
114 ])
115 .output()
116 .await
117 .error("There was a problem running the pacman commands")?
118 .stdout;
119
120 let updates = String::from_utf8(stdout).error("Pacman produced non-UTF8 output")?;
121
122 let updates = updates
123 .lines()
124 .filter(|line| !line.contains("[ignored]"))
125 .map(|line| line.to_string())
126 .collect();
127
128 Ok(updates)
129 }
130}
131
132#[async_trait]
133impl Backend for Aur {
134 fn name(&self) -> Cow<'static, str> {
135 "aur".into()
136 }
137
138 async fn get_updates_list(&self) -> Result<Vec<String>> {
139 let stdout = Command::new("sh")
140 .args(["-c", &self.aur_command])
141 .output()
142 .await
143 .or_error(|| format!("aur command: {} failed", self.aur_command))?
144 .stdout;
145 let updates = String::from_utf8(stdout)
146 .error("There was a problem while converting the aur command output to a string")?;
147
148 let updates = updates
149 .lines()
150 .filter(|line| !line.contains("[ignored]"))
151 .map(|line| line.to_string())
152 .collect();
153
154 Ok(updates)
155 }
156}
157
158async fn check_fakeroot_command_exists() -> Result<()> {
159 if !has_command("fakeroot").await? {
160 Err(Error::new("fakeroot not found"))
161 } else {
162 Ok(())
163 }
164}