i3status_rs/blocks/packages/
pacman.rs

1use 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 the determined `checkup-db` path recursively
66        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        // Create symlink to local cache in `checkup-db` if required
74        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        // Update database
85        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}