From f5da79f30214bb2a3ac064fd229515fd0406f6d7 Mon Sep 17 00:00:00 2001 From: evpeople Date: Wed, 29 Jan 2025 20:20:22 +0800 Subject: [PATCH 1/3] feat(fs): add bulk create method --- yazi-core/src/manager/commands/bulk_create.rs | 129 ++++++++++++++++++ yazi-core/src/manager/commands/mod.rs | 1 + yazi-fs/src/op.rs | 11 ++ 3 files changed, 141 insertions(+) create mode 100644 yazi-core/src/manager/commands/bulk_create.rs diff --git a/yazi-core/src/manager/commands/bulk_create.rs b/yazi-core/src/manager/commands/bulk_create.rs new file mode 100644 index 000000000..3d667c604 --- /dev/null +++ b/yazi-core/src/manager/commands/bulk_create.rs @@ -0,0 +1,129 @@ +use std::{borrow::Cow, collections::{HashMap, HashSet}, ffi::{OsStr, OsString}, io::{stderr, BufWriter, Write}, path::PathBuf}; + +use anyhow::{Result, anyhow}; +use scopeguard::defer; +use tokio::{io::{AsyncReadExt, AsyncWriteExt, stdin}}; +use tokio::fs; +use yazi_config::{OPEN, PREVIEW}; +use yazi_dds::Pubsub; +use yazi_fs::{max_common_root, maybe_exists, ok_or_not_found, paths_to_same_file, realname, File, FilesOp}; +use yazi_proxy::{AppProxy, HIDER, TasksProxy, WATCHER}; +use yazi_shared::{terminal_clear, url::{Url, UrnBuf}}; + +use crate::manager:: Manager; + +impl Manager { + pub(super) fn bulk_create(&self) { + let Some(opener) = OPEN.block_opener("bulk-create.txt", "text/plain") else { + return AppProxy::notify_warn("Bulk create", "No text opener found"); + }; + + let cwd = self.cwd().clone(); + tokio::spawn(async move { + let tmp = PREVIEW.tmpfile("bulk"); + defer! { tokio::spawn(fs::remove_file(tmp.clone())); } + TasksProxy::process_exec(Cow::Borrowed(opener), cwd, vec![ + OsString::new(), + tmp.to_owned().into(), + ]) + .await; + + let _permit = HIDER.acquire().await.unwrap(); + defer!(AppProxy::resume()); + AppProxy::stop().await; + + let new: Vec<_> = fs::read_to_string(&tmp).await?.lines().map(Url::from).collect(); + Self::bulk_create_do( new).await + }); + } + + async fn bulk_create_do(new:Vec) -> Result<()> { + terminal_clear(&mut stderr())?; + if new.is_empty() { + return Ok(()); + } + + { + let mut stderr = BufWriter::new(stderr().lock()); + for n in &new { + if n.to_str().unwrap().ends_with('/') || n.to_str().unwrap().ends_with('\\') { + writeln!(stderr, "create new dir-> {}", n.display())?; + }else{ + writeln!(stderr, "create new file-> {}", n.display())?; + } + } + write!(stderr, "Continue to create? (y/N): ")?; + stderr.flush()?; + } + + let mut buf = [0; 10]; + _ = stdin().read(&mut buf).await?; + if buf[0] != b'y' && buf[0] != b'Y' { + return Ok(()); + } + + let permit = WATCHER.acquire().await.unwrap(); + let (mut failed, mut succeeded) = (Vec::new(), HashMap::with_capacity(new.len())); + for n in new { + let Some(parent) = n.parent_url() else { return Ok(()) }; + let dir = n.to_str().unwrap().ends_with('/') || n.to_str().unwrap().ends_with('\\'); + + if dir { + if let Err(e) = fs::create_dir_all(&n).await { + failed.push((PathBuf::new(), n.into(), e.into())); + } else if let Ok(f) = File::from(n.clone()).await { + succeeded.insert(n.clone(), f); + } else { + failed.push((PathBuf::new(), n, anyhow!("Failed to retrieve file info"))); + } + } else { + fs::create_dir_all(&parent).await.ok(); + if let Some(real) = realname(&n).await { + if let Err(e) = ok_or_not_found(fs::remove_file(&n).await) { + failed.push((PathBuf::new(), n.into(), e.into())); + continue; + } + FilesOp::Deleting(parent.clone(), HashSet::from_iter([UrnBuf::from(real)])).emit(); + } + + if let Err(e) = fs::File::create(&n).await { + failed.push((PathBuf::new(), n.into(), e.into())); + } else if let Ok(f) = File::from(n.clone()).await { + succeeded.insert(n.clone(), f); + } else { + failed.push((PathBuf::new(), n.into(), anyhow!("Failed to retrieve file info"))); + } + } + } + + + + if !succeeded.is_empty() { + Pubsub::pub_from_bulk(succeeded.iter().map(|(o, n)| (o, &n.url)).collect()); + FilesOp::create(succeeded.into_values().collect()); + } + drop(permit); + + if !failed.is_empty() { + Self::output_failed_for_bulk_create(failed).await?; + } + Ok(()) + } + + async fn output_failed_for_bulk_create(failed: Vec<(PathBuf, Url, anyhow::Error)>) -> Result<()> { + terminal_clear(&mut stderr())?; + + { + let mut stderr = BufWriter::new(stderr().lock()); + writeln!(stderr, "Failed to create:")?; + for (o, n, e) in failed { + writeln!(stderr, "{} -> {}: {e}", o.display(), n.display())?; + } + writeln!(stderr, "\nPress ENTER to exit")?; + stderr.flush()?; + } + + stdin().read_exact(&mut [0]).await?; + Ok(()) + } +} diff --git a/yazi-core/src/manager/commands/mod.rs b/yazi-core/src/manager/commands/mod.rs index c184cbe97..6f97e5340 100644 --- a/yazi-core/src/manager/commands/mod.rs +++ b/yazi-core/src/manager/commands/mod.rs @@ -1,4 +1,5 @@ yazi_macro::mod_flat!( + bulk_create bulk_rename close create diff --git a/yazi-fs/src/op.rs b/yazi-fs/src/op.rs index 2e142fedf..db141322e 100644 --- a/yazi-fs/src/op.rs +++ b/yazi-fs/src/op.rs @@ -74,6 +74,17 @@ impl FilesOp { } } } + pub fn create(files: Vec) { + let mut parents: HashMap<_, Vec<_>> = Default::default(); + for file in files { + let Some(parent) = file.url.parent_url() else { continue }; + parents.entry(parent).or_default().push(file); + } + + for (parent, files) in parents { + Self::Creating(parent, files).emit(); + } + } pub fn mutate(ops: Vec) { let mut parents: HashMap<_, (HashMap<_, _>, HashSet<_>)> = Default::default(); From a2ea22a5f26e72f10c16036fda773ed62bd3a8af Mon Sep 17 00:00:00 2001 From: evpeople Date: Wed, 29 Jan 2025 20:24:45 +0800 Subject: [PATCH 2/3] fmt --- yazi-core/src/manager/commands/bulk_create.rs | 83 +++++++++---------- yazi-fs/src/op.rs | 21 ++--- 2 files changed, 51 insertions(+), 53 deletions(-) diff --git a/yazi-core/src/manager/commands/bulk_create.rs b/yazi-core/src/manager/commands/bulk_create.rs index 3d667c604..8580a3e91 100644 --- a/yazi-core/src/manager/commands/bulk_create.rs +++ b/yazi-core/src/manager/commands/bulk_create.rs @@ -1,16 +1,15 @@ -use std::{borrow::Cow, collections::{HashMap, HashSet}, ffi::{OsStr, OsString}, io::{stderr, BufWriter, Write}, path::PathBuf}; +use std::{borrow::Cow, collections::{HashMap, HashSet}, ffi::{OsStr, OsString}, io::{BufWriter, Write, stderr}, path::PathBuf}; use anyhow::{Result, anyhow}; use scopeguard::defer; -use tokio::{io::{AsyncReadExt, AsyncWriteExt, stdin}}; -use tokio::fs; +use tokio::{fs, io::{AsyncReadExt, AsyncWriteExt, stdin}}; use yazi_config::{OPEN, PREVIEW}; use yazi_dds::Pubsub; -use yazi_fs::{max_common_root, maybe_exists, ok_or_not_found, paths_to_same_file, realname, File, FilesOp}; +use yazi_fs::{File, FilesOp, max_common_root, maybe_exists, ok_or_not_found, paths_to_same_file, realname}; use yazi_proxy::{AppProxy, HIDER, TasksProxy, WATCHER}; use yazi_shared::{terminal_clear, url::{Url, UrnBuf}}; -use crate::manager:: Manager; +use crate::manager::Manager; impl Manager { pub(super) fn bulk_create(&self) { @@ -32,12 +31,12 @@ impl Manager { defer!(AppProxy::resume()); AppProxy::stop().await; - let new: Vec<_> = fs::read_to_string(&tmp).await?.lines().map(Url::from).collect(); - Self::bulk_create_do( new).await + let new: Vec<_> = fs::read_to_string(&tmp).await?.lines().map(Url::from).collect(); + Self::bulk_create_do(new).await }); } - async fn bulk_create_do(new:Vec) -> Result<()> { + async fn bulk_create_do(new: Vec) -> Result<()> { terminal_clear(&mut stderr())?; if new.is_empty() { return Ok(()); @@ -48,7 +47,7 @@ impl Manager { for n in &new { if n.to_str().unwrap().ends_with('/') || n.to_str().unwrap().ends_with('\\') { writeln!(stderr, "create new dir-> {}", n.display())?; - }else{ + } else { writeln!(stderr, "create new file-> {}", n.display())?; } } @@ -64,43 +63,41 @@ impl Manager { let permit = WATCHER.acquire().await.unwrap(); let (mut failed, mut succeeded) = (Vec::new(), HashMap::with_capacity(new.len())); - for n in new { - let Some(parent) = n.parent_url() else { return Ok(()) }; - let dir = n.to_str().unwrap().ends_with('/') || n.to_str().unwrap().ends_with('\\'); - - if dir { - if let Err(e) = fs::create_dir_all(&n).await { - failed.push((PathBuf::new(), n.into(), e.into())); - } else if let Ok(f) = File::from(n.clone()).await { - succeeded.insert(n.clone(), f); - } else { - failed.push((PathBuf::new(), n, anyhow!("Failed to retrieve file info"))); - } - } else { - fs::create_dir_all(&parent).await.ok(); - if let Some(real) = realname(&n).await { - if let Err(e) = ok_or_not_found(fs::remove_file(&n).await) { - failed.push((PathBuf::new(), n.into(), e.into())); - continue; - } - FilesOp::Deleting(parent.clone(), HashSet::from_iter([UrnBuf::from(real)])).emit(); - } - - if let Err(e) = fs::File::create(&n).await { - failed.push((PathBuf::new(), n.into(), e.into())); - } else if let Ok(f) = File::from(n.clone()).await { - succeeded.insert(n.clone(), f); - } else { - failed.push((PathBuf::new(), n.into(), anyhow!("Failed to retrieve file info"))); - } - } - } - - + for n in new { + let Some(parent) = n.parent_url() else { return Ok(()) }; + let dir = n.to_str().unwrap().ends_with('/') || n.to_str().unwrap().ends_with('\\'); + + if dir { + if let Err(e) = fs::create_dir_all(&n).await { + failed.push((PathBuf::new(), n.into(), e.into())); + } else if let Ok(f) = File::from(n.clone()).await { + succeeded.insert(n.clone(), f); + } else { + failed.push((PathBuf::new(), n, anyhow!("Failed to retrieve file info"))); + } + } else { + fs::create_dir_all(&parent).await.ok(); + if let Some(real) = realname(&n).await { + if let Err(e) = ok_or_not_found(fs::remove_file(&n).await) { + failed.push((PathBuf::new(), n.into(), e.into())); + continue; + } + FilesOp::Deleting(parent.clone(), HashSet::from_iter([UrnBuf::from(real)])).emit(); + } + + if let Err(e) = fs::File::create(&n).await { + failed.push((PathBuf::new(), n.into(), e.into())); + } else if let Ok(f) = File::from(n.clone()).await { + succeeded.insert(n.clone(), f); + } else { + failed.push((PathBuf::new(), n.into(), anyhow!("Failed to retrieve file info"))); + } + } + } if !succeeded.is_empty() { Pubsub::pub_from_bulk(succeeded.iter().map(|(o, n)| (o, &n.url)).collect()); - FilesOp::create(succeeded.into_values().collect()); + FilesOp::create(succeeded.into_values().collect()); } drop(permit); diff --git a/yazi-fs/src/op.rs b/yazi-fs/src/op.rs index db141322e..1f690ee35 100644 --- a/yazi-fs/src/op.rs +++ b/yazi-fs/src/op.rs @@ -74,17 +74,18 @@ impl FilesOp { } } } + pub fn create(files: Vec) { - let mut parents: HashMap<_, Vec<_>> = Default::default(); - for file in files { - let Some(parent) = file.url.parent_url() else { continue }; - parents.entry(parent).or_default().push(file); - } - - for (parent, files) in parents { - Self::Creating(parent, files).emit(); - } - } + let mut parents: HashMap<_, Vec<_>> = Default::default(); + for file in files { + let Some(parent) = file.url.parent_url() else { continue }; + parents.entry(parent).or_default().push(file); + } + + for (parent, files) in parents { + Self::Creating(parent, files).emit(); + } + } pub fn mutate(ops: Vec) { let mut parents: HashMap<_, (HashMap<_, _>, HashSet<_>)> = Default::default(); From 275678912bff1c3116564de5f73f9bd204213d6a Mon Sep 17 00:00:00 2001 From: evpeople Date: Wed, 29 Jan 2025 20:58:43 +0800 Subject: [PATCH 3/3] a try --- yazi-core/src/manager/commands/bulk_create.rs | 18 +++++++++++++----- yazi-fm/src/executor.rs | 2 ++ 2 files changed, 15 insertions(+), 5 deletions(-) diff --git a/yazi-core/src/manager/commands/bulk_create.rs b/yazi-core/src/manager/commands/bulk_create.rs index 8580a3e91..d019bfffd 100644 --- a/yazi-core/src/manager/commands/bulk_create.rs +++ b/yazi-core/src/manager/commands/bulk_create.rs @@ -1,18 +1,26 @@ -use std::{borrow::Cow, collections::{HashMap, HashSet}, ffi::{OsStr, OsString}, io::{BufWriter, Write, stderr}, path::PathBuf}; +use std::{borrow::Cow, collections::{HashMap, HashSet}, ffi::OsString, io::{BufWriter, Write, stderr}, path::PathBuf}; use anyhow::{Result, anyhow}; use scopeguard::defer; -use tokio::{fs, io::{AsyncReadExt, AsyncWriteExt, stdin}}; +use tokio::{fs, io::{AsyncReadExt, stdin}}; use yazi_config::{OPEN, PREVIEW}; use yazi_dds::Pubsub; -use yazi_fs::{File, FilesOp, max_common_root, maybe_exists, ok_or_not_found, paths_to_same_file, realname}; +use yazi_fs::{File, FilesOp, ok_or_not_found, realname}; use yazi_proxy::{AppProxy, HIDER, TasksProxy, WATCHER}; -use yazi_shared::{terminal_clear, url::{Url, UrnBuf}}; +use yazi_shared::{event::CmdCow, terminal_clear, url::{Url, UrnBuf}}; use crate::manager::Manager; +struct Opt { + +} + +impl From for Opt { + fn from(_c: CmdCow) -> Self { Self { } } +} impl Manager { - pub(super) fn bulk_create(&self) { + #[yazi_codegen::command] + pub fn bulk_create(&self,_opt: Opt) { let Some(opener) = OPEN.block_opener("bulk-create.txt", "text/plain") else { return AppProxy::notify_warn("Bulk create", "No text opener found"); }; diff --git a/yazi-fm/src/executor.rs b/yazi-fm/src/executor.rs index 769a44093..d6abfb5bc 100644 --- a/yazi-fm/src/executor.rs +++ b/yazi-fm/src/executor.rs @@ -114,6 +114,8 @@ impl<'a> Executor<'a> { on!(MANAGER, remove, &self.app.cx.tasks); on!(MANAGER, remove_do, &self.app.cx.tasks); on!(MANAGER, create); + on!(MANAGER, bulk_create); + on!(MANAGER, rename); on!(ACTIVE, copy); on!(ACTIVE, shell);