leenfetch_core/modules/linux/info/
disk.rs1use std::fs;
2
3use crate::modules::{
4 enums::{DiskDisplay, DiskSubtitle},
5 utils::get_bar,
6};
7
8pub fn get_disks(
9 subtitle_mode: DiskSubtitle,
10 display_mode: DiskDisplay,
11 paths: Option<Vec<&str>>,
12) -> Option<Vec<(String, String)>> {
13 let mount_points = if let Some(ref user_paths) = paths {
15 user_paths.iter().map(|s| s.to_string()).collect()
16 } else {
17 get_default_mount_points()
19 };
20
21 let mut results = Vec::new();
22
23 for mount_point in mount_points {
24 if let Some(disk_info) = get_disk_info_for_path(&mount_point, subtitle_mode, &display_mode)
25 {
26 results.push(disk_info);
27 }
28 }
29
30 if results.is_empty() {
31 return None;
32 }
33
34 Some(results)
35}
36
37fn get_default_mount_points() -> Vec<String> {
38 let mut points = vec!["/".to_string()];
39 let mut seen_devices: std::collections::HashSet<String> = std::collections::HashSet::new();
40
41 if let Ok(content) = fs::read_to_string("/proc/mounts") {
43 let common_prefixes = ["/home", "/boot", "/var", "/usr", "/opt", "/data"];
44
45 for line in content.lines() {
46 let parts: Vec<&str> = line.split_whitespace().collect();
47 if parts.len() >= 2 {
48 let mount = parts[0]; let mount_point = parts[1];
50
51 if seen_devices.contains(mount) {
53 continue;
54 }
55
56 if !mount_point.starts_with("/dev")
58 && !mount_point.starts_with("/sys")
59 && !mount_point.starts_with("/proc")
60 && !mount_point.starts_with("/run")
61 && !mount_point.starts_with("/snap")
62 {
63 for prefix in &common_prefixes {
65 if mount_point.starts_with(prefix) && mount_point != "/" {
66 seen_devices.insert(mount.to_string());
67 points.push(mount_point.to_string());
68 break;
69 }
70 }
71 }
72 }
73 }
74 }
75
76 points.truncate(5);
78 points
79}
80
81fn get_disk_info_for_path(
82 path: &str,
83 subtitle_mode: DiskSubtitle,
84 display_mode: &DiskDisplay,
85) -> Option<(String, String)> {
86 let path_cstr = std::ffi::CString::new(path).ok()?;
87
88 let mut statfs: libc::statvfs = unsafe { std::mem::zeroed() };
90
91 if unsafe { libc::statvfs(path_cstr.as_ptr(), &mut statfs) } != 0 {
92 return None;
93 }
94
95 let total = statfs.f_blocks as u64 * statfs.f_frsize as u64;
97 let available = statfs.f_bavail as u64 * statfs.f_frsize as u64;
98 let used = total.saturating_sub(available);
99
100 if total == 0 {
101 return None;
102 }
103
104 let percent = ((used as f64 / total as f64) * 100.0).round().clamp(0.0, 100.0) as u8;
105
106 let total_h = format_size(total);
108 let used_h = format_size(used);
109
110 let usage_display = format!("{} / {}", used_h, total_h);
111 let perc_val = percent.min(100);
112
113 let final_str = match display_mode {
114 DiskDisplay::Info => usage_display,
115 DiskDisplay::Percentage => format!("{}% {}", percent, get_bar(perc_val)),
116 DiskDisplay::InfoBar => format!("{} {}", usage_display, get_bar(perc_val)),
117 DiskDisplay::BarInfo => format!("{} {}", get_bar(perc_val), usage_display),
118 DiskDisplay::Bar => get_bar(perc_val),
119 };
120
121 let subtitle = match subtitle_mode {
123 DiskSubtitle::Name => get_device_name(path),
124 DiskSubtitle::Dir => path
125 .trim_start_matches('/')
126 .split('/')
127 .next()
128 .unwrap_or("")
129 .to_string(),
130 DiskSubtitle::None => "".to_string(),
131 DiskSubtitle::Mount => path.to_string(),
132 };
133
134 let full_subtitle = if subtitle.is_empty() {
135 "Disk".to_string()
136 } else {
137 format!("Disk ({})", subtitle)
138 };
139
140 Some((full_subtitle, final_str))
141}
142
143fn get_device_name(path: &str) -> String {
144 if let Ok(content) = fs::read_to_string("/proc/mounts") {
145 for line in content.lines() {
146 let parts: Vec<&str> = line.split_whitespace().collect();
147 if parts.len() >= 2 && parts[1] == path {
148 return parts[0].to_string();
149 }
150 }
151 }
152 "".to_string()
153}
154
155fn format_size(bytes: u64) -> String {
156 const KB: u64 = 1024;
157 const MB: u64 = KB * 1024;
158 const GB: u64 = MB * 1024;
159 const TB: u64 = GB * 1024;
160
161 if bytes >= TB {
162 format!("{:.1}T", bytes as f64 / TB as f64)
163 } else if bytes >= GB {
164 format!("{:.1}G", bytes as f64 / GB as f64)
165 } else if bytes >= MB {
166 format!("{:.1}M", bytes as f64 / MB as f64)
167 } else if bytes >= KB {
168 format!("{:.1}K", bytes as f64 / KB as f64)
169 } else {
170 format!("{}B", bytes)
171 }
172}
173
174