Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Try to fix issue #1974 #2262

Open
wants to merge 3 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
134 changes: 134 additions & 0 deletions yazi-core/src/manager/commands/bulk_create.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,134 @@
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, stdin}};
use yazi_config::{OPEN, PREVIEW};
use yazi_dds::Pubsub;
use yazi_fs::{File, FilesOp, ok_or_not_found, realname};
use yazi_proxy::{AppProxy, HIDER, TasksProxy, WATCHER};
use yazi_shared::{event::CmdCow, terminal_clear, url::{Url, UrnBuf}};

use crate::manager::Manager;
struct Opt {

}

impl From<CmdCow> for Opt {
fn from(_c: CmdCow) -> Self { Self { } }
}

impl Manager {
#[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");
};

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<Url>) -> 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(())
}
}
1 change: 1 addition & 0 deletions yazi-core/src/manager/commands/mod.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
yazi_macro::mod_flat!(
bulk_create
bulk_rename
close
create
Expand Down
2 changes: 2 additions & 0 deletions yazi-fm/src/executor.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
12 changes: 12 additions & 0 deletions yazi-fs/src/op.rs
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,18 @@ impl FilesOp {
}
}

pub fn create(files: Vec<File>) {
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<Self>) {
let mut parents: HashMap<_, (HashMap<_, _>, HashSet<_>)> = Default::default();
for op in ops {
Expand Down
Loading