1#[cfg(feature = "os-integration")]
8use std::ffi::OsString;
9use std::path::{Path, PathBuf};
10
11use async_trait::async_trait;
12
13#[derive(Debug, thiserror::Error)]
15#[non_exhaustive]
16pub enum TrashError {
17 #[error("{0}")]
18 Backend(String),
19 #[error("task join failed: {0}")]
20 Join(String),
21}
22
23pub struct TrashId(pub(crate) TrashIdInner);
27
28pub(crate) enum TrashIdInner {
29 #[cfg(feature = "os-integration")]
31 System(OsString),
32 #[cfg(not(feature = "os-integration"))]
34 _Unavailable,
35}
36
37impl TrashId {
38 #[cfg(feature = "os-integration")]
40 pub(crate) fn system(id: OsString) -> Self {
41 Self(TrashIdInner::System(id))
42 }
43}
44
45impl std::fmt::Debug for TrashId {
46 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
47 match &self.0 {
48 #[cfg(feature = "os-integration")]
49 TrashIdInner::System(_) => f.write_str("TrashId::System(..)"),
50 #[cfg(not(feature = "os-integration"))]
51 TrashIdInner::_Unavailable => f.write_str("TrashId::Unavailable"),
52 }
53 }
54}
55
56#[derive(Debug)]
58pub struct TrashEntry {
59 pub id: TrashId,
60 pub name: String,
61 pub original_path: PathBuf,
62 pub deleted_at: i64,
64}
65
66pub fn find_restore_match<T>(items: Vec<(String, T)>, target: &str) -> Result<Vec<T>, String> {
71 let mut exact = Vec::new();
72 let mut substring = Vec::new();
73 let mut substring_names = Vec::new();
74
75 for (name, item) in items {
76 if name == target {
77 exact.push(item);
78 } else if name.contains(target) {
79 substring_names.push(name);
80 substring.push(item);
81 }
82 }
83
84 if exact.len() == 1 {
85 return Ok(exact);
86 }
87
88 let mut all_names: Vec<String> = Vec::new();
90 if !exact.is_empty() {
91 all_names.extend(std::iter::repeat_n(target.to_string(), exact.len()));
92 }
93 all_names.extend(substring_names);
94
95 let mut all: Vec<T> = exact;
96 all.extend(substring);
97
98 if all.is_empty() {
99 return Err(format!("'{}' not found in trash", target));
100 }
101 if all.len() > 1 {
102 return Err(format!(
103 "multiple matches for '{}': {}. Be more specific.",
104 target,
105 all_names.join(", ")
106 ));
107 }
108 Ok(all)
109}
110
111#[async_trait]
113pub trait TrashBackend: Send + Sync {
114 async fn trash(&self, path: &Path) -> Result<(), TrashError>;
116
117 async fn trash_bytes(&self, original_path: &Path, bytes: &[u8]) -> Result<(), TrashError>;
126
127 async fn list(&self, filter: Option<&str>) -> Result<Vec<TrashEntry>, TrashError>;
129
130 async fn find_by_name(&self, name: &str) -> Result<Vec<TrashEntry>, TrashError>;
134
135 async fn restore(&self, entries: Vec<TrashEntry>) -> Result<(), TrashError>;
137
138 async fn purge_all(&self) -> Result<usize, TrashError>;
140}