Skip to main content

trash_cli_core/
helpers.rs

1//! Shared utility helpers for migration-aware command behavior.
2
3use chrono::{DateTime, NaiveDateTime, TimeZone, Utc};
4use std::path::{Path, PathBuf};
5use std::time::{Duration, SystemTime};
6
7/// File extension used by trash info files.
8pub const TRASHINFO_EXTENSION: &str = ".trashinfo";
9
10/// Deletion date format commonly used by Trash info metadata.
11pub const TRASHINFO_TIME_FORMAT: &str = "%Y-%m-%dT%H:%M:%S";
12
13/// Returns a user-safe, trimmed path string that can be used in logs and messages.
14pub fn sanitize_user_path(path: &Path) -> String {
15    path.display().to_string().trim().to_string()
16}
17
18/// Builds a deterministic, namespaced filename for trash files.
19pub fn build_unique_basename(file_name: &str, suffix: u64) -> String {
20    let base = Path::new(file_name)
21        .file_name()
22        .and_then(|v| v.to_str())
23        .unwrap_or("item");
24    format!("{base}.{suffix}")
25}
26
27/// Parses an ISO-like deletion date string into a UTC datetime.
28pub fn parse_trash_datetime(value: &str) -> Option<DateTime<Utc>> {
29    NaiveDateTime::parse_from_str(value, TRASHINFO_TIME_FORMAT)
30        .ok()
31        .map(|naive| Utc.from_utc_datetime(&naive))
32        .or_else(|| DateTime::parse_from_rfc3339(value).ok().map(|dt| dt.with_timezone(&Utc)))
33}
34
35/// Serializes a UTC datetime into the repository-standard trash format.
36pub fn serialize_system_time(time: SystemTime) -> String {
37    let dt = DateTime::<Utc>::from(time);
38    dt.format(TRASHINFO_TIME_FORMAT).to_string()
39}
40
41/// Human readable size rendering shared across commands.
42pub fn print_size(bytes: u64) -> String {
43    const SUFFIXES: [&str; 5] = ["B", "K", "M", "G", "T"];
44    let mut value = bytes as f64;
45    let mut idx = 0usize;
46
47    while value >= 1024.0 && idx < SUFFIXES.len() - 1 {
48        value /= 1024.0;
49        idx += 1;
50    }
51
52    if idx == 0 {
53        format!("{:.0} {}", value, SUFFIXES[idx])
54    } else {
55        format!("{:.1} {}", value, SUFFIXES[idx])
56    }
57}
58
59/// Returns a normalized path by resolving `.` and `..` segments where possible.
60pub fn canonical_or_relaxed(path: &Path) -> PathBuf {
61    path.canonicalize().unwrap_or_else(|_| path.to_path_buf())
62}
63
64/// Produces a human readable timeout string from duration.
65pub fn format_duration(duration: Duration) -> String {
66    let secs = duration.as_secs();
67    let mins = secs / 60;
68    let hours = mins / 60;
69    let days = hours / 24;
70    let rem_secs = secs % 60;
71    let rem_mins = mins % 60;
72    let rem_hours = hours % 24;
73
74    if days > 0 {
75        format!("{days}d {rem_hours:02}:{rem_mins:02}:{rem_secs:02}")
76    } else if hours > 0 {
77        format!("{hours}h {rem_mins:02}:{rem_secs:02}")
78    } else if mins > 0 {
79        format!("{mins}m {rem_secs:02}s")
80    } else {
81        format!("{secs}s")
82    }
83}