|
| 1 | +use super::mount_point::find_mount_point; |
| 2 | +use std::{ |
| 3 | + fs::canonicalize, |
| 4 | + io, |
| 5 | + path::{Path, PathBuf}, |
| 6 | +}; |
| 7 | +use sysinfo::{Disk, DiskKind}; |
| 8 | + |
| 9 | +/// Mockable APIs to interact with the system. |
| 10 | +pub trait Api { |
| 11 | + type Disk; |
| 12 | + fn get_disk_kind(disk: &Self::Disk) -> DiskKind; |
| 13 | + fn get_mount_point(disk: &Self::Disk) -> &Path; |
| 14 | + fn canonicalize(path: &Path) -> io::Result<PathBuf>; |
| 15 | +} |
| 16 | + |
| 17 | +/// Implementation of [`Api`] that interacts with the real system. |
| 18 | +pub struct RealApi; |
| 19 | +impl Api for RealApi { |
| 20 | + type Disk = Disk; |
| 21 | + |
| 22 | + fn get_disk_kind(disk: &Self::Disk) -> DiskKind { |
| 23 | + disk.kind() |
| 24 | + } |
| 25 | + |
| 26 | + fn get_mount_point(disk: &Self::Disk) -> &Path { |
| 27 | + disk.mount_point() |
| 28 | + } |
| 29 | + |
| 30 | + fn canonicalize(path: &Path) -> io::Result<PathBuf> { |
| 31 | + canonicalize(path) |
| 32 | + } |
| 33 | +} |
| 34 | + |
| 35 | +/// Check if any path is in any HDD. |
| 36 | +pub fn any_path_is_in_hdd<Api: self::Api>(paths: &[PathBuf], disks: &[Api::Disk]) -> bool { |
| 37 | + paths |
| 38 | + .iter() |
| 39 | + .filter_map(|file| Api::canonicalize(file).ok()) |
| 40 | + .any(|path| path_is_in_hdd::<Api>(&path, disks)) |
| 41 | +} |
| 42 | + |
| 43 | +/// Check if path is in any HDD. |
| 44 | +fn path_is_in_hdd<Api: self::Api>(path: &Path, disks: &[Api::Disk]) -> bool { |
| 45 | + let Some(mount_point) = find_mount_point(path, disks.iter().map(Api::get_mount_point)) else { |
| 46 | + return false; |
| 47 | + }; |
| 48 | + disks |
| 49 | + .iter() |
| 50 | + .filter(|disk| Api::get_disk_kind(disk) == DiskKind::HDD) |
| 51 | + .any(|disk| Api::get_mount_point(disk) == mount_point) |
| 52 | +} |
| 53 | + |
| 54 | +#[cfg(test)] |
| 55 | +mod tests { |
| 56 | + use super::{any_path_is_in_hdd, path_is_in_hdd, Api}; |
| 57 | + use pipe_trait::Pipe; |
| 58 | + use pretty_assertions::assert_eq; |
| 59 | + use std::path::{Path, PathBuf}; |
| 60 | + use sysinfo::DiskKind; |
| 61 | + |
| 62 | + /// Fake disk for [`Api`]. |
| 63 | + struct Disk { |
| 64 | + kind: DiskKind, |
| 65 | + mount_point: &'static str, |
| 66 | + } |
| 67 | + |
| 68 | + impl Disk { |
| 69 | + fn new(kind: DiskKind, mount_point: &'static str) -> Self { |
| 70 | + Self { kind, mount_point } |
| 71 | + } |
| 72 | + } |
| 73 | + |
| 74 | + /// Mocked implementation of [`Api`] for testing purposes. |
| 75 | + struct MockedApi; |
| 76 | + impl Api for MockedApi { |
| 77 | + type Disk = Disk; |
| 78 | + |
| 79 | + fn get_disk_kind(disk: &Self::Disk) -> DiskKind { |
| 80 | + disk.kind |
| 81 | + } |
| 82 | + |
| 83 | + fn get_mount_point(disk: &Self::Disk) -> &Path { |
| 84 | + Path::new(disk.mount_point) |
| 85 | + } |
| 86 | + |
| 87 | + fn canonicalize(path: &Path) -> std::io::Result<PathBuf> { |
| 88 | + path.to_path_buf().pipe(Ok) |
| 89 | + } |
| 90 | + } |
| 91 | + |
| 92 | + #[test] |
| 93 | + fn test_any_path_in_hdd() { |
| 94 | + let disks = &[ |
| 95 | + Disk::new(DiskKind::SSD, "/"), |
| 96 | + Disk::new(DiskKind::HDD, "/home"), |
| 97 | + Disk::new(DiskKind::HDD, "/mnt/hdd-data"), |
| 98 | + Disk::new(DiskKind::SSD, "/mnt/ssd-data"), |
| 99 | + Disk::new(DiskKind::HDD, "/mnt/hdd-data/repo"), |
| 100 | + ]; |
| 101 | + |
| 102 | + let cases: &[(&[&str], bool)] = &[ |
| 103 | + (&[], false), |
| 104 | + (&["/"], false), |
| 105 | + (&["/home"], true), |
| 106 | + (&["/mnt"], false), |
| 107 | + (&["/mnt/ssd-data"], false), |
| 108 | + (&["/mnt/hdd-data"], true), |
| 109 | + (&["/mnt/hdd-data/repo"], true), |
| 110 | + (&["/etc/fstab"], false), |
| 111 | + (&["/home/usr/file"], true), |
| 112 | + (&["/home/data/repo/test"], true), |
| 113 | + (&["/usr/share"], false), |
| 114 | + (&["/mnt/ssd-data/test"], false), |
| 115 | + (&["/etc/fstab", "/home/user/file"], true), |
| 116 | + (&["/mnt/hdd-data/file", "/mnt/hdd-data/repo/test"], true), |
| 117 | + (&["/usr/share", "/mnt/ssd-data/test"], false), |
| 118 | + ( |
| 119 | + &["/etc/fstab", "/home/user", "/mnt/hdd-data", "/usr/share"], |
| 120 | + true, |
| 121 | + ), |
| 122 | + ]; |
| 123 | + |
| 124 | + for (paths, in_hdd) in cases { |
| 125 | + let paths: Vec<_> = paths.iter().map(PathBuf::from).collect(); |
| 126 | + println!("CASE: {paths:?} → {in_hdd:?}"); |
| 127 | + assert_eq!(any_path_is_in_hdd::<MockedApi>(&paths, disks), *in_hdd); |
| 128 | + } |
| 129 | + } |
| 130 | + |
| 131 | + #[test] |
| 132 | + fn test_path_in_hdd() { |
| 133 | + let disks = &[ |
| 134 | + Disk::new(DiskKind::SSD, "/"), |
| 135 | + Disk::new(DiskKind::HDD, "/home"), |
| 136 | + Disk::new(DiskKind::HDD, "/mnt/hdd-data"), |
| 137 | + Disk::new(DiskKind::SSD, "/mnt/ssd-data"), |
| 138 | + Disk::new(DiskKind::HDD, "/mnt/hdd-data/repo"), |
| 139 | + ]; |
| 140 | + |
| 141 | + for (path, in_hdd) in [ |
| 142 | + ("/etc/fstab", false), |
| 143 | + ("/mnt/", false), |
| 144 | + ("/mnt/hdd-data/repo/test", true), |
| 145 | + ("/mnt/hdd-data/test/test", true), |
| 146 | + ("/mnt/ssd-data/test/test", false), |
| 147 | + ] { |
| 148 | + println!("CASE: {path} → {in_hdd:?}"); |
| 149 | + assert_eq!(path_is_in_hdd::<MockedApi>(Path::new(path), disks), in_hdd); |
| 150 | + } |
| 151 | + } |
| 152 | +} |
0 commit comments