apt_cmd/
upgrade.rs

1// Copyright 2021-2022 System76 <info@system76.com>
2// SPDX-License-Identifier: MPL-2.0
3
4use std::collections::HashMap;
5use std::fmt::{self, Display, Formatter};
6use std::str::FromStr;
7
8#[derive(Clone, Debug, Eq, PartialEq)]
9pub enum AptUpgradeEvent {
10    Processing {
11        package: Box<str>,
12    },
13    Progress {
14        percent: u8,
15    },
16    SettingUp {
17        package: Box<str>,
18    },
19    Unpacking {
20        package: Box<str>,
21        version: Box<str>,
22        over: Box<str>,
23    },
24    WaitingOnLock,
25}
26
27impl AptUpgradeEvent {
28    pub fn into_dbus_map(self) -> HashMap<&'static str, String> {
29        let mut map = HashMap::new();
30
31        match self {
32            AptUpgradeEvent::Processing { package } => {
33                map.insert("processing_package", package.into());
34            }
35            AptUpgradeEvent::Progress { percent } => {
36                map.insert("percent", percent.to_string());
37            }
38            AptUpgradeEvent::SettingUp { package } => {
39                map.insert("setting_up", package.into());
40            }
41            AptUpgradeEvent::Unpacking {
42                package,
43                version,
44                over,
45            } => {
46                map.insert("unpacking", package.into());
47                map.insert("version", version.into());
48                map.insert("over", over.into());
49            }
50            AptUpgradeEvent::WaitingOnLock => {
51                map.insert("waiting", "".into());
52            }
53        }
54
55        map
56    }
57
58    #[allow(clippy::result_unit_err)]
59    pub fn from_dbus_map<K: AsRef<str>, V: AsRef<str> + Into<Box<str>>>(
60        mut map: impl Iterator<Item = (K, V)>,
61    ) -> Result<Self, ()> {
62        use self::AptUpgradeEvent::*;
63
64        let (key, value) = match map.next() {
65            Some(value) => value,
66            None => return Err(()),
67        };
68
69        let event = match key.as_ref() {
70            "waiting" => WaitingOnLock,
71            "processing_package" => Processing {
72                package: value.into(),
73            },
74            "percent" => {
75                let percent = value.as_ref().parse::<u8>().map_err(|_| ())?;
76                Progress { percent }
77            }
78            "setting_up" => SettingUp {
79                package: value.into(),
80            },
81            key => match (map.next(), map.next()) {
82                (Some((key1, value1)), Some((key2, value2))) => {
83                    let over = &mut None;
84                    let version = &mut None;
85                    let package = &mut None;
86
87                    match_field(over, version, package, key, value.into())?;
88                    match_field(over, version, package, key1.as_ref(), value1.into())?;
89                    match_field(over, version, package, key2.as_ref(), value2.into())?;
90
91                    match (over.take(), version.take(), package.take()) {
92                        (Some(over), Some(version), Some(package)) => Unpacking {
93                            package,
94                            version,
95                            over,
96                        },
97                        _ => return Err(()),
98                    }
99                }
100                _ => return Err(()),
101            },
102        };
103
104        Ok(event)
105    }
106}
107
108fn match_field<'a>(
109    over: &'a mut Option<Box<str>>,
110    version: &'a mut Option<Box<str>>,
111    package: &'a mut Option<Box<str>>,
112    key: &str,
113    value: Box<str>,
114) -> Result<(), ()> {
115    let field = match key {
116        "over" => over,
117        "version" => version,
118        "unpacking" => package,
119        _ => return Err(()),
120    };
121
122    *field = Some(value);
123
124    Ok(())
125}
126
127impl Display for AptUpgradeEvent {
128    fn fmt(&self, fmt: &mut Formatter) -> fmt::Result {
129        match self {
130            AptUpgradeEvent::Processing { package } => {
131                write!(fmt, "processing triggers for {}", package)
132            }
133            AptUpgradeEvent::Progress { percent } => write!(fmt, "progress: [{:>3}%]", percent),
134            AptUpgradeEvent::SettingUp { package } => write!(fmt, "setting up {}", package),
135            AptUpgradeEvent::Unpacking {
136                package,
137                version,
138                over,
139            } => write!(fmt, "unpacking {} ({}) over ({})", package, version, over),
140            AptUpgradeEvent::WaitingOnLock => {
141                write!(fmt, "waiting on a process holding the apt lock files")
142            }
143        }
144    }
145}
146
147// TODO: Unit test this
148impl FromStr for AptUpgradeEvent {
149    type Err = ();
150
151    fn from_str(input: &str) -> Result<Self, Self::Err> {
152        if let Some(mut progress) = input.strip_prefix("Progress: [") {
153            progress = progress.trim();
154            if let Some(pos) = progress.find('%') {
155                if let Ok(percent) = progress[..pos].parse::<u8>() {
156                    return Ok(AptUpgradeEvent::Progress { percent });
157                }
158            }
159        } else if let Some(input) = input.strip_prefix("Processing triggers for ") {
160            if let Some(package) = input.split_whitespace().next() {
161                return Ok(AptUpgradeEvent::Processing {
162                    package: package.into(),
163                });
164            }
165        } else if let Some(input) = input.strip_prefix("Setting up ") {
166            if let Some(package) = input.split_whitespace().next() {
167                return Ok(AptUpgradeEvent::SettingUp {
168                    package: package.into(),
169                });
170            }
171        } else if let Some(input) = input.strip_prefix("Unpacking ") {
172            let mut fields = input.split_whitespace();
173            if let (Some(package), Some(version), Some(over)) =
174                (fields.next(), fields.next(), fields.nth(1))
175            {
176                if version.len() > 2 && over.len() > 2 {
177                    return Ok(AptUpgradeEvent::Unpacking {
178                        package: package.into(),
179                        version: version[1..version.len() - 1].into(),
180                        over: over[1..over.len() - 1].into(),
181                    });
182                }
183            }
184        }
185
186        Err(())
187    }
188}
189
190#[cfg(test)]
191mod tests {
192    use super::*;
193
194    #[test]
195    fn apt_upgrade_event_progress() {
196        assert_eq!(
197            AptUpgradeEvent::Progress { percent: 1 },
198            "Progress: [  1%]".parse::<AptUpgradeEvent>().unwrap()
199        );
200
201        assert_eq!(
202            AptUpgradeEvent::Progress { percent: 25 },
203            "Progress: [ 25%] ".parse::<AptUpgradeEvent>().unwrap()
204        );
205
206        assert_eq!(
207            AptUpgradeEvent::Progress { percent: 100 },
208            "Progress: [100%]".parse::<AptUpgradeEvent>().unwrap()
209        );
210    }
211}