~iptq/garbage

388c9e44520c3b42d59b64db7b4e7fce9a66470d — Michael Zhang 2 years ago 394192f
implement trash-empty
7 files changed, 83 insertions(+), 26 deletions(-)

M Cargo.lock
M Cargo.toml
M README.md
M src/main.rs
M src/ops.rs
M src/trashdir.rs
M src/trashinfo.rs
M Cargo.lock => Cargo.lock +1 -1
@@ 283,7 283,7 @@ dependencies = [

[[package]]
name = "trash"
version = "0.1.0"
version = "0.1.1"
dependencies = [
 "chrono 0.4.7 (registry+https://github.com/rust-lang/crates.io-index)",
 "env_logger 0.6.1 (registry+https://github.com/rust-lang/crates.io-index)",

M Cargo.toml => Cargo.toml +1 -1
@@ 1,6 1,6 @@
[package]
name = "trash"
version = "0.1.0"
version = "0.1.1"
authors = ["Michael Zhang <iptq@protonmail.com>"]
edition = "2018"


M README.md => README.md +2 -0
@@ 15,6 15,8 @@ $ trash restore
[..interactive]

$ trash list

$ trash empty [days]
```

About

M src/main.rs => src/main.rs +19 -6
@@ 24,7 24,16 @@ lazy_static! {
#[derive(StructOpt)]
enum Command {
    #[structopt(name = "empty")]
    Empty,
    Empty {
        /// Only list the files that are to be deleted, without
        /// actually deleting anything.
        #[structopt(long = "dry")]
        dry: bool,

        /// Delete all files older than (this number) of days.
        /// Removes everything if this option is not specified
        days: Option<u32>,
    },

    #[structopt(name = "list")]
    List,


@@ 53,12 62,13 @@ fn main() {

    let cmd = Command::from_args();
    match cmd {
        Command::Empty => {
            println!("TODO");
        }
        Command::Empty { dry, days } => match crate::ops::empty(dry, days) {
            Ok(_) => (),
            Err(err) => error!("error: {:?}", err),
        },
        Command::List => {
            let home_trash = TrashDir::get_home_trash();
            for info in home_trash.iter() {
            for info in home_trash.iter().unwrap() {
                let info = match info {
                    Ok(info) => info,
                    Err(err) => {


@@ 69,7 79,9 @@ fn main() {
                println!("{}\t{}", info.deletion_date, info.path.to_str().unwrap());
            }
        }
        Command::Put { paths, recursive, .. } => {
        Command::Put {
            paths, recursive, ..
        } => {
            for path in paths {
                match crate::ops::put(path, recursive) {
                    Ok(_) => (),


@@ 81,6 93,7 @@ fn main() {
            let home_trash = TrashDir::get_home_trash();
            let files = home_trash
                .iter()
                .unwrap()
                .filter_map(|entry| match entry {
                    Ok(info) => Some(info),
                    Err(err) => {

M src/ops.rs => src/ops.rs +34 -1
@@ 1,12 1,44 @@
use std::fs::{self, File};
use std::path::Path;

use chrono::Local;
use chrono::{Duration, Local};

use crate::errors::Error;
use crate::trashdir::TrashDir;
use crate::trashinfo::TrashInfo;

pub fn empty(dry: bool, days: Option<u32>) -> Result<(), Error> {
    let home_trash = TrashDir::get_home_trash();
    let files_dir = home_trash.files_dir()?;
    let info_dir = home_trash.info_dir()?;

    let cutoff = if let Some(days) = days {
        Local::now() - Duration::days(days.into())
    } else {
        Local::now()
    };
    for file in home_trash.iter()? {
        let file = file?;

        let mut ignore = false;
        // ignore files that were deleted after the cutoff (younger)
        if file.deletion_date > cutoff {
            ignore = true;
        }

        if !ignore {
            if dry {
                println!("{:?}", file.path);
            } else {
                fs::remove_file(file.info_path)?;
                fs::remove_file(file.deleted_path)?;
            }
        }
    }

    Ok(())
}

pub fn put(path: impl AsRef<Path>, recursive: bool) -> Result<(), Error> {
    let path = path.as_ref().canonicalize()?;
    if path.is_dir() && !recursive {


@@ 31,6 63,7 @@ pub fn put(path: impl AsRef<Path>, recursive: bool) -> Result<(), Error> {
        path: path.clone(),
        deletion_date: now,
        deleted_path: trash_file_path.clone(),
        info_path: trash_info_path.clone(),
    };
    {
        let trash_info_file = File::create(trash_info_path)?;

M src/trashdir.rs => src/trashdir.rs +12 -13
@@ 31,22 31,19 @@ impl TrashDir {
        Ok(target)
    }

    pub fn iter(&self) -> TrashDirIter {
        let iter = WalkDir::new(&self.0.join("info"))
        .contents_first(true)
    pub fn iter(&self) -> Result<TrashDirIter, Error> {
        let iter = WalkDir::new(&self.info_dir()?)
            .contents_first(true)
            .into_iter()
            .filter_entry(|entry| {
                // warn!("path: {:?}", entry.path());
                match entry.path().extension() {
                    Some(x) => x == "trashinfo",
                    _ => false,
                }
            .filter_entry(|entry| match entry.path().extension() {
                Some(x) => x == "trashinfo",
                _ => false,
            });
        TrashDirIter(Box::new(iter))
        Ok(TrashDirIter(self.0.clone(), Box::new(iter)))
    }
}

pub struct TrashDirIter(Box<Iterator<Item = walkdir::Result<DirEntry>>>);
pub struct TrashDirIter(PathBuf, Box<Iterator<Item = walkdir::Result<DirEntry>>>);

impl Iterator for TrashDirIter {
    type Item = Result<TrashInfo, Error>;


@@ 55,7 52,7 @@ impl Iterator for TrashDirIter {
        let entry = {
            let mut entry;
            loop {
                entry = match self.0.next() {
                entry = match self.1.next() {
                    Some(Ok(entry)) => entry,
                    Some(Err(err)) => return Some(Err(Error::from(err))),
                    None => return None,


@@ 68,6 65,8 @@ impl Iterator for TrashDirIter {
            entry
        };

        Some(TrashInfo::from_file(entry.path()).map_err(Error::from))
        let name = entry.path().file_name().unwrap();
        let deleted_path = self.0.join("files").join(name);
        Some(TrashInfo::from_files(entry.path(), deleted_path).map_err(Error::from))
    }
}

M src/trashinfo.rs => src/trashinfo.rs +14 -4
@@ 15,15 15,24 @@ const DATE_FORMAT: &str = "%Y-%m-%dT%H:%M:%S";

#[derive(Debug)]
pub struct TrashInfo {
    /// The original path where this file was located before it was deleted.
    pub path: PathBuf,
    pub deletion_date: DateTime<Local>,

    /// The location of the deleted file after deletion.
    pub deleted_path: PathBuf,
    /// The location of the `info` description file.
    pub info_path: PathBuf,
}

impl TrashInfo {
    pub fn from_file(path: impl AsRef<Path>) -> Result<Self, Error> {
        let path = path.as_ref();
        let original_path = path.to_path_buf();
    pub fn from_files(
        info_path: impl AsRef<Path>,
        deleted_path: impl AsRef<Path>,
    ) -> Result<Self, Error> {
        let path = info_path.as_ref();
        let info_path = path.to_path_buf();
        let deleted_path = deleted_path.as_ref().to_path_buf();
        let file = File::open(path)?;
        let reader = BufReader::new(file);



@@ 74,7 83,8 @@ impl TrashInfo {
        Ok(TrashInfo {
            path,
            deletion_date,
            deleted_path: original_path,
            deleted_path,
            info_path,
        })
    }