update
This commit is contained in:
parent
e9f87764fb
commit
22fbd032dd
13 changed files with 481 additions and 104 deletions
38
Cargo.lock
generated
38
Cargo.lock
generated
|
@ -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"
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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
|
||||
------------
|
||||
|
||||
|
|
1
run-tests.sh
Executable file
1
run-tests.sh
Executable file
|
@ -0,0 +1 @@
|
|||
#!/bin/bash
|
10
src/dir.rs
10
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<PathBuf, Error> {
|
||||
let target = self.0.join("files");
|
||||
if !target.exists() {
|
||||
|
|
32
src/lib.rs
Normal file
32
src/lib.rs
Normal file
|
@ -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();
|
||||
}
|
40
src/main.rs
40
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::<Vec<_>>();
|
||||
files.sort_unstable_by_key(|info| info.deletion_date);
|
||||
for (i, info) in files.iter().enumerate() {
|
||||
println!(
|
||||
"[{}]\t{}\t{}",
|
||||
|
|
77
src/mounts.rs
Normal file
77
src/mounts.rs
Normal file
|
@ -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<OsStr>) -> PathBuf {
|
||||
Path::new(&s).to_path_buf()
|
||||
}
|
||||
|
||||
impl From<libmount::mountinfo::MountPoint<'_>> 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<MountPoint>);
|
||||
|
||||
impl Mounts {
|
||||
pub fn read() -> Result<Mounts, Error> {
|
||||
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<Path>) -> Option<PathBuf> {
|
||||
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())
|
||||
}
|
||||
}
|
73
src/ops.rs
73
src/ops.rs
|
@ -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<u32>) -> 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<Path>, 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(())
|
||||
}
|
33
src/ops/empty.rs
Normal file
33
src/ops/empty.rs
Normal file
|
@ -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<u32>) -> 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(())
|
||||
}
|
5
src/ops/mod.rs
Normal file
5
src/ops/mod.rs
Normal file
|
@ -0,0 +1,5 @@
|
|||
mod empty;
|
||||
mod put;
|
||||
|
||||
pub use self::empty::empty;
|
||||
pub use self::put::put;
|
224
src/ops/put.rs
Normal file
224
src/ops/put.rs
Normal file
|
@ -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<PathBuf>, 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<Path>, 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>,
|
||||
path: impl AsRef<Path>,
|
||||
) -> 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<Path>) -> 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<Path>) -> 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<Path>) -> 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(())
|
||||
}
|
||||
}
|
48
src/utils.rs
Normal file
48
src/utils.rs
Normal file
|
@ -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<Path>) -> Result<PathBuf, Error> {
|
||||
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<Path>, dst: impl AsRef<Path>) -> 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(())
|
||||
}
|
Loading…
Reference in a new issue