use std::ffi::OsString;
use std::hash::{Hash, Hasher};
use std::path::{Path, PathBuf};
use std::fmt;
use std::{env::current_dir, error};
use log::trace;
#[cfg(test)]
pub mod tests;
#[cfg(target_os = "windows")]
#[path = "windows.rs"]
mod platform;
#[cfg(all(unix, not(target_os = "macos"), not(target_os = "ios"), not(target_os = "android")))]
#[path = "freedesktop.rs"]
mod platform;
#[cfg(target_os = "macos")]
pub mod macos;
#[cfg(target_os = "macos")]
use macos as platform;
pub const DEFAULT_TRASH_CTX: TrashContext = TrashContext::new();
#[derive(Clone, Default, Debug)]
pub struct TrashContext {
#[cfg_attr(not(target_os = "macos"), allow(dead_code))]
platform_specific: platform::PlatformTrashContext,
}
impl TrashContext {
pub const fn new() -> Self {
Self { platform_specific: platform::PlatformTrashContext::new() }
}
pub fn delete<T: AsRef<Path>>(&self, path: T) -> Result<(), Error> {
self.delete_all(&[path])
}
pub fn delete_all<I, T>(&self, paths: I) -> Result<(), Error>
where
I: IntoIterator<Item = T>,
T: AsRef<Path>,
{
trace!("Starting canonicalize_paths");
let full_paths = canonicalize_paths(paths)?;
trace!("Finished canonicalize_paths");
self.delete_all_canonicalized(full_paths)
}
}
pub fn delete<T: AsRef<Path>>(path: T) -> Result<(), Error> {
DEFAULT_TRASH_CTX.delete(path)
}
pub fn delete_all<I, T>(paths: I) -> Result<(), Error>
where
I: IntoIterator<Item = T>,
T: AsRef<Path>,
{
DEFAULT_TRASH_CTX.delete_all(paths)
}
#[derive(Debug)]
pub enum Error {
Unknown {
description: String,
},
Os {
code: i32,
description: String,
},
#[cfg(all(unix, not(target_os = "macos"), not(target_os = "ios"), not(target_os = "android")))]
FileSystem {
path: PathBuf,
source: std::io::Error,
},
TargetedRoot,
CouldNotAccess {
target: String,
},
CanonicalizePath {
original: PathBuf,
},
ConvertOsString {
original: OsString,
},
RestoreCollision {
path: PathBuf,
remaining_items: Vec<TrashItem>,
},
RestoreTwins {
path: PathBuf,
items: Vec<TrashItem>,
},
}
impl fmt::Display for Error {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "Error during a `trash` operation: {self:?}")
}
}
impl error::Error for Error {
fn source(&self) -> Option<&(dyn error::Error + 'static)> {
match self {
#[cfg(all(unix, not(target_os = "macos"), not(target_os = "ios"), not(target_os = "android")))]
Self::FileSystem { path: _, source: e } => e.source(),
_ => None,
}
}
}
pub fn into_unknown<E: std::fmt::Display>(err: E) -> Error {
Error::Unknown { description: format!("{err}") }
}
pub(crate) fn canonicalize_paths<I, T>(paths: I) -> Result<Vec<PathBuf>, Error>
where
I: IntoIterator<Item = T>,
T: AsRef<Path>,
{
let paths = paths.into_iter();
paths
.map(|x| {
let target_ref = x.as_ref();
if target_ref.as_os_str().is_empty() {
return Err(Error::CanonicalizePath { original: target_ref.to_owned() });
}
let target = if target_ref.is_relative() {
let curr_dir = current_dir()
.map_err(|_| Error::CouldNotAccess { target: "[Current working directory]".into() })?;
curr_dir.join(target_ref)
} else {
target_ref.to_owned()
};
let parent = target.parent().ok_or(Error::TargetedRoot)?;
let canonical_parent =
parent.canonicalize().map_err(|_| Error::CanonicalizePath { original: parent.to_owned() })?;
if let Some(file_name) = target.file_name() {
Ok(canonical_parent.join(file_name))
} else {
Ok(canonical_parent)
}
})
.collect::<Result<Vec<_>, _>>()
}
#[derive(Debug, Clone)]
pub struct TrashItem {
pub id: OsString,
pub name: OsString,
pub original_parent: PathBuf,
pub time_deleted: i64,
}
impl TrashItem {
pub fn original_path(&self) -> PathBuf {
self.original_parent.join(&self.name)
}
}
impl PartialEq for TrashItem {
fn eq(&self, other: &Self) -> bool {
self.id == other.id
}
}
impl Eq for TrashItem {}
impl Hash for TrashItem {
fn hash<H: Hasher>(&self, state: &mut H) {
self.id.hash(state);
}
}
#[derive(Debug, Clone, Copy, Ord, PartialOrd, Eq, PartialEq, Hash)]
pub enum TrashItemSize {
Bytes(u64),
Entries(usize),
}
impl TrashItemSize {
pub fn size(&self) -> Option<u64> {
match self {
TrashItemSize::Bytes(s) => Some(*s),
TrashItemSize::Entries(_) => None,
}
}
pub fn entries(&self) -> Option<usize> {
match self {
TrashItemSize::Bytes(_) => None,
TrashItemSize::Entries(e) => Some(*e),
}
}
}
#[derive(Debug, Clone, Copy, Ord, PartialOrd, Eq, PartialEq, Hash)]
pub struct TrashItemMetadata {
pub size: TrashItemSize,
}
#[cfg(any(
target_os = "windows",
all(unix, not(target_os = "macos"), not(target_os = "ios"), not(target_os = "android"))
))]
pub mod os_limited {
use std::{
borrow::Borrow,
collections::HashSet,
hash::{Hash, Hasher},
};
use super::{platform, Error, TrashItem, TrashItemMetadata};
pub fn list() -> Result<Vec<TrashItem>, Error> {
platform::list()
}
pub fn is_empty() -> Result<bool, Error> {
platform::is_empty()
}
#[cfg(all(unix, not(target_os = "macos"), not(target_os = "ios"), not(target_os = "android")))]
pub fn trash_folders() -> Result<HashSet<std::path::PathBuf>, Error> {
platform::trash_folders()
}
pub fn metadata(item: &TrashItem) -> Result<TrashItemMetadata, Error> {
platform::metadata(item)
}
pub fn purge_all<I>(items: I) -> Result<(), Error>
where
I: IntoIterator,
<I as IntoIterator>::Item: Borrow<TrashItem>,
{
platform::purge_all(items)
}
pub fn restore_all<I>(items: I) -> Result<(), Error>
where
I: IntoIterator<Item = TrashItem>,
{
struct ItemWrapper<'a>(&'a TrashItem);
impl PartialEq for ItemWrapper<'_> {
fn eq(&self, other: &Self) -> bool {
self.0.original_path() == other.0.original_path()
}
}
impl Eq for ItemWrapper<'_> {}
impl Hash for ItemWrapper<'_> {
fn hash<H: Hasher>(&self, state: &mut H) {
self.0.original_path().hash(state);
}
}
let items = items.into_iter().collect::<Vec<_>>();
let mut item_set = HashSet::with_capacity(items.len());
for item in items.iter() {
if !item_set.insert(ItemWrapper(item)) {
return Err(Error::RestoreTwins { path: item.original_path(), items });
}
}
platform::restore_all(items)
}
}