1use 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
147impl 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}