From 6e3c96289e7f1b5f5657f7a41770177ab8374080 Mon Sep 17 00:00:00 2001 From: sxyazi Date: Wed, 24 Dec 2025 01:15:18 +0800 Subject: [PATCH] feat: make task post-hooks stateful (#3454) --- yazi-actor/src/lives/task.rs | 1 + yazi-fs/src/provider/traits.rs | 6 +- yazi-plugin/preset/components/tasks.lua | 10 +-- yazi-scheduler/src/file/out.rs | 26 ++++-- yazi-scheduler/src/file/progress.rs | 104 ++++++++++++++---------- yazi-scheduler/src/hook/hook.rs | 27 +++--- yazi-scheduler/src/plugin/progress.rs | 6 +- yazi-scheduler/src/prework/progress.rs | 18 ++-- yazi-scheduler/src/process/progress.rs | 18 ++-- yazi-scheduler/src/progress.rs | 26 +++++- yazi-scheduler/src/scheduler.rs | 6 +- yazi-vfs/src/provider/provider.rs | 2 +- 12 files changed, 165 insertions(+), 85 deletions(-) diff --git a/yazi-actor/src/lives/task.rs b/yazi-actor/src/lives/task.rs index b646c3b3..7d63b7b5 100644 --- a/yazi-actor/src/lives/task.rs +++ b/yazi-actor/src/lives/task.rs @@ -29,6 +29,7 @@ impl UserData for TaskSnap { cached_field!(fields, name, |lua, me| lua.create_string(&me.name)); cached_field!(fields, prog, |lua, me| lua.to_value_with(&me.prog, SER_OPT)); + fields.add_field_method_get("cooked", |_, me| Ok(me.prog.cooked())); fields.add_field_method_get("running", |_, me| Ok(me.prog.running())); fields.add_field_method_get("success", |_, me| Ok(me.prog.success())); fields.add_field_method_get("failed", |_, me| Ok(me.prog.failed())); diff --git a/yazi-fs/src/provider/traits.rs b/yazi-fs/src/provider/traits.rs index 9cb591c4..c8739819 100644 --- a/yazi-fs/src/provider/traits.rs +++ b/yazi-fs/src/provider/traits.rs @@ -126,11 +126,12 @@ pub trait Provider: Sized { } } - fn remove_dir_clean(&self) -> impl Future { + fn remove_dir_clean(&self) -> impl Future> { let root = self.url().to_owned(); async move { let mut stack = vec![(root, false)]; + let mut result = Ok(()); while let Some((dir, visited)) = stack.pop() { let Ok(provider) = Self::new(dir.as_url()).await else { @@ -138,7 +139,7 @@ pub trait Provider: Sized { }; if visited { - provider.remove_dir().await.ok(); + result = provider.remove_dir().await; } else if let Ok(mut it) = provider.read_dir().await { stack.push((dir, true)); while let Ok(Some(ent)) = it.next().await { @@ -148,6 +149,7 @@ pub trait Provider: Sized { } } } + result } } diff --git a/yazi-plugin/preset/components/tasks.lua b/yazi-plugin/preset/components/tasks.lua index 11b5ec4e..f52891a5 100644 --- a/yazi-plugin/preset/components/tasks.lua +++ b/yazi-plugin/preset/components/tasks.lua @@ -87,7 +87,7 @@ function Tasks:progress_redraw(snap, y) or kind == "FileUpload" then local percent - if snap.success then + if snap.cooked then percent = "Cleaning…" else percent = string.format("%3d%%", math.floor(snap.percent)) @@ -101,7 +101,7 @@ function Tasks:progress_redraw(snap, y) ) local style = th.status.progress_normal - if snap.prog.failed_files > 0 then + if snap.failed or snap.prog.failed_files > 0 then style = th.status.progress_error end @@ -119,10 +119,10 @@ function Tasks:progress_redraw(snap, y) } else local text - if snap.running then - text = "Running…" - elseif snap.success then + if snap.cooked then text = "Cleaning…" + elseif snap.running then + text = "Running…" else text = "Failed, press Enter to view log…" end diff --git a/yazi-scheduler/src/file/out.rs b/yazi-scheduler/src/file/out.rs index 41273ca9..2ebca310 100644 --- a/yazi-scheduler/src/file/out.rs +++ b/yazi-scheduler/src/file/out.rs @@ -1,3 +1,5 @@ +use std::io; + use crate::{Task, TaskProg}; // --- Copy @@ -82,7 +84,7 @@ pub(crate) enum FileOutCut { Deform(String), Succ, Fail(String), - Clean, + Clean(io::Result<()>), } impl From for FileOutCut { @@ -113,8 +115,12 @@ impl FileOutCut { prog.collected = Some(false); task.log(reason); } - Self::Clean => { - prog.cleaned = true; + Self::Clean(Ok(())) => { + prog.cleaned = Some(true); + } + Self::Clean(Err(reason)) => { + prog.cleaned = Some(false); + task.log(format!("Failed cleaning up cut file: {reason:?}")); } } } @@ -273,7 +279,7 @@ pub(crate) enum FileOutDelete { New(u64), Succ, Fail(String), - Clean, + Clean(io::Result<()>), } impl From for FileOutDelete { @@ -295,8 +301,12 @@ impl FileOutDelete { prog.collected = Some(false); task.log(reason); } - Self::Clean => { - prog.cleaned = true; + Self::Clean(Ok(())) => { + prog.cleaned = Some(true); + } + Self::Clean(Err(reason)) => { + prog.cleaned = Some(false); + task.log(format!("Failed cleaning up deleted file: {reason:?}")); } } } @@ -353,7 +363,7 @@ impl FileOutTrash { task.log(reason); } Self::Clean => { - prog.cleaned = true; + prog.cleaned = Some(true); } } } @@ -394,7 +404,7 @@ impl FileOutDownload { task.log(reason); } Self::Clean => { - prog.cleaned = true; + prog.cleaned = Some(true); } } } diff --git a/yazi-scheduler/src/file/progress.rs b/yazi-scheduler/src/file/progress.rs index 0950d8c3..53640d42 100644 --- a/yazi-scheduler/src/file/progress.rs +++ b/yazi-scheduler/src/file/progress.rs @@ -24,17 +24,19 @@ impl From for TaskSummary { } impl FileProgCopy { + pub fn cooked(self) -> bool { + self.collected == Some(true) && self.success_files == self.total_files + } + pub fn running(self) -> bool { self.collected.is_none() || self.success_files + self.failed_files != self.total_files } - pub fn success(self) -> bool { - self.collected == Some(true) && self.success_files == self.total_files - } + pub fn success(self) -> bool { self.cooked() } pub fn failed(self) -> bool { self.collected == Some(false) } - pub fn cleaned(self) -> bool { false } + pub fn cleaned(self) -> Option { None } pub fn percent(self) -> Option { Some(if self.success() { @@ -58,7 +60,7 @@ pub struct FileProgCut { pub total_bytes: u64, pub processed_bytes: u64, pub collected: Option, - pub cleaned: bool, + pub cleaned: Option, } impl From for TaskSummary { @@ -73,17 +75,21 @@ impl From for TaskSummary { } impl FileProgCut { - pub fn running(self) -> bool { - self.collected.is_none() || self.success_files + self.failed_files != self.total_files - } - - pub fn success(self) -> bool { + pub fn cooked(self) -> bool { self.collected == Some(true) && self.success_files == self.total_files } - pub fn failed(self) -> bool { self.collected == Some(false) } + pub fn running(self) -> bool { + self.collected.is_none() + || self.success_files + self.failed_files != self.total_files + || (self.cleaned.is_none() && self.cooked()) + } - pub fn cleaned(self) -> bool { self.cleaned } + pub fn success(self) -> bool { self.cleaned == Some(true) && self.cooked() } + + pub fn failed(self) -> bool { self.cleaned == Some(false) || self.collected == Some(false) } + + pub fn cleaned(self) -> Option { self.cleaned } pub fn percent(self) -> Option { Some(if self.success() { @@ -116,13 +122,15 @@ impl From for TaskSummary { } impl FileProgLink { + pub fn cooked(self) -> bool { self.state == Some(true) } + pub fn running(self) -> bool { self.state.is_none() } - pub fn success(self) -> bool { self.state == Some(true) } + pub fn success(self) -> bool { self.cooked() } pub fn failed(self) -> bool { self.state == Some(false) } - pub fn cleaned(self) -> bool { false } + pub fn cleaned(self) -> Option { None } pub fn percent(self) -> Option { None } } @@ -148,15 +156,17 @@ impl From for TaskSummary { } impl FileProgHardlink { + pub fn cooked(self) -> bool { self.collected == Some(true) && self.success == self.total } + pub fn running(self) -> bool { self.collected.is_none() || self.success + self.failed != self.total } - pub fn success(self) -> bool { self.collected == Some(true) && self.success == self.total } + pub fn success(self) -> bool { self.cooked() } pub fn failed(self) -> bool { self.collected == Some(false) } - pub fn cleaned(self) -> bool { false } + pub fn cleaned(self) -> Option { None } pub fn percent(self) -> Option { None } } @@ -170,7 +180,7 @@ pub struct FileProgDelete { pub total_bytes: u64, pub processed_bytes: u64, pub collected: Option, - pub cleaned: bool, + pub cleaned: Option, } impl From for TaskSummary { @@ -185,17 +195,21 @@ impl From for TaskSummary { } impl FileProgDelete { - pub fn running(self) -> bool { - self.collected.is_none() || self.success_files + self.failed_files != self.total_files - } - - pub fn success(self) -> bool { + pub fn cooked(self) -> bool { self.collected == Some(true) && self.success_files == self.total_files } - pub fn failed(self) -> bool { self.collected == Some(false) } + pub fn running(self) -> bool { + self.collected.is_none() + || self.success_files + self.failed_files != self.total_files + || (self.cleaned.is_none() && self.cooked()) + } - pub fn cleaned(self) -> bool { self.cleaned } + pub fn success(self) -> bool { self.cleaned == Some(true) && self.cooked() } + + pub fn failed(self) -> bool { self.cleaned == Some(false) || self.collected == Some(false) } + + pub fn cleaned(self) -> Option { self.cleaned } pub fn percent(self) -> Option { Some(if self.success() { @@ -214,7 +228,7 @@ impl FileProgDelete { #[derive(Clone, Copy, Debug, Default, Eq, PartialEq, Serialize)] pub struct FileProgTrash { pub state: Option, - pub cleaned: bool, + pub cleaned: Option, } impl From for TaskSummary { @@ -229,13 +243,15 @@ impl From for TaskSummary { } impl FileProgTrash { - pub fn running(self) -> bool { self.state.is_none() } + pub fn cooked(self) -> bool { self.state == Some(true) } - pub fn success(self) -> bool { self.state == Some(true) } + pub fn running(self) -> bool { self.state.is_none() || (self.cleaned.is_none() && self.cooked()) } - pub fn failed(self) -> bool { self.state == Some(false) } + pub fn success(self) -> bool { self.cleaned == Some(true) && self.cooked() } - pub fn cleaned(self) -> bool { self.cleaned } + pub fn failed(self) -> bool { self.cleaned == Some(false) || self.state == Some(false) } + + pub fn cleaned(self) -> Option { self.cleaned } pub fn percent(self) -> Option { None } } @@ -249,7 +265,7 @@ pub struct FileProgDownload { pub total_bytes: u64, pub processed_bytes: u64, pub collected: Option, - pub cleaned: bool, + pub cleaned: Option, } impl From for TaskSummary { @@ -264,17 +280,21 @@ impl From for TaskSummary { } impl FileProgDownload { - pub fn running(self) -> bool { - self.collected.is_none() || self.success_files + self.failed_files != self.total_files - } - - pub fn success(self) -> bool { + pub fn cooked(self) -> bool { self.collected == Some(true) && self.success_files == self.total_files } - pub fn failed(self) -> bool { self.collected == Some(false) } + pub fn running(self) -> bool { + self.collected.is_none() + || self.success_files + self.failed_files != self.total_files + || (self.cleaned.is_none() && self.cooked()) + } - pub fn cleaned(self) -> bool { self.cleaned } + pub fn success(self) -> bool { self.cleaned == Some(true) && self.cooked() } + + pub fn failed(self) -> bool { self.cleaned == Some(false) || self.collected == Some(false) } + + pub fn cleaned(self) -> Option { self.cleaned } pub fn percent(self) -> Option { Some(if self.success() { @@ -312,17 +332,19 @@ impl From for TaskSummary { } impl FileProgUpload { + pub fn cooked(self) -> bool { + self.collected == Some(true) && self.success_files == self.total_files + } + pub fn running(self) -> bool { self.collected.is_none() || self.success_files + self.failed_files != self.total_files } - pub fn success(self) -> bool { - self.collected == Some(true) && self.success_files == self.total_files - } + pub fn success(self) -> bool { self.cooked() } pub fn failed(self) -> bool { self.collected == Some(false) } - pub fn cleaned(self) -> bool { false } + pub fn cleaned(self) -> Option { None } pub fn percent(self) -> Option { Some(if self.success() { diff --git a/yazi-scheduler/src/hook/hook.rs b/yazi-scheduler/src/hook/hook.rs index f67e941e..c1439700 100644 --- a/yazi-scheduler/src/hook/hook.rs +++ b/yazi-scheduler/src/hook/hook.rs @@ -3,6 +3,7 @@ use std::sync::Arc; use parking_lot::Mutex; use tokio::sync::mpsc; use yazi_dds::Pump; +use yazi_fs::ok_or_not_found; use yazi_proxy::TasksProxy; use yazi_vfs::provider; @@ -20,22 +21,26 @@ impl Hook { // --- File pub(crate) async fn cut(&self, task: HookInOutCut) { - let intact = self.ongoing.lock().intact(task.id); - if intact { - provider::remove_dir_clean(&task.from).await.ok(); - Pump::push_move(&task.from, &task.to); + if !self.ongoing.lock().intact(task.id) { + return self.ops.out(task.id, FileOutCut::Clean(Ok(()))); } - self.ops.out(task.id, FileOutCut::Clean); + + let result = ok_or_not_found(provider::remove_dir_clean(&task.from).await); + Pump::push_move(&task.from, &task.to); + + self.ops.out(task.id, FileOutCut::Clean(result)); } pub(crate) async fn delete(&self, task: HookInOutDelete) { - let intact = self.ongoing.lock().intact(task.id); - if intact { - provider::remove_dir_all(&task.target).await.ok(); - TasksProxy::update_succeed(&task.target); - Pump::push_delete(&task.target); + if !self.ongoing.lock().intact(task.id) { + return self.ops.out(task.id, FileOutDelete::Clean(Ok(()))); } - self.ops.out(task.id, FileOutDelete::Clean); + + let result = ok_or_not_found(provider::remove_dir_all(&task.target).await); + TasksProxy::update_succeed(&task.target); + Pump::push_delete(&task.target); + + self.ops.out(task.id, FileOutDelete::Clean(result)); } pub(crate) async fn trash(&self, task: HookInOutTrash) { diff --git a/yazi-scheduler/src/plugin/progress.rs b/yazi-scheduler/src/plugin/progress.rs index d6255c5a..b629836a 100644 --- a/yazi-scheduler/src/plugin/progress.rs +++ b/yazi-scheduler/src/plugin/progress.rs @@ -19,13 +19,15 @@ impl From for TaskSummary { } impl PluginProgEntry { + pub fn cooked(self) -> bool { self.state == Some(true) } + pub fn running(self) -> bool { self.state.is_none() } - pub fn success(self) -> bool { self.state == Some(true) } + pub fn success(self) -> bool { self.cooked() } pub fn failed(self) -> bool { self.state == Some(false) } - pub fn cleaned(self) -> bool { false } + pub fn cleaned(self) -> Option { None } pub fn percent(self) -> Option { None } } diff --git a/yazi-scheduler/src/prework/progress.rs b/yazi-scheduler/src/prework/progress.rs index 9174ef82..f66bde2e 100644 --- a/yazi-scheduler/src/prework/progress.rs +++ b/yazi-scheduler/src/prework/progress.rs @@ -19,13 +19,15 @@ impl From for TaskSummary { } impl PreworkProgFetch { + pub fn cooked(self) -> bool { self.state == Some(true) } + pub fn running(self) -> bool { self.state.is_none() } - pub fn success(self) -> bool { self.state == Some(true) } + pub fn success(self) -> bool { self.cooked() } pub fn failed(self) -> bool { self.state == Some(false) } - pub fn cleaned(self) -> bool { false } + pub fn cleaned(self) -> Option { None } pub fn percent(self) -> Option { None } } @@ -48,13 +50,15 @@ impl From for TaskSummary { } impl PreworkProgLoad { + pub fn cooked(self) -> bool { self.state == Some(true) } + pub fn running(self) -> bool { self.state.is_none() } - pub fn success(self) -> bool { self.state == Some(true) } + pub fn success(self) -> bool { self.cooked() } pub fn failed(self) -> bool { self.state == Some(false) } - pub fn cleaned(self) -> bool { false } + pub fn cleaned(self) -> Option { None } pub fn percent(self) -> Option { None } } @@ -77,13 +81,15 @@ impl From for TaskSummary { } impl PreworkProgSize { + pub fn cooked(self) -> bool { self.done } + pub fn running(self) -> bool { !self.done } - pub fn success(self) -> bool { self.done } + pub fn success(self) -> bool { self.cooked() } pub fn failed(self) -> bool { false } - pub fn cleaned(self) -> bool { false } + pub fn cleaned(self) -> Option { None } pub fn percent(self) -> Option { None } } diff --git a/yazi-scheduler/src/process/progress.rs b/yazi-scheduler/src/process/progress.rs index 9cc212bb..9dd27dd4 100644 --- a/yazi-scheduler/src/process/progress.rs +++ b/yazi-scheduler/src/process/progress.rs @@ -19,13 +19,15 @@ impl From for TaskSummary { } impl ProcessProgBlock { + pub fn cooked(self) -> bool { self.state == Some(true) } + pub fn running(self) -> bool { self.state.is_none() } - pub fn success(self) -> bool { self.state == Some(true) } + pub fn success(self) -> bool { self.cooked() } pub fn failed(self) -> bool { self.state == Some(false) } - pub fn cleaned(self) -> bool { false } + pub fn cleaned(self) -> Option { None } pub fn percent(self) -> Option { None } } @@ -48,13 +50,15 @@ impl From for TaskSummary { } impl ProcessProgOrphan { + pub fn cooked(self) -> bool { self.state == Some(true) } + pub fn running(self) -> bool { self.state.is_none() } - pub fn success(self) -> bool { self.state == Some(true) } + pub fn success(self) -> bool { self.cooked() } pub fn failed(self) -> bool { self.state == Some(false) } - pub fn cleaned(self) -> bool { false } + pub fn cleaned(self) -> Option { None } pub fn percent(self) -> Option { None } } @@ -77,13 +81,15 @@ impl From for TaskSummary { } impl ProcessProgBg { + pub fn cooked(self) -> bool { self.state == Some(true) } + pub fn running(self) -> bool { self.state.is_none() } - pub fn success(self) -> bool { self.state == Some(true) } + pub fn success(self) -> bool { self.cooked() } pub fn failed(self) -> bool { self.state == Some(false) } - pub fn cleaned(self) -> bool { false } + pub fn cleaned(self) -> Option { None } pub fn percent(self) -> Option { None } } diff --git a/yazi-scheduler/src/progress.rs b/yazi-scheduler/src/progress.rs index 05ade88f..68c9efaf 100644 --- a/yazi-scheduler/src/progress.rs +++ b/yazi-scheduler/src/progress.rs @@ -65,6 +65,30 @@ impl From for TaskSummary { } impl TaskProg { + pub fn cooked(self) -> bool { + match self { + // File + Self::FileCopy(p) => p.cooked(), + Self::FileCut(p) => p.cooked(), + Self::FileLink(p) => p.cooked(), + Self::FileHardlink(p) => p.cooked(), + Self::FileDelete(p) => p.cooked(), + Self::FileTrash(p) => p.cooked(), + Self::FileDownload(p) => p.cooked(), + Self::FileUpload(p) => p.cooked(), + // Plugin + Self::PluginEntry(p) => p.cooked(), + // Prework + Self::PreworkFetch(p) => p.cooked(), + Self::PreworkLoad(p) => p.cooked(), + Self::PreworkSize(p) => p.cooked(), + // Process + Self::ProcessBlock(p) => p.cooked(), + Self::ProcessOrphan(p) => p.cooked(), + Self::ProcessBg(p) => p.cooked(), + } + } + pub fn running(self) -> bool { match self { // File @@ -137,7 +161,7 @@ impl TaskProg { } } - pub fn cleaned(self) -> bool { + pub fn cleaned(self) -> Option { match self { // File Self::FileCopy(p) => p.cleaned(), diff --git a/yazi-scheduler/src/scheduler.rs b/yazi-scheduler/src/scheduler.rs index da245321..a8ce3b12 100644 --- a/yazi-scheduler/src/scheduler.rs +++ b/yazi-scheduler/src/scheduler.rs @@ -392,8 +392,10 @@ impl Scheduler { let Some(task) = ongoing.get_mut(op.id) else { continue }; op.out.reduce(task); - if !task.prog.success() && !task.prog.cleaned() { - continue; + if !task.prog.cooked() { + continue; // Not cooked yet + } else if task.prog.cleaned() == Some(false) { + continue; // Failed to clean up } else if let Some(hook) = task.hook.take() { micro.try_send(hook, LOW).ok(); } else { diff --git a/yazi-vfs/src/provider/provider.rs b/yazi-vfs/src/provider/provider.rs index c10a943b..35607d86 100644 --- a/yazi-vfs/src/provider/provider.rs +++ b/yazi-vfs/src/provider/provider.rs @@ -185,7 +185,7 @@ pub async fn remove_dir_clean(url: U) -> io::Result<()> where U: AsUrl, { - Ok(Providers::new(url.as_url()).await?.remove_dir_clean().await) + Providers::new(url.as_url()).await?.remove_dir_clean().await } pub async fn remove_file(url: U) -> io::Result<()>