From 22fbd032dd7e0700ea00a014d11e4c282ee69078 Mon Sep 17 00:00:00 2001 From: Michael Zhang Date: Thu, 5 Dec 2019 01:06:49 -0600 Subject: [PATCH] update --- Cargo.lock | 38 ++++++++ Cargo.toml | 2 + README.md | 2 + run-tests.sh | 1 + src/dir.rs | 10 ++- src/lib.rs | 32 +++++++ src/main.rs | 40 +++------ src/mounts.rs | 77 ++++++++++++++++ src/ops.rs | 73 --------------- src/ops/empty.rs | 33 +++++++ src/ops/mod.rs | 5 ++ src/ops/put.rs | 224 +++++++++++++++++++++++++++++++++++++++++++++++ src/utils.rs | 48 ++++++++++ 13 files changed, 481 insertions(+), 104 deletions(-) create mode 100755 run-tests.sh create mode 100644 src/lib.rs create mode 100644 src/mounts.rs delete mode 100644 src/ops.rs create mode 100644 src/ops/empty.rs create mode 100644 src/ops/mod.rs create mode 100644 src/ops/put.rs create mode 100644 src/utils.rs diff --git a/Cargo.lock b/Cargo.lock index d55b6a2..9dd4585 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -40,6 +40,11 @@ name = "bitflags" version = "1.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" +[[package]] +name = "cc" +version = "1.0.48" +source = "registry+https://github.com/rust-lang/crates.io-index" + [[package]] name = "cfg-if" version = "0.1.10" @@ -89,6 +94,8 @@ dependencies = [ "chrono 0.4.10 (registry+https://github.com/rust-lang/crates.io-index)", "env_logger 0.7.1 (registry+https://github.com/rust-lang/crates.io-index)", "lazy_static 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.65 (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)", "regex 1.3.1 (registry+https://github.com/rust-lang/crates.io-index)", "structopt 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)", @@ -123,6 +130,16 @@ name = "libc" version = "0.2.65" source = "registry+https://github.com/rust-lang/crates.io-index" +[[package]] +name = "libmount" +version = "0.1.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "libc 0.2.65 (registry+https://github.com/rust-lang/crates.io-index)", + "nix 0.14.1 (registry+https://github.com/rust-lang/crates.io-index)", + "quick-error 1.2.2 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "log" version = "0.4.8" @@ -136,6 +153,18 @@ name = "memchr" version = "2.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" +[[package]] +name = "nix" +version = "0.14.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "bitflags 1.2.1 (registry+https://github.com/rust-lang/crates.io-index)", + "cc 1.0.48 (registry+https://github.com/rust-lang/crates.io-index)", + "cfg-if 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.65 (registry+https://github.com/rust-lang/crates.io-index)", + "void 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "num-integer" version = "0.1.41" @@ -321,6 +350,11 @@ name = "vec_map" version = "0.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" +[[package]] +name = "void" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" + [[package]] name = "walkdir" version = "2.2.9" @@ -379,6 +413,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" "checksum atty 0.2.13 (registry+https://github.com/rust-lang/crates.io-index)" = "1803c647a3ec87095e7ae7acfca019e98de5ec9a7d01343f611cf3152ed71a90" "checksum autocfg 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)" = "1d49d90015b3c36167a20fe2810c5cd875ad504b39cff3d4eae7977e6b7c1cb2" "checksum bitflags 1.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "cf1de2fe8c75bc145a2f577add951f8134889b4795d47466a54a5c846d691693" +"checksum cc 1.0.48 (registry+https://github.com/rust-lang/crates.io-index)" = "f52a465a666ca3d838ebbf08b241383421412fe7ebb463527bba275526d89f76" "checksum cfg-if 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)" = "4785bdd1c96b2a846b2bd7cc02e86b6b3dbf14e7e53446c4f54c92a361040822" "checksum chrono 0.4.10 (registry+https://github.com/rust-lang/crates.io-index)" = "31850b4a4d6bae316f7a09e691c944c28299298837edc0a03f755618c23cbc01" "checksum clap 2.33.0 (registry+https://github.com/rust-lang/crates.io-index)" = "5067f5bb2d80ef5d68b4c87db81601f0b75bca627bc2ef76b141d7b846a3c6d9" @@ -387,8 +422,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index" "checksum humantime 1.3.0 (registry+https://github.com/rust-lang/crates.io-index)" = "df004cfca50ef23c36850aaaa59ad52cc70d0e90243c3c7737a4dd32dc7a3c4f" "checksum lazy_static 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" "checksum libc 0.2.65 (registry+https://github.com/rust-lang/crates.io-index)" = "1a31a0627fdf1f6a39ec0dd577e101440b7db22672c0901fe00a9a6fbb5c24e8" +"checksum libmount 0.1.15 (registry+https://github.com/rust-lang/crates.io-index)" = "23c4c2ad2d5cbd2f5a05620c3daf45930add53ec207fa99ce5eec971089dc35f" "checksum log 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)" = "14b6052be84e6b71ab17edffc2eeabf5c2c3ae1fdb464aae35ac50c67a44e1f7" "checksum memchr 2.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "88579771288728879b57485cc7d6b07d648c9f0141eb955f8ab7f9d45394468e" +"checksum nix 0.14.1 (registry+https://github.com/rust-lang/crates.io-index)" = "6c722bee1037d430d0f8e687bbdbf222f27cc6e4e68d5caf630857bb2b6dbdce" "checksum num-integer 0.1.41 (registry+https://github.com/rust-lang/crates.io-index)" = "b85e541ef8255f6cf42bbfe4ef361305c6c135d10919ecc26126c4e5ae94bc09" "checksum num-traits 0.2.10 (registry+https://github.com/rust-lang/crates.io-index)" = "d4c81ffc11c212fa327657cb19dd85eb7419e163b5b076bede2bdb5c974c07e4" "checksum proc-macro-error 0.2.6 (registry+https://github.com/rust-lang/crates.io-index)" = "aeccfe4d5d8ea175d5f0e4a2ad0637e0f4121d63bd99d356fb1f39ab2e7c6097" @@ -413,6 +450,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" "checksum unicode-width 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)" = "7007dbd421b92cc6e28410fe7362e2e0a2503394908f417b68ec8d1c364c4e20" "checksum unicode-xid 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "826e7639553986605ec5979c7dd957c7895e93eabed50ab2ffa7f6128a75097c" "checksum vec_map 0.8.1 (registry+https://github.com/rust-lang/crates.io-index)" = "05c78687fb1a80548ae3250346c3db86a80a7cdd77bda190189f2d0a0987c81a" +"checksum void 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)" = "6a02e4885ed3bc0f2de90ea6dd45ebcbb66dacffe03547fadbb0eeae2770887d" "checksum walkdir 2.2.9 (registry+https://github.com/rust-lang/crates.io-index)" = "9658c94fa8b940eab2250bd5a457f9c48b748420d71293b165c8cdbe2f55f71e" "checksum winapi 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)" = "8093091eeb260906a183e6ae1abdba2ef5ef2257a21801128899c3fc699229c6" "checksum winapi-i686-pc-windows-gnu 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" diff --git a/Cargo.toml b/Cargo.toml index 1d7665c..fb68462 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -15,6 +15,8 @@ anyhow = "1.0" chrono = "0.4" env_logger = "0.7" lazy_static = "1.0" +libc = "0.2" +libmount = "0.1" log = "0.4" regex = "1.1" structopt = "0.3" diff --git a/README.md b/README.md index 17dc682..9407ced 100644 --- a/README.md +++ b/README.md @@ -5,6 +5,8 @@ rust ver of trash-cli, basic functionality is in, code is probably shit full free-desktop compliance when +* **Windows Recycle Bin not supported** + Installation ------------ diff --git a/run-tests.sh b/run-tests.sh new file mode 100755 index 0000000..a9bf588 --- /dev/null +++ b/run-tests.sh @@ -0,0 +1 @@ +#!/bin/bash diff --git a/src/dir.rs b/src/dir.rs index 7e7e662..cf91992 100644 --- a/src/dir.rs +++ b/src/dir.rs @@ -1,5 +1,5 @@ use std::fs; -use std::path::PathBuf; +use std::path::{Path, PathBuf}; use walkdir::{DirEntry, WalkDir}; @@ -7,14 +7,18 @@ use crate::errors::Error; use crate::info::TrashInfo; use crate::XDG; -#[derive(Debug)] -pub struct TrashDir(PathBuf); +#[derive(Clone, Debug)] +pub struct TrashDir(pub PathBuf); impl TrashDir { pub fn get_home_trash() -> Self { TrashDir(XDG.get_data_home().join("Trash")) } + pub fn path(&self) -> &Path { + self.0.as_ref() + } + pub fn files_dir(&self) -> Result { let target = self.0.join("files"); if !target.exists() { diff --git a/src/lib.rs b/src/lib.rs new file mode 100644 index 0000000..36c85ca --- /dev/null +++ b/src/lib.rs @@ -0,0 +1,32 @@ +#[macro_use] +extern crate lazy_static; +#[macro_use] +extern crate log; +#[macro_use] +extern crate anyhow; +#[macro_use] +extern crate thiserror; + +mod dir; +mod errors; +mod info; +mod mounts; +pub mod ops; +mod utils; + +use std::io; +use std::path::PathBuf; + +use xdg::BaseDirectories; + +pub use crate::dir::TrashDir; +pub use crate::errors::Error; +pub use crate::info::TrashInfo; +use crate::mounts::Mounts; + +lazy_static! { + static ref XDG: BaseDirectories = BaseDirectories::new().unwrap(); + pub static ref MOUNTS: Mounts = Mounts::read().unwrap(); + static ref HOME_TRASH: TrashDir = TrashDir::get_home_trash(); + static ref HOME_MOUNT: PathBuf = MOUNTS.get_mount_point(HOME_TRASH.path()).unwrap(); +} diff --git a/src/main.rs b/src/main.rs index c72977e..5da3fb6 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,28 +1,14 @@ #[macro_use] -extern crate lazy_static; -#[macro_use] -extern crate log; -#[macro_use] -extern crate thiserror; - -mod errors; -mod ops; -mod dir; -mod info; +extern crate anyhow; use std::fs; use std::io; use std::path::PathBuf; +use anyhow::Error; use structopt::StructOpt; -use xdg::BaseDirectories; -use crate::errors::Error; -use crate::dir::TrashDir; - -lazy_static! { - static ref XDG: BaseDirectories = BaseDirectories::new().unwrap(); -} +use garbage::*; #[derive(StructOpt)] enum Command { @@ -63,11 +49,13 @@ enum Command { fn main() -> Result<(), Error> { env_logger::init(); + // println!("{:?}", *garbage::MOUNTS); + let cmd = Command::from_args(); match cmd { - Command::Empty { dry, days } => match crate::ops::empty(dry, days) { + Command::Empty { dry, days } => match ops::empty(dry, days) { Ok(_) => (), - Err(err) => error!("error: {:?}", err), + Err(err) => eprintln!("error: {:?}", err), }, Command::List => { let home_trash = TrashDir::get_home_trash(); @@ -75,7 +63,7 @@ fn main() -> Result<(), Error> { let info = match info { Ok(info) => info, Err(err) => { - warn!("failed to get file info: {:?}", err); + eprintln!("failed to get file info: {:?}", err); continue; } }; @@ -85,26 +73,22 @@ fn main() -> Result<(), Error> { Command::Put { paths, recursive, .. } => { - for path in paths { - match crate::ops::put(path, recursive) { - Ok(_) => (), - Err(err) => error!("error: {:?}", err), - } - } + ops::put(paths, recursive); } Command::Restore => { let home_trash = TrashDir::get_home_trash(); - let files = home_trash + let mut files = home_trash .iter() .unwrap() .filter_map(|entry| match entry { Ok(info) => Some(info), Err(err) => { - warn!("failed to get file info: {:?}", err); + eprintln!("failed to get file info: {:?}", err); None } }) .collect::>(); + files.sort_unstable_by_key(|info| info.deletion_date); for (i, info) in files.iter().enumerate() { println!( "[{}]\t{}\t{}", diff --git a/src/mounts.rs b/src/mounts.rs new file mode 100644 index 0000000..bdc39c0 --- /dev/null +++ b/src/mounts.rs @@ -0,0 +1,77 @@ +use std::borrow::Cow; +use std::env; +use std::ffi::OsStr; +use std::fs::File; +use std::io::Read; +use std::path::{Path, PathBuf}; + +use anyhow::Error; +use libmount::mountinfo::Parser; + +use crate::utils; + +#[derive(Debug)] +pub struct MountPoint { + pub mount_id: u64, + pub parent_id: u64, + pub major: u64, + pub minor: u64, + pub root: PathBuf, + pub mount_point: PathBuf, +} + +fn get_path(s: Cow) -> PathBuf { + Path::new(&s).to_path_buf() +} + +impl From> for MountPoint { + fn from(mp: libmount::mountinfo::MountPoint) -> Self { + MountPoint { + mount_id: mp.mount_id, + parent_id: mp.parent_id, + major: mp.major, + minor: mp.minor, + root: get_path(mp.root), + mount_point: get_path(mp.mount_point), + } + } +} + +#[derive(Debug)] +pub struct Mounts(Vec); + +impl Mounts { + pub fn read() -> Result { + let pid = unsafe { libc::getpid() }; + let path = Path::new("/") + .join("proc") + .join(pid.to_string()) + .join("mountinfo"); + + let mut buf = Vec::new(); + { + let mut file = File::open(path)?; + file.read_to_end(&mut buf)?; + } + + let parser = Parser::new(&buf); + let mut mounts = Vec::new(); + + for mp in parser { + let mp = mp?; + mounts.push(MountPoint::from(mp)); + } + + Ok(Mounts(mounts)) + } + + pub fn get_mount_point(&self, path: impl AsRef) -> Option { + let path = utils::into_absolute(path).ok()?; + + self.0 + .iter() + .filter(|mp| path.starts_with(&mp.mount_point)) + .max_by_key(|mp| mp.mount_point.components().count()) + .map(|mp| mp.mount_point.to_path_buf()) + } +} diff --git a/src/ops.rs b/src/ops.rs deleted file mode 100644 index c618b37..0000000 --- a/src/ops.rs +++ /dev/null @@ -1,73 +0,0 @@ -use std::fs::{self, File}; -use std::path::Path; - -use chrono::{Duration, Local}; - -use crate::errors::Error; -use crate::dir::TrashDir; -use crate::info::TrashInfo; - -pub fn empty(dry: bool, days: Option) -> Result<(), Error> { - let home_trash = TrashDir::get_home_trash(); - 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?; - - // ignore files that were deleted after the cutoff (younger) - let ignore = file.deletion_date > cutoff; - - 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, recursive: bool) -> Result<(), Error> { - let path = path.as_ref().canonicalize()?; - if path.is_dir() && !recursive { - error!("cannot trash directories without --recursive"); - return Ok(()); - } - - let now = Local::now(); - let elapsed = now.timestamp_millis(); - - let home_trash = TrashDir::get_home_trash(); - let file_name = format!( - "{}.{}", - elapsed, - path.file_name().unwrap().to_str().unwrap() - ); - - let trash_file_path = home_trash.files_dir()?.join(&file_name); - let trash_info_path = home_trash.info_dir()?.join(file_name + ".trashinfo"); - - let trash_info = TrashInfo { - 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)?; - trash_info.write(&trash_info_file)?; - } - - let result = fs::rename(&path, &trash_file_path); - if result.is_err() { - fs::copy(&path, &trash_file_path)?; - } - - Ok(()) -} diff --git a/src/ops/empty.rs b/src/ops/empty.rs new file mode 100644 index 0000000..d861665 --- /dev/null +++ b/src/ops/empty.rs @@ -0,0 +1,33 @@ +use std::fs; + +use anyhow::Error; +use chrono::{Duration, Local}; + +use crate::TrashDir; +use crate::TrashInfo; + +pub fn empty(dry: bool, days: Option) -> Result<(), Error> { + let home_trash = TrashDir::get_home_trash(); + 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?; + + // ignore files that were deleted after the cutoff (younger) + let ignore = file.deletion_date > cutoff; + + if !ignore { + if dry { + println!("{:?}", file.path); + } else { + fs::remove_file(file.info_path)?; + fs::remove_file(file.deleted_path)?; + } + } + } + + Ok(()) +} diff --git a/src/ops/mod.rs b/src/ops/mod.rs new file mode 100644 index 0000000..02df138 --- /dev/null +++ b/src/ops/mod.rs @@ -0,0 +1,5 @@ +mod empty; +mod put; + +pub use self::empty::empty; +pub use self::put::put; diff --git a/src/ops/put.rs b/src/ops/put.rs new file mode 100644 index 0000000..4c0dd4d --- /dev/null +++ b/src/ops/put.rs @@ -0,0 +1,224 @@ +use std::env; +use std::fs::{self, File}; +use std::os::unix::fs::PermissionsExt; +use std::path::{Path, PathBuf}; + +use anyhow::Result; +use chrono::{Duration, Local}; + +use crate::utils; +use crate::TrashDir; +use crate::TrashInfo; +use crate::{HOME_MOUNT, HOME_TRASH, MOUNTS}; + +#[derive(Debug, Error)] +pub enum Error { + #[error("Refusing to remove '.' or '..', skipping...")] + CannotTrashDotDirs, +} + +pub fn put(paths: Vec, recursive: bool) -> Result<()> { + // println!("HOME MOUNT: {:?}", *HOME_MOUNT); + let strategy = DeletionStrategy::Copy; + for path in paths { + println!( + "PATH: {:?}, MOUNTPOINT: {:?}", + &path, + MOUNTS.get_mount_point(&path) + ); + if let Err(err) = strategy.delete(path) { + eprintln!("{:?}", err); + } + } + + Ok(()) +} + +// fn put_single(path: impl AsRef, recursive: bool) -> Result<()> { +// let current_dir = env::current_dir()?; +// let path = path.as_ref().canonicalize()?; + +// ensure!( +// path == current_dir +// || (current_dir.parent().is_some() && path == current_dir.parent().unwrap()), +// Error::CannotTrashDotDirs +// ); + +// // if path.is_dir() && !recursive { +// // error!("cannot trash directories without --recursive"); +// // return Ok(()); +// // } + +// let now = Local::now(); +// let elapsed = now.timestamp_millis(); + +// let home_trash = TrashDir::get_home_trash(); +// let file_name = format!( +// "{}.{}", +// elapsed, +// path.file_name().unwrap().to_str().unwrap() +// ); + +// let trash_file_path = home_trash.files_dir()?.join(&file_name); +// let trash_info_path = home_trash.info_dir()?.join(file_name + ".trashinfo"); + +// let trash_info = TrashInfo { +// 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)?; +// trash_info.write(&trash_info_file)?; +// } + +// let result = fs::rename(&path, &trash_file_path); +// if result.is_err() { +// fs::copy(&path, &trash_file_path)?; +// } + +// Ok(()) +// } + +pub enum DeletionStrategy { + Copy, + Topdir, + TopdirOrCopy, +} + +impl DeletionStrategy { + fn get_target_trash( + &self, + mount: impl AsRef, + path: impl AsRef, + ) -> Option<(TrashDir, bool)> { + let mount = mount.as_ref(); + let path = path.as_ref(); + + // first, are we on the home mount? + if mount == *HOME_MOUNT { + return Some((HOME_TRASH.clone(), false)); + } + + // are we just copying? + if let DeletionStrategy::Copy = self { + return Some((HOME_TRASH.clone(), true)); + } + + // try to use the $topdir/.Trash directory + let topdir_trash = mount.join(".Trash"); + if self.should_use_topdir_trash(&topdir_trash) { + return Some(( + TrashDir(topdir_trash.join(utils::get_uid().to_string())), + false, + )); + } + + // try to use the $topdir/.Trash-$uid directory + let topdir_trash_uid = mount.join(format!(".Trash-{}", utils::get_uid())); + if self.should_use_topdir_trash_uid(&topdir_trash_uid) { + return Some((TrashDir(topdir_trash_uid), false)); + } + + // do we have the copy option + if let DeletionStrategy::TopdirOrCopy = self { + return Some((HOME_TRASH.clone(), true)); + } + + None + } + + fn should_use_topdir_trash(&self, path: impl AsRef) -> bool { + let path = path.as_ref(); + if !path.exists() { + return false; + } + + let dir = match File::open(path) { + Ok(file) => file, + Err(_) => return false, + }; + let meta = match dir.metadata() { + Ok(meta) => meta, + Err(_) => return false, + }; + if meta.file_type().is_symlink() { + return false; + } + let perms = meta.permissions(); + + perms.mode() & 0o1000 > 0 + } + + fn should_use_topdir_trash_uid(&self, path: impl AsRef) -> bool { + let path = path.as_ref(); + if !path.exists() { + match fs::create_dir(path) { + Ok(_) => (), + Err(_) => return false, + }; + } + + return true; + } + + pub fn delete(&self, target: impl AsRef) -> Result<()> { + let target = target.as_ref(); + + // don't allow deleting '.' or '..' + let current_dir = env::current_dir()?; + println!("target: {:?}", target); + println!("current_dir: {:?}", current_dir); + println!("current_dir parent: {:?}", current_dir.parent()); + ensure!( + !(target == current_dir + || (current_dir.parent().is_some() && target == current_dir.parent().unwrap())), + Error::CannotTrashDotDirs + ); + + let target_mount = MOUNTS + .get_mount_point(target) + .ok_or_else(|| anyhow!("couldn't get mount point"))?; + let (trash_dir, copy) = match self.get_target_trash(target_mount, target) { + Some(x) => x, + None => bail!("no trash dir could be selected, u suck"), + }; + + println!("Trash dir: {:?}", trash_dir); + println!("Copying?: {:?}", copy); + + // preparing metadata + let now = Local::now(); + let elapsed = now.timestamp_millis(); + let file_name = 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 = TrashInfo { + path: target.to_path_buf(), + deletion_date: now, + deleted_path: trash_file_path.clone(), + info_path: trash_info_path.clone(), + }; + { + let trash_info_file = File::create(trash_info_path)?; + trash_info.write(&trash_info_file)?; + } + + // copy the file over + if copy { + utils::recursive_copy(&target, &trash_file_path)?; + fs::remove_dir_all(&target); + } else { + fs::rename(&target, &trash_file_path)?; + } + + Ok(()) + } +} diff --git a/src/utils.rs b/src/utils.rs new file mode 100644 index 0000000..72f4c57 --- /dev/null +++ b/src/utils.rs @@ -0,0 +1,48 @@ +use std::env; +use std::fs; +use std::path::{Path, PathBuf}; + +use walkdir::WalkDir; +use anyhow::Error; + +pub fn into_absolute(path: impl AsRef) -> Result { + let path = path.as_ref(); + + Ok(if !path.is_absolute() { + env::current_dir()?.canonicalize()?.join(path) + } else { + path.to_path_buf() + }) +} + +pub fn get_uid() -> u64 { + unsafe { libc::getuid().into() } +} + +pub fn recursive_copy(src: impl AsRef, dst: impl AsRef) -> Result<(), Error> { + let src = src.as_ref(); + let dst = dst.as_ref(); + + for entry in WalkDir::new(src).contents_first(false).follow_links(false).same_file_system(true) { + let entry = entry?; + let path = entry.path(); + let relative_path = path.strip_prefix(src)?; + + // this must be the root + if let None = relative_path.file_name() { + fs::create_dir(dst); + continue; + } + + let target_name = dst.join(relative_path); + if path.is_dir() { + fs::create_dir(&target_name); + } else { + fs::copy(path, &target_name); + } + println!("entry path: {:?}", relative_path); + println!("> copied to: {:?}", target_name); + } + + Ok(()) +}