~iptq/garbage

02ba29053be29149ec4ca4f73cd375f02f2cf21b — Michael Zhang 1 year, 1 month ago 45fc4f1
fix panic problem by not converting to utf-8 all the time
9 files changed, 110 insertions(+), 27 deletions(-)

M Cargo.lock
M Cargo.toml
M src/dir.rs
M src/info.rs
M src/lib.rs
M src/ops/list.rs
M src/ops/put.rs
M src/ops/restore.rs
M src/utils.rs
M Cargo.lock => Cargo.lock +7 -0
@@ 77,6 77,7 @@ dependencies = [
 "libc 0.2.67 (registry+https://github.com/rust-lang/crates.io-index)",
 "libmount 0.1.15 (registry+https://github.com/rust-lang/crates.io-index)",
 "log 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)",
 "percent-encoding 2.1.0 (registry+https://github.com/rust-lang/crates.io-index)",
 "structopt 0.3.11 (registry+https://github.com/rust-lang/crates.io-index)",
 "thiserror 1.0.11 (registry+https://github.com/rust-lang/crates.io-index)",
 "walkdir 2.3.1 (registry+https://github.com/rust-lang/crates.io-index)",


@@ 157,6 158,11 @@ dependencies = [
]

[[package]]
name = "percent-encoding"
version = "2.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"

[[package]]
name = "proc-macro-error"
version = "0.4.11"
source = "registry+https://github.com/rust-lang/crates.io-index"


@@ 388,6 394,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
"checksum nix 0.14.1 (registry+https://github.com/rust-lang/crates.io-index)" = "6c722bee1037d430d0f8e687bbdbf222f27cc6e4e68d5caf630857bb2b6dbdce"
"checksum num-integer 0.1.42 (registry+https://github.com/rust-lang/crates.io-index)" = "3f6ea62e9d81a77cd3ee9a2a5b9b609447857f3d358704331e4ef39eb247fcba"
"checksum num-traits 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)" = "c62be47e61d1842b9170f0fdeec8eba98e60e90e5446449a0545e5152acd7096"
"checksum percent-encoding 2.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "d4fd5641d01c8f18a23da7b6fe29298ff4b55afcccdf78973b24cf3175fee32e"
"checksum proc-macro-error 0.4.11 (registry+https://github.com/rust-lang/crates.io-index)" = "e7959c6467d962050d639361f7703b2051c43036d03493c36f01d440fdd3138a"
"checksum proc-macro-error-attr 0.4.11 (registry+https://github.com/rust-lang/crates.io-index)" = "e4002d9f55991d5e019fb940a90e1a95eb80c24e77cb2462dd4dc869604d543a"
"checksum proc-macro2 1.0.9 (registry+https://github.com/rust-lang/crates.io-index)" = "6c09721c6781493a2a492a96b5a5bf19b65917fe6728884e7c44dd0c60ca3435"

M Cargo.toml => Cargo.toml +1 -0
@@ 25,3 25,4 @@ structopt = "0.3"
thiserror = "1.0"
walkdir = "2.2"
xdg = "2.2"
percent-encoding = "2.1.0"

M src/dir.rs => src/dir.rs +10 -5
@@ 1,4 1,6 @@
use std::ffi::OsStr;
use std::fs;
use std::os::unix::ffi::OsStrExt;
use std::path::{Path, PathBuf};

use walkdir::{DirEntry, WalkDir};


@@ 113,13 115,16 @@ impl Iterator for TrashDirIter {
            entry
        };

        let name = entry.path().file_name().unwrap().to_str().unwrap();
        let deleted_path = if !name.ends_with(".trashinfo") {
        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(name.trim_end_matches(".trashinfo"))
            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(Error::from))
    }

M src/info.rs => src/info.rs +11 -2
@@ 1,10 1,14 @@
use std::ffi::OsStr;
use std::fs::File;
use std::io::{self, BufRead, BufReader, Write};
use std::os::unix::ffi::OsStrExt;
use std::path::{Path, PathBuf};

use chrono::{DateTime, Local, TimeZone};
use percent_encoding::{percent_decode, };

use crate::errors::{Error, TrashInfoError};
use crate::utils;

const DATE_FORMAT: &str = "%Y-%m-%dT%H:%M:%S";



@@ 67,7 71,8 @@ impl TrashInfo {
            if let Some((key, value)) = parse_key_value(&line) {
                match key {
                    "Path" => {
                        let value = PathBuf::from(value);
                        let value = percent_decode(value.as_bytes()).collect::<Vec<_>>();
                        let value = PathBuf::from(OsStr::from_bytes(&value));
                        path = Some(value)
                    }
                    "DeletionDate" => {


@@ 102,7 107,11 @@ impl TrashInfo {
    /// Write the current TrashInfo into a .trashinfo file.
    pub fn write(&self, mut out: impl Write) -> Result<(), io::Error> {
        writeln!(out, "[Trash Info]")?;
        writeln!(out, "Path={}", self.path.to_str().unwrap())?;
        writeln!(
            out,
            "Path={}",
            utils::percent_encode(&self.path)
        )?;
        writeln!(
            out,
            "DeletionDate={}",

M src/lib.rs => src/lib.rs +3 -1
@@ 12,12 12,14 @@ extern crate anyhow;
#[macro_use]
extern crate thiserror;

#[macro_use]
mod utils;

mod dir;
mod errors;
mod info;
mod mounts;
pub mod ops;
mod utils;

use std::path::PathBuf;


M src/ops/list.rs => src/ops/list.rs +2 -1
@@ 3,6 3,7 @@ use std::path::PathBuf;
use anyhow::Result;

use crate::TrashDir;
use crate::utils;

/// Options to pass to list
#[derive(StructOpt)]


@@ 30,7 31,7 @@ pub fn list(options: ListOptions) -> Result<()> {
        .collect::<Vec<_>>();
    files.sort_unstable_by_key(|info| info.deletion_date);
    for info in files {
        println!("{}\t{}", info.deletion_date, info.path.to_str().unwrap());
        println!("{}\t{}", info.deletion_date, utils::percent_encode(info.path));
    }

    Ok(())

M src/ops/put.rs => src/ops/put.rs +27 -16
@@ 7,20 7,23 @@ use std::path::{Path, PathBuf};
use anyhow::Result;
use chrono::Local;

use crate::utils;
use crate::utils::{self};
use crate::{TrashDir, TrashInfo};
use crate::{HOME_MOUNT, MOUNTS};

#[derive(Debug, Error)]
pub enum Error {
    #[error("Refusing to remove directory {0} without '-r' option")]
    MissingRecursiveOption(PathBuf),
    // #[error("Refusing to remove directory {0} without '-r' option")]
    // MissingRecursiveOption(PathBuf),

    #[error("Refusing to remove '.' or '..', skipping...")]
    CannotTrashDotDirs,

    #[error("Cancelled by user.")]
    CancelledByUser,

    #[error("Invalid filename.")]
    InvalidFilename,
}

/// Options to pass to put


@@ 153,19 156,13 @@ impl DeletionStrategy {
        let target = target.as_ref();

        // this will be None if target isn't a symlink
        let link_info = target.read_link().ok();

        // file is a directory
        // if !link_info.is_some() && target.is_dir() && !options.recursive {
        //     bail!(Error::MissingRecursiveOption(target.to_path_buf()));
        // }
        let _link_info = target.read_link().ok();

        let (trash_dir, requires_copy) = self.get_target_trash();

        // prompt if not suppressed
        // TODO: streamline this logic better
        if !options.force && (requires_copy || options.prompt) {
            // TODO: actually handle prompting instead of manually flushing
            if requires_copy {
                eprint!(
                    "Removing file '{}' requires potentially expensive copying. Continue? [Y/n] ",


@@ 174,6 171,7 @@ impl DeletionStrategy {
            } else if options.prompt {
                eprint!("Remove file '{}'? [Y/n] ", target.to_str().unwrap());
            }
            // TODO: actually handle prompting instead of manually flushing
            io::stderr().flush()?;

            let should_continue = loop {


@@ 196,14 194,27 @@ impl DeletionStrategy {
        // preparing metadata
        let now = Local::now();
        let elapsed = now.timestamp_millis();
        let file_name = format!(
            "{}.{}",
            elapsed,
            target.file_name().unwrap().to_str().unwrap()
        );

        let elapsed_str = elapsed.to_string();
        let target_file = match target.file_name() {
            Some(file) => file.to_os_string(),
            None => bail!(Error::InvalidFilename),
        };
        let file_name = crate::concat_os_str!(elapsed_str, ".", target_file);
        // {
        //     let buf = vec![0; elapsed_str.len() + 1 + target_file.len()];
        //     unimplemented!()
        //     //     format!(
        //     //     "{}.{}",
        //     //     elapsed,
        //     //     target.file_name().unwrap().to_str().unwrap()
        //     // )
        // };

        let trash_file_path = trash_dir.files_dir()?.join(&file_name);
        let trash_info_path = trash_dir.info_dir()?.join(file_name + ".trashinfo");
        let trash_info_path = trash_dir
            .info_dir()?
            .join(crate::concat_os_str!(file_name, ".trashinfo"));

        let trash_info = TrashInfo {
            path: utils::into_absolute(target)?,

M src/ops/restore.rs => src/ops/restore.rs +2 -1
@@ 5,6 5,7 @@ use std::path::PathBuf;
use anyhow::Result;

use crate::TrashDir;
use crate::utils;

/// Options to pass to restore
#[derive(StructOpt)]


@@ 50,7 51,7 @@ pub fn restore(options: RestoreOptions) -> Result<()> {
            "[{}]\t{}\t{}",
            i,
            info.deletion_date,
            info.path.to_str().unwrap()
            utils::percent_encode(&info.path)
        );
    }


M src/utils.rs => src/utils.rs +47 -1
@@ 1,10 1,15 @@
use std::env;
use std::ffi::{OsStr, OsString};
use std::fs;
use std::path::{Path, PathBuf};
use std::os::unix::ffi::OsStrExt;
use std::path::{Path, PathBuf, MAIN_SEPARATOR};

use anyhow::Result;
use percent_encoding::{AsciiSet};
use walkdir::WalkDir;

const MASK: &AsciiSet = percent_encoding::CONTROLS;

pub fn into_absolute(path: impl AsRef<Path>) -> Result<PathBuf> {
    let path = path.as_ref();



@@ 49,3 54,44 @@ pub fn recursive_copy(src: impl AsRef<Path>, dst: impl AsRef<Path>) -> Result<()

    Ok(())
}

/// Percent-encodes a path, but only the file names, not the separators.
pub fn percent_encode(path: impl AsRef<Path>) -> String {
    let path = path.as_ref();
    path.iter()
        .map(|segment| {
            percent_encoding::percent_encode(segment.as_bytes(), MASK).to_string()
        })
        .collect::<Vec<_>>()
        .join(&MAIN_SEPARATOR.to_string())
}

pub(crate) fn concat_os_str_iter<'a>(ss: &Vec<&'a OsStr>) -> OsString {
    let mut len = 0;
    for s in ss {
        len += s.len();
    }

    let mut buf = vec![0; len];
    let mut c = 0;
    for s in ss {
        let segment = &mut buf[c..c + s.len()];
        segment.copy_from_slice(s.as_bytes());
        c += s.len();
    }

    OsStr::from_bytes(&buf).to_os_string()
}

/// Concatenates an arbitrary number of OsStrs
#[macro_export]
macro_rules! concat_os_str {
    ($($str:expr),* $(,)?) => {
        {
            let _wtf = vec![
                $($str.as_ref(),)*
            ];
            crate::utils::concat_os_str_iter(&_wtf)
        }
    };
}