404 lines
12 KiB
Rust
404 lines
12 KiB
Rust
// 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 <https://www.gnu.org/licenses/>.
|
|
//
|
|
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<Partition>,
|
|
pub parts_description: Vec<String>,
|
|
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<Partition> {
|
|
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<Disk> {
|
|
let disks: Vec<Disk>;
|
|
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<Disk> {
|
|
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<Partition> {
|
|
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<char> = 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<Regex> = 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
|
|
}
|