refactor: improve bulk rename performance (#54)

This commit is contained in:
Collide 2023-08-14 22:52:34 +08:00 committed by GitHub
parent ebb06789fc
commit 053652c740
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
2 changed files with 40 additions and 34 deletions

View file

@ -1,9 +1,9 @@
use std::{collections::{BTreeMap, BTreeSet, HashMap, HashSet}, env, ffi::OsStr, io::{stdout, Write}, mem, os::unix::prelude::OsStrExt, path::{Path, PathBuf}}; use std::{collections::{BTreeMap, BTreeSet, HashMap, HashSet}, env, ffi::OsStr, io::{stdout, BufWriter, Write}, mem, os::unix::prelude::OsStrExt, path::{Path, PathBuf}};
use anyhow::{bail, Error, Result}; use anyhow::{anyhow, bail, Error, Result};
use config::{open::Opener, BOOT, OPEN}; use config::{open::Opener, BOOT, OPEN};
use shared::{max_common_root, Defer, Term, MIME_DIR}; use shared::{max_common_root, Defer, Term, MIME_DIR};
use tokio::{fs::{self, OpenOptions}, io::{AsyncReadExt, AsyncWriteExt}}; use tokio::{fs::{self, OpenOptions}, io::{stdin, AsyncReadExt, AsyncWriteExt}};
use super::{PreviewData, Tab, Tabs, Watcher}; use super::{PreviewData, Tab, Tabs, Watcher};
use crate::{emit, external::{self, ShellOpt}, files::{File, FilesOp}, input::InputOpt, manager::Folder, select::SelectOpt, tasks::Tasks, Event, BLOCKER}; use crate::{emit, external::{self, ShellOpt}, files::{File, FilesOp}, input::InputOpt, manager::Folder, select::SelectOpt, tasks::Tasks, Event, BLOCKER};
@ -226,7 +226,7 @@ impl Manager {
let mut old: Vec<_> = self.selected().iter().map(|&f| f.path()).collect(); let mut old: Vec<_> = self.selected().iter().map(|&f| f.path()).collect();
let root = max_common_root(&old); let root = max_common_root(&old);
old = old.into_iter().map(|p| p.strip_prefix(&root).unwrap().to_owned()).collect(); old.iter_mut().for_each(|p| *p = p.strip_prefix(&root).unwrap().to_owned());
let tmp = BOOT.tmpfile("bulk"); let tmp = BOOT.tmpfile("bulk");
tokio::spawn(async move { tokio::spawn(async move {
@ -265,52 +265,56 @@ impl Manager {
Term::clear()?; Term::clear()?;
if old.len() != new.len() { if old.len() != new.len() {
println!("Number of old and new differ, press ENTER to exit"); println!("Number of old and new differ, press ENTER to exit");
tokio::io::stdin().read_exact(&mut [0]).await?; stdin().read_exact(&mut [0]).await?;
return Ok(()); return Ok(());
} }
let mut todo = Vec::with_capacity(old.len()); let todo: Vec<_> = old.into_iter().zip(new).filter(|(o, n)| o != n).collect();
for (o, n) in old.into_iter().zip(new) {
if n != o {
stdout().write_all(o.as_os_str().as_bytes())?;
stdout().write_all(b" -> ")?;
stdout().write_all(n.as_os_str().as_bytes())?;
stdout().write_all(b"\n")?;
todo.push((root.join(o), root.join(n)));
}
}
if todo.is_empty() { if todo.is_empty() {
return Ok(()); return Ok(());
} else {
print!("Continue to rename? (y/N): ");
stdout().flush()?;
} }
let mut buf = [0]; {
tokio::io::stdin().read_exact(&mut buf).await?; let mut stdout = BufWriter::new(stdout().lock());
for (o, n) in &todo {
writeln!(stdout, "{} -> {}", o.display(), n.display())?;
}
write!(stdout, "Continue to rename? (y/N): ")?;
stdout.flush()?;
}
let mut buf = [0; 10];
stdin().read(&mut buf).await.ok();
if buf[0] != b'y' && buf[0] != b'Y' { if buf[0] != b'y' && buf[0] != b'Y' {
return Ok(()); return Ok(());
} }
let mut failed = Vec::new(); let mut failed = Vec::new();
for (o, n) in todo { for (o, n) in todo {
if let Err(e) = fs::rename(&o, &n).await { if fs::metadata(&n).await.is_ok() {
failed.push((o, n, e)); failed.push((o, n, anyhow!("Destination already exists")));
continue;
} }
if let Err(e) = fs::rename(root.join(&o), root.join(&n)).await {
failed.push((o, n, e.into()));
}
}
if failed.is_empty() {
return Ok(());
} }
if !failed.is_empty() {
Term::clear()?; Term::clear()?;
println!("Failed to rename:"); {
let mut stdout = BufWriter::new(stdout().lock());
writeln!(stdout, "Failed to rename:")?;
for (o, n, e) in failed { for (o, n, e) in failed {
stdout().write_all(o.as_os_str().as_bytes())?; writeln!(stdout, "{} -> {}: {e}", o.display(), n.display())?;
stdout().write_all(b" -> ")?;
stdout().write_all(n.as_os_str().as_bytes())?;
stdout().write_fmt(format_args!(": {e}\n"))?;
} }
println!("\nPress ENTER to exit"); writeln!(stdout, "\nPress ENTER to exit")?;
tokio::io::stdin().read_exact(&mut [0]).await?; stdout.flush()?;
} }
stdin().read_exact(&mut [0]).await?;
Ok(()) Ok(())
} }

View file

@ -158,7 +158,7 @@ pub fn max_common_root(files: &[PathBuf]) -> PathBuf {
return PathBuf::new(); return PathBuf::new();
} }
let mut it = files.iter().map(|p| p.components()); let mut it = files.iter().map(|p| p.parent().unwrap_or(Path::new("")).components());
let mut root = it.next().unwrap().collect::<PathBuf>(); let mut root = it.next().unwrap().collect::<PathBuf>();
for components in it { for components in it {
let mut new_root = PathBuf::new(); let mut new_root = PathBuf::new();
@ -177,12 +177,14 @@ pub fn max_common_root(files: &[PathBuf]) -> PathBuf {
fn test_max_common_root() { fn test_max_common_root() {
assert_eq!(max_common_root(&[]).as_os_str(), ""); assert_eq!(max_common_root(&[]).as_os_str(), "");
assert_eq!(max_common_root(&["".into()]).as_os_str(), ""); assert_eq!(max_common_root(&["".into()]).as_os_str(), "");
assert_eq!(max_common_root(&["/a/b".into()]).as_os_str(), "/a/b"); assert_eq!(max_common_root(&["a".into()]).as_os_str(), "");
assert_eq!(max_common_root(&["/a".into()]).as_os_str(), "/");
assert_eq!(max_common_root(&["/a/b".into()]).as_os_str(), "/a");
assert_eq!(max_common_root(&["/a/b/c".into(), "/a/b/d".into()]).as_os_str(), "/a/b"); assert_eq!(max_common_root(&["/a/b/c".into(), "/a/b/d".into()]).as_os_str(), "/a/b");
assert_eq!(max_common_root(&["/aa/bb/cc".into(), "/aa/dd/ee".into()]).as_os_str(), "/aa"); assert_eq!(max_common_root(&["/aa/bb/cc".into(), "/aa/dd/ee".into()]).as_os_str(), "/aa");
assert_eq!( assert_eq!(
max_common_root(&["/aa/bb/cc".into(), "/aa/bb/cc/dd/ee".into(), "/aa/bb/cc/ff".into()]) max_common_root(&["/aa/bb/cc".into(), "/aa/bb/cc/dd/ee".into(), "/aa/bb/cc/ff".into()])
.as_os_str(), .as_os_str(),
"/aa/bb/cc" "/aa/bb"
); );
} }