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)",
|
||||
"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)",
|
||||
|
@ -156,6 +157,11 @@ dependencies = [
|
|||
"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]]
|
||||
name = "proc-macro-error"
|
||||
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 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"
|
||||
|
|
|
@ -25,3 +25,4 @@ structopt = "0.3"
|
|||
thiserror = "1.0"
|
||||
walkdir = "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::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))
|
||||
}
|
||||
|
|
13
src/info.rs
13
src/info.rs
|
@ -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={}",
|
||||
|
|
|
@ -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;
|
||||
|
||||
|
|
|
@ -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(())
|
||||
|
|
|
@ -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)?,
|
||||
|
|
|
@ -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)
|
||||
);
|
||||
}
|
||||
|
||||
|
|
48
src/utils.rs
48
src/utils.rs
|
@ -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)
|
||||
}
|
||||
};
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue