1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
//! Filesystem utility helpers shared across binaries (CLI, launcher-service, ...).
//!
//! These helpers were previously duplicated locally in each binary's cache-cleanup
//! code; they live here so there's a single implementation to maintain.
use std::path::{Path, PathBuf};
/// Build a path rooted at the user's home directory by joining the given segments.
///
/// Falls back to `.` (the current directory) if the home directory cannot be
/// determined, matching the previous ad-hoc `dirs::home_dir().unwrap_or_else(...)`
/// behavior duplicated across the CLI, auth, and launcher-service crates.
///
/// # Example
///
/// ```
/// use mecha10_core::fs_utils::home_subpath;
///
/// let path = home_subpath(&[".mecha10", "credentials.json"]);
/// assert!(path.ends_with(".mecha10/credentials.json"));
/// ```
pub fn home_subpath(segments: &[&str]) -> PathBuf {
let mut path = dirs::home_dir().unwrap_or_else(|| PathBuf::from("."));
for segment in segments {
path.push(segment);
}
path
}
/// Calculate the total size (in bytes) of a directory, recursively.
///
/// Unreadable directories/entries are silently skipped (best-effort accounting
/// for cache-cleanup UX, not a strict filesystem audit).
pub fn dir_size(path: &Path) -> u64 {
let mut total = 0;
if let Ok(entries) = std::fs::read_dir(path) {
for entry in entries.flatten() {
let path = entry.path();
if path.is_dir() {
total += dir_size(&path);
} else if let Ok(metadata) = path.metadata() {
total += metadata.len();
}
}
}
total
}
/// Format a byte count as a human-readable string (e.g. `1.5 MB`).
pub fn format_size(bytes: u64) -> String {
const KB: u64 = 1024;
const MB: u64 = 1024 * KB;
const GB: u64 = 1024 * MB;
if bytes >= GB {
format!("{:.1} GB", bytes as f64 / GB as f64)
} else if bytes >= MB {
format!("{:.1} MB", bytes as f64 / MB as f64)
} else if bytes >= KB {
format!("{:.1} KB", bytes as f64 / KB as f64)
} else {
format!("{} B", bytes)
}
}
#[cfg(test)]
mod tests {
use super::*;
use std::fs;
#[test]
fn format_size_formats_by_magnitude() {
assert_eq!(format_size(0), "0 B");
assert_eq!(format_size(512), "512 B");
assert_eq!(format_size(1024), "1.0 KB");
assert_eq!(format_size(1024 * 1024), "1.0 MB");
assert_eq!(format_size(1024 * 1024 * 1024), "1.0 GB");
assert_eq!(format_size(1536 * 1024 * 1024), "1.5 GB");
}
#[test]
fn dir_size_sums_nested_files() {
let dir = tempfile::tempdir().expect("tempdir");
fs::write(dir.path().join("a.txt"), b"hello").unwrap();
let nested = dir.path().join("nested");
fs::create_dir(&nested).unwrap();
fs::write(nested.join("b.txt"), b"world!").unwrap();
assert_eq!(dir_size(dir.path()), 5 + 6);
}
#[test]
fn dir_size_missing_dir_is_zero() {
assert_eq!(dir_size(Path::new("/nonexistent/does/not/exist")), 0);
}
}