// This file is part of Deja-vu. // // Deja-vu is free software: you can redistribute it and/or modify it // under the terms of the GNU General Public License as published by // the Free Software Foundation, either version 3 of the License, or // (at your option) any later version. // // Deja-vu is distributed in the hope that it will be useful, but // WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. // See the GNU General Public License for more details. // // You should have received a copy of the GNU General Public License // along with Deja-vu. If not, see . // use serde::{Deserialize, Serialize}; use std::{ fmt, process::{Command, Stdio}, thread::sleep, time::Duration, }; use tracing::info; use once_cell::sync::Lazy; use regex::Regex; use crate::system::diskpart; #[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)] pub struct Disk { pub conn_type: String, pub description: String, pub id: usize, pub model: String, pub part_type: PartitionTableType, pub parts: Vec, pub parts_description: Vec, pub sector_size: usize, pub serial: String, pub size: u64, // In bytes } #[derive(Clone, Debug, Default, PartialEq, Eq, Serialize, Deserialize)] pub struct Partition { pub id: usize, pub fs_type: String, pub label: String, pub letter: String, pub part_type: String, pub size: u64, // In bytes } #[derive(Clone, Debug, Default, PartialEq, Eq, Serialize, Deserialize)] pub enum PartitionTableType { #[default] Guid, Legacy, } impl Disk { pub fn generate_descriptions(&mut self) { self.description = format!("{self}"); self.parts_description = Vec::new(); for part in &self.parts { self.parts_description.push(format!("{part}")); } } pub fn get_part_letter(&self, part_index: usize) -> String { // Used to get Boot and OS letters if let Some(part) = self.parts.get(part_index) { part.letter.clone() } else { String::new() } } pub fn get_parts(&self) -> Vec { self.parts.clone() } pub fn num_parts(&self) -> usize { self.parts.len() } } impl fmt::Display for Disk { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { write!( f, "Disk {:<3} {:>11} {:<4} {:<4} {} ({})", self.id, bytes_to_string(self.size), self.conn_type, match self.part_type { PartitionTableType::Guid => "GPT", PartitionTableType::Legacy => "MBR", }, self.model, self.serial, ) } } impl fmt::Display for Partition { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { let mut s: String; let fs = if self.fs_type.is_empty() { String::from("(?)") } else { format!("({})", self.fs_type) }; s = format!( "{:<8} {:>11} {:<7}", self.id, bytes_to_string(self.size), fs ); if !self.label.is_empty() { s = format!("{s} \"{}\"", &self.label); } write!(f, "{s}") } } impl fmt::Display for PartitionTableType { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { match &self { PartitionTableType::Guid => write!(f, "GPT / UEFI"), PartitionTableType::Legacy => write!(f, "MBR / Legacy"), } } } pub fn get_disks() -> Vec { let disks: Vec; if cfg!(windows) { info!("Get disks via Diskpart"); disks = diskpart::get_disks(); } else { info!("Get (fake) disks"); disks = get_fake_disks(); sleep(Duration::from_millis(250)); } disks } #[allow(clippy::too_many_lines)] #[must_use] pub fn get_fake_disks() -> Vec { let mut disks = vec![ Disk { conn_type: "SATA".to_string(), id: 0, model: "Samsung Evo 870".to_string(), part_type: PartitionTableType::Legacy, parts: vec![ Partition { id: 1, fs_type: String::from("NTFS"), label: String::from("System Reserved"), part_type: String::from("7"), size: 104_857_600, ..Default::default() }, Partition { id: 2, part_type: String::from("5"), size: 267_806_310_400, ..Default::default() }, Partition { id: 5, fs_type: String::from("NTFS"), label: String::from("Win7"), part_type: String::from("7"), size: 267_701_452_800, ..Default::default() }, Partition { id: 6, fs_type: String::from("NTFS"), label: String::from("Tools"), part_type: String::from("7"), size: 524_288_000, ..Default::default() }, ], serial: "MDZ1243".to_string(), size: 268_435_456_000, ..Default::default() }, Disk { conn_type: "SATA".to_string(), id: 1, model: "ADATA Garbage".to_string(), part_type: PartitionTableType::Legacy, parts: vec![Partition { id: 1, fs_type: String::from("NTFS"), label: String::from("Scratch"), part_type: String::from("7"), size: 249_998_951_424, ..Default::default() }], serial: "000010000".to_string(), size: 250_000_000_000, ..Default::default() }, Disk { conn_type: "NVMe".to_string(), id: 2, model: "Crucial P3 Plus".to_string(), part_type: PartitionTableType::Guid, parts: vec![ Partition { id: 1, fs_type: String::from("FAT32"), label: String::from("ESP"), letter: String::from("Q"), part_type: String::from("EFI"), size: 272_629_760, }, Partition { id: 2, part_type: String::from("MSR"), size: 16_777_216, ..Default::default() }, Partition { id: 4, fs_type: String::from("NTFS"), label: String::from("Win10"), letter: String::from("V"), part_type: String::from("MS Basic Data"), size: 824_340_119_552, }, ], serial: "80085".to_string(), size: 268_435_456_000, ..Default::default() }, Disk { conn_type: "IDE".to_string(), id: 3, model: "Fireball".to_string(), part_type: PartitionTableType::Guid, parts: vec![ Partition { id: 1, fs_type: String::from("FAT32"), label: String::from("EFI Boot"), part_type: String::from("EFI"), size: 209_715_200, ..Default::default() }, Partition { id: 2, part_type: String::from("{48465300-0000-11AA-AA11-00306543ECAC}"), size: 171_586_879_488, ..Default::default() }, ], serial: "0xfff".to_string(), size: 171_798_691_840, ..Default::default() }, Disk { conn_type: "MISC".to_string(), id: 4, part_type: PartitionTableType::Legacy, model: "Iomega".to_string(), serial: "000".to_string(), size: 14, ..Default::default() }, Disk { id: 5, ..Default::default() }, Disk::default(), ]; for disk in &mut disks { disk.generate_descriptions(); } disks } #[must_use] pub fn get_disk_serial_number(id: usize) -> String { let mut serial = String::new(); if cfg!(windows) { let output = Command::new("wmic") .args([ "diskdrive", "where", format!("index='{id}'").as_str(), "get", "serialNumber", "/value", ]) .stdout(Stdio::piped()) .output(); if let Ok(result) = output { let s = String::from_utf8_lossy(&result.stdout).trim().to_string(); if s.len() >= 15 { serial = String::from(&s[14..]); } } } serial } pub fn refresh_disk_info(disk: &mut Disk) -> Disk { let mut new_disk: Disk; if cfg!(windows) { info!("Refresh disk via Diskpart"); new_disk = diskpart::refresh_disk_info(disk); } else { info!("Refresh fake disk"); new_disk = disk.clone(); new_disk.parts = refresh_fake_disk_info(); new_disk.generate_descriptions(); } new_disk } fn refresh_fake_disk_info() -> Vec { vec![ Partition { id: 1, fs_type: String::from("FAT32"), label: String::from("New ESP Volume"), letter: String::from("S"), part_type: String::from("EFI"), size: 272_629_760, }, Partition { id: 2, label: String::from("New MSR Partition"), part_type: String::from("MSR"), size: 16_777_216, ..Default::default() }, Partition { id: 4, fs_type: String::from("NTFS"), label: String::from("Cloned OS Volume"), letter: String::from("W"), part_type: String::from("MS Basic Data"), size: 824_340_119_552, }, ] } /// Misc /// /// Clippy exception is fine because this supports sizes up to 2 EiB #[allow(clippy::cast_precision_loss)] #[must_use] pub fn bytes_to_string(size: u64) -> String { let units = "KMGTPEZY".chars(); let scale = 1024.0; let mut size = size as f64; let mut suffix: Option = None; for u in units { if size < scale { break; } size /= scale; suffix = Some(u); } if let Some(s) = suffix { format!("{size:4.2} {s}iB") } else { format!("{size:4.2} B") } } /// # Panics /// /// Will panic if s is not simliar to 32B, 64MB, etc... pub fn string_to_bytes(s: &str) -> u64 { static RE: Lazy = Lazy::new(|| Regex::new(r"(\d+)\s+(\w+)B").unwrap()); let base: u64 = 1024; let mut size: u64 = 0; for (_, [size_str, suffix]) in RE.captures_iter(s).map(|c| c.extract()) { let x: u64 = size_str.parse().unwrap(); size += x; match suffix { "K" => size *= base, "M" => size *= base.pow(2), "G" => size *= base.pow(3), "T" => size *= base.pow(4), "P" => size *= base.pow(5), "E" => size *= base.pow(6), "Z" => size *= base.pow(7), "Y" => size *= base.pow(8), _ => (), } } size }