Skip to main content

kaish_kernel/
trash.rs

1//! Trash backend trait and types.
2//!
3//! Abstracts trash operations behind a trait so the implementation
4//! can be swapped (system trash, WASI, pure-Rust disk trash, etc.)
5//! without changing builtins.
6
7#[cfg(feature = "os-integration")]
8use std::ffi::OsString;
9use std::path::{Path, PathBuf};
10
11use async_trait::async_trait;
12
13/// Errors from trash operations.
14#[derive(Debug, thiserror::Error)]
15pub enum TrashError {
16    #[error("{0}")]
17    Backend(String),
18    #[error("task join failed: {0}")]
19    Join(String),
20}
21
22/// Opaque trash item identifier.
23///
24/// Wraps backend-specific IDs so callers don't depend on the `trash` crate directly.
25pub struct TrashId(pub(crate) TrashIdInner);
26
27pub(crate) enum TrashIdInner {
28    /// System trash: wraps the `trash` crate's `OsString` ID.
29    #[cfg(feature = "os-integration")]
30    System(OsString),
31    /// Placeholder — TrashId is opaque and never constructed without a backend.
32    #[cfg(not(feature = "os-integration"))]
33    _Unavailable,
34}
35
36impl TrashId {
37    /// Create a TrashId wrapping a system trash item ID.
38    #[cfg(feature = "os-integration")]
39    pub(crate) fn system(id: OsString) -> Self {
40        Self(TrashIdInner::System(id))
41    }
42}
43
44impl std::fmt::Debug for TrashId {
45    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
46        match &self.0 {
47            #[cfg(feature = "os-integration")]
48            TrashIdInner::System(_) => f.write_str("TrashId::System(..)"),
49            #[cfg(not(feature = "os-integration"))]
50            TrashIdInner::_Unavailable => f.write_str("TrashId::Unavailable"),
51        }
52    }
53}
54
55/// A trashed item, independent of backend.
56#[derive(Debug)]
57pub struct TrashEntry {
58    pub id: TrashId,
59    pub name: String,
60    pub original_path: PathBuf,
61    /// Seconds since UNIX epoch when the item was deleted.
62    pub deleted_at: i64,
63}
64
65/// Find restore matches: exact (1) wins, else substring.
66///
67/// Single pass over items. Returns matched items or error message.
68/// Used by backends that need name-based matching (e.g., SystemTrash).
69pub fn find_restore_match<T>(items: Vec<(String, T)>, target: &str) -> Result<Vec<T>, String> {
70    let mut exact = Vec::new();
71    let mut substring = Vec::new();
72    let mut substring_names = Vec::new();
73
74    for (name, item) in items {
75        if name == target {
76            exact.push(item);
77        } else if name.contains(target) {
78            substring_names.push(name);
79            substring.push(item);
80        }
81    }
82
83    if exact.len() == 1 {
84        return Ok(exact);
85    }
86
87    // Combine exact + substring if no single exact match
88    let mut all_names: Vec<String> = Vec::new();
89    if !exact.is_empty() {
90        all_names.extend(std::iter::repeat_n(target.to_string(), exact.len()));
91    }
92    all_names.extend(substring_names);
93
94    let mut all: Vec<T> = exact;
95    all.extend(substring);
96
97    if all.is_empty() {
98        return Err(format!("'{}' not found in trash", target));
99    }
100    if all.len() > 1 {
101        return Err(format!(
102            "multiple matches for '{}': {}. Be more specific.",
103            target,
104            all_names.join(", ")
105        ));
106    }
107    Ok(all)
108}
109
110/// Backend trait for trash operations.
111#[async_trait]
112pub trait TrashBackend: Send + Sync {
113    /// Move a file or directory to trash.
114    async fn trash(&self, path: &Path) -> Result<(), TrashError>;
115
116    /// List trashed items, optionally filtered by name substring.
117    async fn list(&self, filter: Option<&str>) -> Result<Vec<TrashEntry>, TrashError>;
118
119    /// Find entries matching a name (exact first, then substring).
120    ///
121    /// Returns matched entries or an error describing the ambiguity.
122    async fn find_by_name(&self, name: &str) -> Result<Vec<TrashEntry>, TrashError>;
123
124    /// Restore trashed items to their original locations.
125    async fn restore(&self, entries: Vec<TrashEntry>) -> Result<(), TrashError>;
126
127    /// Permanently delete all trashed items. Returns count purged.
128    async fn purge_all(&self) -> Result<usize, TrashError>;
129}