fix panic problem by not converting to utf-8 all the time
This commit is contained in:
parent
45fc4f1ee1
commit
02ba29053b
9 changed files with 110 additions and 27 deletions
7
Cargo.lock
generated
7
Cargo.lock
generated
|
@ -77,6 +77,7 @@ dependencies = [
|
||||||
"libc 0.2.67 (registry+https://github.com/rust-lang/crates.io-index)",
|
"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)",
|
"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)",
|
"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)",
|
"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)",
|
"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)",
|
"walkdir 2.3.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
|
@ -156,6 +157,11 @@ dependencies = [
|
||||||
"autocfg 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
"autocfg 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "percent-encoding"
|
||||||
|
version = "2.1.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "proc-macro-error"
|
name = "proc-macro-error"
|
||||||
version = "0.4.11"
|
version = "0.4.11"
|
||||||
|
@ -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 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-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 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 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-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"
|
"checksum proc-macro2 1.0.9 (registry+https://github.com/rust-lang/crates.io-index)" = "6c09721c6781493a2a492a96b5a5bf19b65917fe6728884e7c44dd0c60ca3435"
|
||||||
|
|
|
@ -25,3 +25,4 @@ structopt = "0.3"
|
||||||
thiserror = "1.0"
|
thiserror = "1.0"
|
||||||
walkdir = "2.2"
|
walkdir = "2.2"
|
||||||
xdg = "2.2"
|
xdg = "2.2"
|
||||||
|
percent-encoding = "2.1.0"
|
||||||
|
|
15
src/dir.rs
15
src/dir.rs
|
@ -1,4 +1,6 @@
|
||||||
|
use std::ffi::OsStr;
|
||||||
use std::fs;
|
use std::fs;
|
||||||
|
use std::os::unix::ffi::OsStrExt;
|
||||||
use std::path::{Path, PathBuf};
|
use std::path::{Path, PathBuf};
|
||||||
|
|
||||||
use walkdir::{DirEntry, WalkDir};
|
use walkdir::{DirEntry, WalkDir};
|
||||||
|
@ -113,13 +115,16 @@ impl Iterator for TrashDirIter {
|
||||||
entry
|
entry
|
||||||
};
|
};
|
||||||
|
|
||||||
let name = entry.path().file_name().unwrap().to_str().unwrap();
|
let name = match entry.path().file_name() {
|
||||||
let deleted_path = if !name.ends_with(".trashinfo") {
|
Some(name) => name,
|
||||||
|
None => return None,
|
||||||
|
};
|
||||||
|
let deleted_path = if !name.as_bytes().ends_with(b".trashinfo") {
|
||||||
return self.next();
|
return self.next();
|
||||||
} else {
|
} else {
|
||||||
self.0
|
self.0.join("files").join(OsStr::from_bytes(
|
||||||
.join("files")
|
&name.as_bytes()[..name.len() - b".trashinfo".len()],
|
||||||
.join(name.trim_end_matches(".trashinfo"))
|
))
|
||||||
};
|
};
|
||||||
Some(TrashInfo::from_files(entry.path(), deleted_path).map_err(Error::from))
|
Some(TrashInfo::from_files(entry.path(), deleted_path).map_err(Error::from))
|
||||||
}
|
}
|
||||||
|
|
13
src/info.rs
13
src/info.rs
|
@ -1,10 +1,14 @@
|
||||||
|
use std::ffi::OsStr;
|
||||||
use std::fs::File;
|
use std::fs::File;
|
||||||
use std::io::{self, BufRead, BufReader, Write};
|
use std::io::{self, BufRead, BufReader, Write};
|
||||||
|
use std::os::unix::ffi::OsStrExt;
|
||||||
use std::path::{Path, PathBuf};
|
use std::path::{Path, PathBuf};
|
||||||
|
|
||||||
use chrono::{DateTime, Local, TimeZone};
|
use chrono::{DateTime, Local, TimeZone};
|
||||||
|
use percent_encoding::{percent_decode, };
|
||||||
|
|
||||||
use crate::errors::{Error, TrashInfoError};
|
use crate::errors::{Error, TrashInfoError};
|
||||||
|
use crate::utils;
|
||||||
|
|
||||||
const DATE_FORMAT: &str = "%Y-%m-%dT%H:%M:%S";
|
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) {
|
if let Some((key, value)) = parse_key_value(&line) {
|
||||||
match key {
|
match key {
|
||||||
"Path" => {
|
"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)
|
path = Some(value)
|
||||||
}
|
}
|
||||||
"DeletionDate" => {
|
"DeletionDate" => {
|
||||||
|
@ -102,7 +107,11 @@ impl TrashInfo {
|
||||||
/// Write the current TrashInfo into a .trashinfo file.
|
/// Write the current TrashInfo into a .trashinfo file.
|
||||||
pub fn write(&self, mut out: impl Write) -> Result<(), io::Error> {
|
pub fn write(&self, mut out: impl Write) -> Result<(), io::Error> {
|
||||||
writeln!(out, "[Trash Info]")?;
|
writeln!(out, "[Trash Info]")?;
|
||||||
writeln!(out, "Path={}", self.path.to_str().unwrap())?;
|
writeln!(
|
||||||
|
out,
|
||||||
|
"Path={}",
|
||||||
|
utils::percent_encode(&self.path)
|
||||||
|
)?;
|
||||||
writeln!(
|
writeln!(
|
||||||
out,
|
out,
|
||||||
"DeletionDate={}",
|
"DeletionDate={}",
|
||||||
|
|
|
@ -12,12 +12,14 @@ extern crate anyhow;
|
||||||
#[macro_use]
|
#[macro_use]
|
||||||
extern crate thiserror;
|
extern crate thiserror;
|
||||||
|
|
||||||
|
#[macro_use]
|
||||||
|
mod utils;
|
||||||
|
|
||||||
mod dir;
|
mod dir;
|
||||||
mod errors;
|
mod errors;
|
||||||
mod info;
|
mod info;
|
||||||
mod mounts;
|
mod mounts;
|
||||||
pub mod ops;
|
pub mod ops;
|
||||||
mod utils;
|
|
||||||
|
|
||||||
use std::path::PathBuf;
|
use std::path::PathBuf;
|
||||||
|
|
||||||
|
|
|
@ -3,6 +3,7 @@ use std::path::PathBuf;
|
||||||
use anyhow::Result;
|
use anyhow::Result;
|
||||||
|
|
||||||
use crate::TrashDir;
|
use crate::TrashDir;
|
||||||
|
use crate::utils;
|
||||||
|
|
||||||
/// Options to pass to list
|
/// Options to pass to list
|
||||||
#[derive(StructOpt)]
|
#[derive(StructOpt)]
|
||||||
|
@ -30,7 +31,7 @@ pub fn list(options: ListOptions) -> Result<()> {
|
||||||
.collect::<Vec<_>>();
|
.collect::<Vec<_>>();
|
||||||
files.sort_unstable_by_key(|info| info.deletion_date);
|
files.sort_unstable_by_key(|info| info.deletion_date);
|
||||||
for info in files {
|
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(())
|
Ok(())
|
||||||
|
|
|
@ -7,20 +7,23 @@ use std::path::{Path, PathBuf};
|
||||||
use anyhow::Result;
|
use anyhow::Result;
|
||||||
use chrono::Local;
|
use chrono::Local;
|
||||||
|
|
||||||
use crate::utils;
|
use crate::utils::{self};
|
||||||
use crate::{TrashDir, TrashInfo};
|
use crate::{TrashDir, TrashInfo};
|
||||||
use crate::{HOME_MOUNT, MOUNTS};
|
use crate::{HOME_MOUNT, MOUNTS};
|
||||||
|
|
||||||
#[derive(Debug, Error)]
|
#[derive(Debug, Error)]
|
||||||
pub enum Error {
|
pub enum Error {
|
||||||
#[error("Refusing to remove directory {0} without '-r' option")]
|
// #[error("Refusing to remove directory {0} without '-r' option")]
|
||||||
MissingRecursiveOption(PathBuf),
|
// MissingRecursiveOption(PathBuf),
|
||||||
|
|
||||||
#[error("Refusing to remove '.' or '..', skipping...")]
|
#[error("Refusing to remove '.' or '..', skipping...")]
|
||||||
CannotTrashDotDirs,
|
CannotTrashDotDirs,
|
||||||
|
|
||||||
#[error("Cancelled by user.")]
|
#[error("Cancelled by user.")]
|
||||||
CancelledByUser,
|
CancelledByUser,
|
||||||
|
|
||||||
|
#[error("Invalid filename.")]
|
||||||
|
InvalidFilename,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Options to pass to put
|
/// Options to pass to put
|
||||||
|
@ -153,19 +156,13 @@ impl DeletionStrategy {
|
||||||
let target = target.as_ref();
|
let target = target.as_ref();
|
||||||
|
|
||||||
// this will be None if target isn't a symlink
|
// this will be None if target isn't a symlink
|
||||||
let link_info = target.read_link().ok();
|
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 (trash_dir, requires_copy) = self.get_target_trash();
|
let (trash_dir, requires_copy) = self.get_target_trash();
|
||||||
|
|
||||||
// prompt if not suppressed
|
// prompt if not suppressed
|
||||||
// TODO: streamline this logic better
|
// TODO: streamline this logic better
|
||||||
if !options.force && (requires_copy || options.prompt) {
|
if !options.force && (requires_copy || options.prompt) {
|
||||||
// TODO: actually handle prompting instead of manually flushing
|
|
||||||
if requires_copy {
|
if requires_copy {
|
||||||
eprint!(
|
eprint!(
|
||||||
"Removing file '{}' requires potentially expensive copying. Continue? [Y/n] ",
|
"Removing file '{}' requires potentially expensive copying. Continue? [Y/n] ",
|
||||||
|
@ -174,6 +171,7 @@ impl DeletionStrategy {
|
||||||
} else if options.prompt {
|
} else if options.prompt {
|
||||||
eprint!("Remove file '{}'? [Y/n] ", target.to_str().unwrap());
|
eprint!("Remove file '{}'? [Y/n] ", target.to_str().unwrap());
|
||||||
}
|
}
|
||||||
|
// TODO: actually handle prompting instead of manually flushing
|
||||||
io::stderr().flush()?;
|
io::stderr().flush()?;
|
||||||
|
|
||||||
let should_continue = loop {
|
let should_continue = loop {
|
||||||
|
@ -196,14 +194,27 @@ impl DeletionStrategy {
|
||||||
// preparing metadata
|
// preparing metadata
|
||||||
let now = Local::now();
|
let now = Local::now();
|
||||||
let elapsed = now.timestamp_millis();
|
let elapsed = now.timestamp_millis();
|
||||||
let file_name = format!(
|
|
||||||
"{}.{}",
|
let elapsed_str = elapsed.to_string();
|
||||||
elapsed,
|
let target_file = match target.file_name() {
|
||||||
target.file_name().unwrap().to_str().unwrap()
|
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_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 {
|
let trash_info = TrashInfo {
|
||||||
path: utils::into_absolute(target)?,
|
path: utils::into_absolute(target)?,
|
||||||
|
|
|
@ -5,6 +5,7 @@ use std::path::PathBuf;
|
||||||
use anyhow::Result;
|
use anyhow::Result;
|
||||||
|
|
||||||
use crate::TrashDir;
|
use crate::TrashDir;
|
||||||
|
use crate::utils;
|
||||||
|
|
||||||
/// Options to pass to restore
|
/// Options to pass to restore
|
||||||
#[derive(StructOpt)]
|
#[derive(StructOpt)]
|
||||||
|
@ -50,7 +51,7 @@ pub fn restore(options: RestoreOptions) -> Result<()> {
|
||||||
"[{}]\t{}\t{}",
|
"[{}]\t{}\t{}",
|
||||||
i,
|
i,
|
||||||
info.deletion_date,
|
info.deletion_date,
|
||||||
info.path.to_str().unwrap()
|
utils::percent_encode(&info.path)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
48
src/utils.rs
48
src/utils.rs
|
@ -1,10 +1,15 @@
|
||||||
use std::env;
|
use std::env;
|
||||||
|
use std::ffi::{OsStr, OsString};
|
||||||
use std::fs;
|
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 anyhow::Result;
|
||||||
|
use percent_encoding::{AsciiSet};
|
||||||
use walkdir::WalkDir;
|
use walkdir::WalkDir;
|
||||||
|
|
||||||
|
const MASK: &AsciiSet = percent_encoding::CONTROLS;
|
||||||
|
|
||||||
pub fn into_absolute(path: impl AsRef<Path>) -> Result<PathBuf> {
|
pub fn into_absolute(path: impl AsRef<Path>) -> Result<PathBuf> {
|
||||||
let path = path.as_ref();
|
let path = path.as_ref();
|
||||||
|
|
||||||
|
@ -49,3 +54,44 @@ pub fn recursive_copy(src: impl AsRef<Path>, dst: impl AsRef<Path>) -> Result<()
|
||||||
|
|
||||||
Ok(())
|
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)
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
Loading…
Add table
Reference in a new issue