1use crate::domain::{DriveInfo, ImageInfo};
7
8const UNKNOWN_SIZE: f64 = 0.0;
10
11pub const LARGE_DRIVE_SIZE: f64 = 128.0;
13
14#[derive(Debug, Clone, Copy, PartialEq, Eq)]
16pub enum CompatibilityStatusType {
17 Warning,
19 Error,
21}
22
23#[derive(Debug, Clone)]
25pub struct CompatibilityStatus {
26 pub status_type: CompatibilityStatusType,
28 pub message: String,
30}
31
32impl CompatibilityStatus {
33 pub fn new(status_type: CompatibilityStatusType, message: String) -> Self {
35 Self {
36 status_type,
37 message,
38 }
39 }
40
41 pub fn error(message: String) -> Self {
43 Self::new(CompatibilityStatusType::Error, message)
44 }
45
46 pub fn warning(message: String) -> Self {
48 Self::new(CompatibilityStatusType::Warning, message)
49 }
50}
51
52pub fn is_system_drive(drive: &DriveInfo) -> bool {
57 drive.is_system
58}
59
60pub fn is_source_drive(drive: &DriveInfo, image: Option<&ImageInfo>) -> bool {
64 if let Some(img) = image {
65 let image_path = img.path.to_string_lossy();
67
68 if !drive.mount_point.is_empty()
70 && drive.mount_point != drive.device_path
71 && image_path.starts_with(&drive.mount_point)
72 {
73 return true;
74 }
75
76 if image_path.starts_with(&drive.device_path) {
78 return true;
79 }
80 }
81 false
82}
83
84pub fn is_drive_large_enough(drive: &DriveInfo, image: Option<&ImageInfo>) -> bool {
89 let drive_size_gb = if drive.size_gb > 0.0 {
90 drive.size_gb
91 } else {
92 UNKNOWN_SIZE
93 };
94
95 if let Some(img) = image {
96 let image_size_gb = img.size_mb / 1024.0;
97
98 if drive_size_gb <= 0.0 {
100 return false;
101 }
102
103 return drive_size_gb >= image_size_gb;
105 }
106
107 true
109}
110
111pub fn is_drive_size_recommended(_drive: &DriveInfo, _image: Option<&ImageInfo>) -> bool {
116 true
119}
120
121pub fn is_drive_size_large(drive: &DriveInfo) -> bool {
126 drive.size_gb > LARGE_DRIVE_SIZE
127}
128
129pub fn is_drive_valid(drive: &DriveInfo, image: Option<&ImageInfo>) -> bool {
134 !drive.disabled && is_drive_large_enough(drive, image) && !is_source_drive(drive, image)
135}
136
137pub fn get_drive_image_compatibility_statuses(
142 drive: &DriveInfo,
143 image: Option<&ImageInfo>,
144) -> Vec<CompatibilityStatus> {
145 let mut statuses = Vec::new();
146
147 if drive.is_read_only {
149 statuses.push(CompatibilityStatus::error(
150 "This drive is read-only and cannot be flashed.".to_string(),
151 ));
152 }
153
154 if !is_drive_large_enough(drive, image) {
156 if let Some(img) = image {
157 statuses.push(CompatibilityStatus::error(format!(
158 "Drive is too small. Need at least {:.2} GB, but drive is {:.2} GB.",
159 img.size_mb / 1024.0,
160 drive.size_gb
161 )));
162 } else {
163 statuses.push(CompatibilityStatus::error(
164 "Drive is too small for the image.".to_string(),
165 ));
166 }
167 } else {
168 if is_system_drive(drive) {
172 statuses.push(CompatibilityStatus::warning(
173 "This is a system drive. Flashing it may damage your operating system.".to_string(),
174 ));
175 } else if is_drive_size_large(drive) {
176 statuses.push(CompatibilityStatus::warning(format!(
178 "This drive is larger than {}GB. Are you sure this is the right drive?",
179 LARGE_DRIVE_SIZE as i32
180 )));
181 }
182
183 if is_source_drive(drive, image) {
185 statuses.push(CompatibilityStatus::error(
186 "This drive contains the source image and cannot be selected.".to_string(),
187 ));
188 }
189
190 if !is_drive_size_recommended(drive, image) {
192 statuses.push(CompatibilityStatus::warning(
193 "Drive size is smaller than recommended for optimal performance.".to_string(),
194 ));
195 }
196 }
197
198 statuses
199}
200
201pub fn get_list_drive_image_compatibility_statuses(
205 drives: &[DriveInfo],
206 image: Option<&ImageInfo>,
207) -> Vec<CompatibilityStatus> {
208 drives
209 .iter()
210 .flat_map(|drive| get_drive_image_compatibility_statuses(drive, image))
211 .collect()
212}
213
214pub fn has_drive_image_compatibility_status(drive: &DriveInfo, image: Option<&ImageInfo>) -> bool {
218 !get_drive_image_compatibility_statuses(drive, image).is_empty()
219}
220
221pub fn mark_invalid_drives(drives: &mut [DriveInfo], image: Option<&ImageInfo>) {
226 for drive in drives.iter_mut() {
227 let statuses = get_drive_image_compatibility_statuses(drive, image);
228
229 drive.disabled = statuses
231 .iter()
232 .any(|s| s.status_type == CompatibilityStatusType::Error);
233 }
234}
235
236#[cfg(test)]
237mod tests {
238 use super::*;
239 use std::path::PathBuf;
240
241 fn create_test_drive(size_gb: f64, is_system: bool, is_read_only: bool) -> DriveInfo {
242 DriveInfo::with_constraints(
243 "Test Drive".to_string(),
244 "/media/test".to_string(),
245 size_gb,
246 "/dev/sdb".to_string(),
247 is_system,
248 is_read_only,
249 )
250 }
251
252 fn create_test_image(size_mb: f64) -> ImageInfo {
253 ImageInfo {
254 path: PathBuf::from("/tmp/test.img"),
255 name: "test.img".to_string(),
256 size_mb,
257 }
258 }
259
260 #[test]
261 fn test_is_drive_large_enough() {
262 let drive = create_test_drive(16.0, false, false);
263 let image = create_test_image(8000.0); assert!(is_drive_large_enough(&drive, Some(&image)));
266 }
267
268 #[test]
269 fn test_is_drive_too_small() {
270 let drive = create_test_drive(4.0, false, false);
271 let image = create_test_image(8000.0); assert!(!is_drive_large_enough(&drive, Some(&image)));
274 }
275
276 #[test]
277 fn test_is_drive_size_large() {
278 let small_drive = create_test_drive(64.0, false, false);
279 let large_drive = create_test_drive(256.0, false, false);
280
281 assert!(!is_drive_size_large(&small_drive));
282 assert!(is_drive_size_large(&large_drive));
283 }
284
285 #[test]
286 fn test_system_drive_warning() {
287 let drive = create_test_drive(32.0, true, false);
288 let image = create_test_image(4000.0); let statuses = get_drive_image_compatibility_statuses(&drive, Some(&image));
291
292 assert!(!statuses.is_empty());
293 assert!(statuses
294 .iter()
295 .any(|s| s.status_type == CompatibilityStatusType::Warning));
296 }
297
298 #[test]
299 fn test_read_only_drive_error() {
300 let drive = create_test_drive(32.0, false, true);
301 let image = create_test_image(4000.0); let statuses = get_drive_image_compatibility_statuses(&drive, Some(&image));
304
305 assert!(!statuses.is_empty());
306 assert!(statuses
307 .iter()
308 .any(|s| s.status_type == CompatibilityStatusType::Error));
309 }
310
311 #[test]
312 fn test_mark_invalid_drives() {
313 let drives = vec![
314 create_test_drive(32.0, false, false), create_test_drive(2.0, false, false), create_test_drive(16.0, false, true), ];
318
319 let image = create_test_image(4000.0); let mut drives_mut = drives;
322 mark_invalid_drives(&mut drives_mut, Some(&image));
323
324 assert!(!drives_mut[0].disabled); assert!(drives_mut[1].disabled); assert!(drives_mut[2].disabled); }
328
329 #[test]
330 fn test_is_drive_valid() {
331 let mut valid_drive = create_test_drive(32.0, false, false);
332 let invalid_drive = create_test_drive(2.0, false, false);
333 let image = create_test_image(4000.0); assert!(is_drive_valid(&valid_drive, Some(&image)));
336 assert!(!is_drive_valid(&invalid_drive, Some(&image)));
337
338 valid_drive.disabled = true;
340 assert!(!is_drive_valid(&valid_drive, Some(&image)));
341 }
342}