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)
105 .round()
106 .clamp(0.0, 100.0) as u8;
107
108 let total_h = format_size(total);
110 let used_h = format_size(used);
111
112 let usage_display = format!("{} / {}", used_h, total_h);
113 let perc_val = percent.min(100);
114
115 let final_str = match display_mode {
116 DiskDisplay::Info => usage_display,
117 DiskDisplay::Percentage => format!("{}% {}", percent, get_bar(perc_val)),
118 DiskDisplay::InfoBar => format!("{} {}", usage_display, get_bar(perc_val)),
119 DiskDisplay::BarInfo => format!("{} {}", get_bar(perc_val), usage_display),
120 DiskDisplay::Bar => get_bar(perc_val),
121 };
122
123 let subtitle = match subtitle_mode {
125 DiskSubtitle::Name => get_device_name(path),
126 DiskSubtitle::Dir => path
127 .trim_start_matches('/')
128 .split('/')
129 .next()
130 .unwrap_or("")
131 .to_string(),
132 DiskSubtitle::None => "".to_string(),
133 DiskSubtitle::Mount => path.to_string(),
134 };
135
136 let full_subtitle = if subtitle.is_empty() {
137 "Disk".to_string()
138 } else {
139 format!("Disk ({})", subtitle)
140 };
141
142 Some((full_subtitle, final_str))
143}
144
145fn get_device_name(path: &str) -> String {
146 if let Ok(content) = fs::read_to_string("/proc/mounts") {
147 for line in content.lines() {
148 let parts: Vec<&str> = line.split_whitespace().collect();
149 if parts.len() >= 2 && parts[1] == path {
150 return parts[0].to_string();
151 }
152 }
153 }
154 "".to_string()
155}
156
157fn format_size(bytes: u64) -> String {
158 const KB: u64 = 1024;
159 const MB: u64 = KB * 1024;
160 const GB: u64 = MB * 1024;
161 const TB: u64 = GB * 1024;
162
163 if bytes >= TB {
164 format!("{:.1}T", bytes as f64 / TB as f64)
165 } else if bytes >= GB {
166 format!("{:.1}G", bytes as f64 / GB as f64)
167 } else if bytes >= MB {
168 format!("{:.1}M", bytes as f64 / MB as f64)
169 } else if bytes >= KB {
170 format!("{:.1}K", bytes as f64 / KB as f64)
171 } else {
172 format!("{}B", bytes)
173 }
174}
175
176