1#[derive(Debug, server::Message, rkyv::Archive, rkyv::Serialize, rkyv::Deserialize)]
5#[event(ProgressUpdate)]
6pub struct SubscribeUpdateProgress;
7
8#[derive(Debug, server::Message, rkyv::Archive, rkyv::Serialize, rkyv::Deserialize)]
9pub struct StartUpdate {
10 pub release_paths: Vec<String>,
11}
12
13#[derive(Debug, server::Message, rkyv::Archive, rkyv::Serialize, rkyv::Deserialize)]
14pub struct ContinueUpdate;
15
16#[derive(Debug, server::Message, rkyv::Archive, rkyv::Serialize, rkyv::Deserialize)]
17#[response(Result<String, crate::Error>)]
18pub struct FirmwareVersion;
19
20#[derive(Debug, server::Message, rkyv::Archive, rkyv::Serialize, rkyv::Deserialize)]
21pub struct ApplyDownloadedUpdate;
22
23#[derive(Debug, server::Message)]
24#[response(bool)]
25pub struct GetUpdateApplied;
26
27#[derive(Debug, server::Message)]
28pub struct ClearUpdateApplied;
29
30#[derive(Debug, server::Message, rkyv::Archive, rkyv::Serialize, rkyv::Deserialize)]
31#[response(UpdateStatus)]
32pub struct GetUpdateStatus;
33
34#[derive(Clone, Debug, Default, PartialEq, Eq, rkyv::Archive, rkyv::Serialize, rkyv::Deserialize)]
36pub struct UpdateStatus {
37 pub downloaded_update: bool,
39 pub needs_continue: bool,
41 pub installing: bool,
43 pub sufficient_battery: bool,
45}
46
47#[derive(Clone, Debug, rkyv::Archive, rkyv::Serialize, rkyv::Deserialize)]
48pub enum ProgressUpdate {
49 DownloadProgress(DownloadProgress),
50 DownloadComplete,
52 InstallProgress(InstallProgress),
54 Rebooting,
56 Done,
58 InstallError(crate::Error),
59 DownloadError(crate::DownloadError),
60}
61
62#[derive(Debug, Clone, Copy, rkyv::Archive, rkyv::Serialize, rkyv::Deserialize)]
63pub struct DownloadProgress {
64 pub patches_total: u32,
65 pub patches_complete: u32,
66 pub chunks_received: u32,
67 pub total_chunks: u32,
68}
69
70impl DownloadProgress {
71 pub fn is_start(&self) -> bool { self.patches_complete == 0 && self.chunks_received == 0 }
72
73 pub fn completion_percentage(&self) -> u32 {
74 if self.total_chunks == 0 {
75 return 0;
76 }
77
78 self.chunks_received.saturating_mul(100).saturating_div(self.total_chunks).min(100)
79 }
80}
81
82#[derive(Clone, Debug, rkyv::Archive, rkyv::Serialize, rkyv::Deserialize)]
83pub struct InstallProgress {
84 pub patches: Vec<PatchProgress>,
85 pub firmware_copy: FirmwareCopyProgress,
86}
87
88#[derive(Clone, Debug, rkyv::Archive, rkyv::Serialize, rkyv::Deserialize)]
89pub struct FirmwareCopyProgress {
90 pub copied_bytes: u64,
91 pub total_bytes: u64,
92}
93
94#[derive(Clone, Debug, rkyv::Archive, rkyv::Serialize, rkyv::Deserialize)]
96pub struct PatchProgress {
97 pub file_size: u64,
99 pub total_actions: u32,
100 pub completed_actions: u32,
101 pub requires_reboot: bool,
103}
104
105impl InstallProgress {
106 const COPY_BYTES_PER_SECOND: f64 = 7.0 * 1024.0 * 1024.0;
107 const PATCH_OVERHEAD_SECONDS: f64 = 2.0;
108 const SECONDS_PER_ACTION: f64 = 1.5;
109 const SECONDS_PER_MB: f64 = 5.0;
110
111 pub fn action_completed(&mut self) {
112 if let Some(patch) = self.patches.iter_mut().find(|p| p.completed_actions < p.total_actions) {
113 patch.completed_actions += 1;
114 }
115 }
116
117 pub fn set_firmware_copy(&mut self, progress: FirmwareCopyProgress) { self.firmware_copy = progress; }
118
119 pub fn estimate_time_remaining_secs(&self) -> u64 {
120 let total = self.time_total_secs();
121 let completed = self.time_completed_secs();
122 total.saturating_sub(completed)
123 }
124
125 pub fn completion_percentage(&self) -> u32 {
126 let total = self.time_total_secs();
127 if total == 0 {
128 return 100;
129 }
130
131 let completed = self.time_completed_secs();
132 ((completed as f64 / total as f64) * 100.0).min(99.0) as u32
133 }
134
135 pub fn time_total_secs(&self) -> u64 {
136 let mut total = 0.0;
137
138 let copy_time = self.firmware_copy.total_bytes as f64 / Self::COPY_BYTES_PER_SECOND;
139 total += copy_time;
140
141 for (idx, patch) in self.patches.iter().enumerate() {
142 total += Self::PATCH_OVERHEAD_SECONDS;
143 let mb = patch.file_size as f64 / (1024.0 * 1024.0);
144 total += mb * Self::SECONDS_PER_MB;
145 total += patch.total_actions as f64 * Self::SECONDS_PER_ACTION;
146
147 if patch.requires_reboot && idx < self.patches.len() - 1 {
148 total += copy_time;
149 }
150 }
151
152 total as u64
153 }
154
155 pub fn time_completed_secs(&self) -> u64 {
156 let mut completed = 0.0;
157
158 let copy_time = self.firmware_copy.copied_bytes as f64 / Self::COPY_BYTES_PER_SECOND;
159 completed += copy_time;
160
161 for (idx, patch) in self.patches.iter().enumerate() {
162 let mb = patch.file_size as f64 / (1024.0 * 1024.0);
163 let file_work = mb * Self::SECONDS_PER_MB;
164 let action_work = patch.total_actions as f64 * Self::SECONDS_PER_ACTION;
165
166 if patch.completed_actions >= patch.total_actions {
167 completed += Self::PATCH_OVERHEAD_SECONDS + file_work + action_work;
168 } else if patch.completed_actions > 0 {
169 completed += Self::PATCH_OVERHEAD_SECONDS + file_work;
170 completed += patch.completed_actions as f64 * Self::SECONDS_PER_ACTION;
171 }
172
173 if patch.requires_reboot && idx < self.patches.len() - 1 {
174 if patch.completed_actions >= patch.total_actions {
175 let reboot_copy_time =
176 self.firmware_copy.total_bytes as f64 / Self::COPY_BYTES_PER_SECOND;
177 completed += reboot_copy_time;
178 }
179 }
180 }
181
182 completed as u64
183 }
184}
185
186#[cfg(test)]
187mod tests {
188 use super::*;
189
190 #[test]
191 fn action_completed() {
192 let mut progress = InstallProgress {
193 patches: vec![PatchProgress {
194 file_size: 1024,
195 total_actions: 5,
196 completed_actions: 2,
197 requires_reboot: false,
198 }],
199 firmware_copy: FirmwareCopyProgress { copied_bytes: 0, total_bytes: 0 },
200 };
201
202 progress.action_completed();
203 assert_eq!(progress.patches[0].completed_actions, 3);
204
205 progress.action_completed();
206 assert_eq!(progress.patches[0].completed_actions, 4);
207 }
208
209 #[test]
210 fn estimate_time_remaining() {
211 let progress = InstallProgress {
212 patches: vec![
213 PatchProgress {
214 file_size: 5_242_880,
215 total_actions: 10,
216 completed_actions: 5,
217 requires_reboot: false,
218 },
219 PatchProgress {
220 file_size: 1_048_576,
221 total_actions: 5,
222 completed_actions: 0,
223 requires_reboot: true,
224 },
225 PatchProgress {
226 file_size: 10_485_760,
227 total_actions: 8,
228 completed_actions: 0,
229 requires_reboot: false,
230 },
231 ],
232 firmware_copy: FirmwareCopyProgress { copied_bytes: 10_485_760, total_bytes: 10_485_760 },
233 };
234
235 let completed = progress.time_completed_secs();
236 let total = progress.time_total_secs();
237 let remaining = progress.estimate_time_remaining_secs();
238
239 assert!(total > 0);
240 assert!(completed > 0);
241 assert_eq!(remaining, total.saturating_sub(completed));
242 }
243
244 #[test]
245 fn completion_percentage() {
246 let progress = InstallProgress {
247 patches: vec![PatchProgress {
248 file_size: 1_048_576,
249 total_actions: 10,
250 completed_actions: 5,
251 requires_reboot: false,
252 }],
253 firmware_copy: FirmwareCopyProgress { copied_bytes: 10_485_760, total_bytes: 10_485_760 },
254 };
255
256 let percentage = progress.completion_percentage();
257 assert!(percentage > 0 && percentage < 100);
258 }
259
260 #[test]
261 fn download_completion_percentage() {
262 let progress = DownloadProgress {
263 patches_total: 2,
264 patches_complete: 0,
265 chunks_received: 34,
266 total_chunks: 100,
267 };
268
269 assert_eq!(progress.completion_percentage(), 34);
270 }
271
272 #[test]
273 fn download_completion_percentage_handles_zero_total() {
274 let progress =
275 DownloadProgress { patches_total: 2, patches_complete: 0, chunks_received: 0, total_chunks: 0 };
276
277 assert_eq!(progress.completion_percentage(), 0);
278 }
279}