use std::ffi::OsStr;
use std::os::unix::ffi::OsStrExt;
use std::path::{Path, PathBuf};
use anyhow::{Context, Error, Result};
use garbage_fs::Filesystem;
use walkdir::{DirEntry, WalkDir};
use crate::TrashInfo;
use crate::XDG;
#[derive(Clone, Debug)]
pub struct TrashDir {
path: PathBuf,
}
impl TrashDir {
pub fn from(path: impl AsRef<Path>) -> Self {
let path = path.as_ref().to_path_buf();
TrashDir { path }
}
pub fn get_home_trash() -> Self {
TrashDir::from(XDG.get_data_home().join("Trash"))
}
pub fn from_opt(opt: Option<impl AsRef<Path>>) -> Self {
opt
.map(|path| TrashDir::from(path.as_ref().to_path_buf()))
.unwrap_or_else(|| TrashDir::get_home_trash())
}
pub fn mkdir(&self, fs: &impl Filesystem) -> Result<()> {
let path = &self.path;
if !fs.path_exists(&path)? {
fs.create_dir_all(&path)?;
}
Ok(())
}
pub fn path(&self) -> &Path {
self.path.as_ref()
}
pub fn files_dir(&self, fs: &impl Filesystem) -> Result<PathBuf> {
let target = self.path.join("files");
if !fs.path_exists(&target)? {
fs.create_dir_all(&target)
.with_context(|| format!("Could not create directory {:?}", target))?;
}
Ok(target)
}
pub fn info_dir(&self, fs: &impl Filesystem) -> Result<PathBuf> {
let target = self.path.join("info");
if !fs.path_exists(&target)? {
fs.create_dir_all(&target)?;
}
Ok(target)
}
pub fn check_info_dir(
&self,
fs: &impl Filesystem,
) -> Result<Option<PathBuf>> {
let target = self.path.join("info");
if !fs.path_exists(&target)? {
Ok(None)
} else {
Ok(Some(target))
}
}
pub fn iter(&self, fs: &impl Filesystem) -> Result<TrashDirIter> {
let iter = WalkDir::new(&self.info_dir(fs)?)
.contents_first(true)
.into_iter()
.filter_entry(|entry| match entry.path().extension() {
Some(x) => x == "trashinfo",
_ => false,
});
Ok(TrashDirIter(self.path.clone(), Box::new(iter)))
}
}
pub struct TrashDirIter(
PathBuf,
Box<dyn Iterator<Item = walkdir::Result<DirEntry>>>,
);
impl Iterator for TrashDirIter {
type Item = Result<TrashInfo>;
fn next(&mut self) -> Option<Self::Item> {
let entry = {
let mut entry;
loop {
entry = match self.1.next() {
Some(Ok(entry)) => entry,
Some(Err(err)) => {
return Some(Err(
Error::from(err).context("Could not open next entry."),
));
}
None => return None,
};
if entry.path().is_dir() {
continue;
}
break;
}
entry
};
let name = match entry.path().file_name() {
Some(name) => name,
None => return None,
};
let deleted_path = if !name.as_bytes().ends_with(b".trashinfo") {
return self.next();
} else {
self.0.join("files").join(OsStr::from_bytes(
&name.as_bytes()[..name.len() - b".trashinfo".len()],
))
};
Some(
TrashInfo::from_files(entry.path(), deleted_path)
.map_err(|err| err.into()),
)
}
}