From 187063e3a24bbdd3a9134b094bf4ff2d8c0f2ee5 Mon Sep 17 00:00:00 2001 From: sxyazi Date: Sun, 5 Apr 2026 13:39:26 +0800 Subject: [PATCH] refactor: unify progress representation and add cleanup state --- Cargo.lock | 1 + yazi-actor/src/lives/task.rs | 3 +- yazi-scheduler/Cargo.toml | 1 + yazi-scheduler/src/cleanup.rs | 11 ++ yazi-scheduler/src/fetch/progress.rs | 16 +- yazi-scheduler/src/file/out.rs | 22 +-- yazi-scheduler/src/file/progress.rs | 224 +++++++++---------------- yazi-scheduler/src/lib.rs | 2 +- yazi-scheduler/src/macros.rs | 27 +++ yazi-scheduler/src/plugin/progress.rs | 16 +- yazi-scheduler/src/preload/out.rs | 4 +- yazi-scheduler/src/preload/progress.rs | 18 +- yazi-scheduler/src/process/progress.rs | 44 ++--- yazi-scheduler/src/progress.rs | 210 +++++++---------------- yazi-scheduler/src/size/progress.rs | 16 +- yazi-scheduler/src/summary.rs | 2 +- yazi-scheduler/src/worker.rs | 4 +- 17 files changed, 234 insertions(+), 387 deletions(-) create mode 100644 yazi-scheduler/src/cleanup.rs diff --git a/Cargo.lock b/Cargo.lock index 97cbf293..051487e0 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -6058,6 +6058,7 @@ dependencies = [ "ordered-float 5.3.0", "parking_lot", "serde", + "strum 0.28.0", "tokio", "tokio-util", "tracing", diff --git a/yazi-actor/src/lives/task.rs b/yazi-actor/src/lives/task.rs index 7d63b7b5..e7e85905 100644 --- a/yazi-actor/src/lives/task.rs +++ b/yazi-actor/src/lives/task.rs @@ -2,6 +2,7 @@ use std::ops::Deref; use mlua::{AnyUserData, LuaSerdeExt, UserData, UserDataFields, Value}; use yazi_binding::{SER_OPT, cached_field}; +use yazi_scheduler::Progress; use super::{Lives, PtrCell}; @@ -29,8 +30,8 @@ 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("cooked", |_, me| Ok(me.prog.cooked())); fields.add_field_method_get("success", |_, me| Ok(me.prog.success())); fields.add_field_method_get("failed", |_, me| Ok(me.prog.failed())); fields.add_field_method_get("percent", |_, me| Ok(me.prog.percent())); diff --git a/yazi-scheduler/Cargo.toml b/yazi-scheduler/Cargo.toml index 6b44d23b..70806031 100644 --- a/yazi-scheduler/Cargo.toml +++ b/yazi-scheduler/Cargo.toml @@ -33,6 +33,7 @@ mlua = { workspace = true } ordered-float = { workspace = true } parking_lot = { workspace = true } serde = { workspace = true } +strum = { workspace = true } tokio = { workspace = true } tokio-util = { workspace = true } tracing = { workspace = true } diff --git a/yazi-scheduler/src/cleanup.rs b/yazi-scheduler/src/cleanup.rs new file mode 100644 index 00000000..0778aa1c --- /dev/null +++ b/yazi-scheduler/src/cleanup.rs @@ -0,0 +1,11 @@ +use serde::Serialize; +use strum::EnumIs; + +#[derive(Clone, Copy, Debug, Default, EnumIs, Eq, PartialEq, Serialize)] +#[serde(rename_all = "kebab-case")] +pub enum CleanupState { + #[default] + Pending, + Success, + Failed, +} diff --git a/yazi-scheduler/src/fetch/progress.rs b/yazi-scheduler/src/fetch/progress.rs index f63c1b28..6fa25ab5 100644 --- a/yazi-scheduler/src/fetch/progress.rs +++ b/yazi-scheduler/src/fetch/progress.rs @@ -1,6 +1,6 @@ use serde::Serialize; -use crate::TaskSummary; +use crate::{Progress, TaskSummary}; #[derive(Clone, Copy, Debug, Default, Eq, PartialEq, Serialize)] pub struct FetchProg { @@ -18,16 +18,10 @@ impl From for TaskSummary { } } -impl FetchProg { - pub fn cooked(self) -> bool { self.state == Some(true) } +impl Progress for FetchProg { + fn running(self) -> bool { self.state.is_none() } - pub fn running(self) -> bool { self.state.is_none() } + fn cooked(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) -> Option { None } - - pub fn percent(self) -> Option { None } + fn failed(self) -> bool { self.state == Some(false) } } diff --git a/yazi-scheduler/src/file/out.rs b/yazi-scheduler/src/file/out.rs index fb452b3c..d6a34994 100644 --- a/yazi-scheduler/src/file/out.rs +++ b/yazi-scheduler/src/file/out.rs @@ -1,6 +1,6 @@ use std::io; -use crate::{Task, TaskProg}; +use crate::{CleanupState, Task, TaskProg}; // --- Copy #[derive(Debug)] @@ -41,7 +41,7 @@ impl FileOutCopy { task.log(reason); } Self::Clean => { - prog.cleaned = Some(true); + prog.cleaned = CleanupState::Success; } } } @@ -120,10 +120,10 @@ impl FileOutCut { task.log(reason); } Self::Clean(Ok(())) => { - prog.cleaned = Some(true); + prog.cleaned = CleanupState::Success; } Self::Clean(Err(reason)) => { - prog.cleaned = Some(false); + prog.cleaned = CleanupState::Failed; task.log(format!("Failed cleaning up cut file: {reason:?}")); } } @@ -188,7 +188,7 @@ impl FileOutLink { task.log(reason); } Self::Clean => { - prog.cleaned = Some(true); + prog.cleaned = CleanupState::Success; } } } else if let TaskProg::FileCopy(prog) = &mut task.prog { @@ -255,7 +255,7 @@ impl FileOutHardlink { task.log(reason); } Self::Clean => { - prog.cleaned = Some(true); + prog.cleaned = CleanupState::Success; } } } @@ -316,10 +316,10 @@ impl FileOutDelete { task.log(reason); } Self::Clean(Ok(())) => { - prog.cleaned = Some(true); + prog.cleaned = CleanupState::Success; } Self::Clean(Err(reason)) => { - prog.cleaned = Some(false); + prog.cleaned = CleanupState::Failed; task.log(format!("Failed cleaning up deleted file: {reason:?}")); } } @@ -377,7 +377,7 @@ impl FileOutTrash { task.log(reason); } Self::Clean => { - prog.cleaned = Some(true); + prog.cleaned = CleanupState::Success; } } } @@ -418,7 +418,7 @@ impl FileOutDownload { task.log(reason); } Self::Clean => { - prog.cleaned = Some(true); + prog.cleaned = CleanupState::Success; } } } @@ -493,7 +493,7 @@ impl FileOutUpload { task.log(reason); } Self::Clean => { - prog.cleaned = Some(true); + prog.cleaned = CleanupState::Success; } } } diff --git a/yazi-scheduler/src/file/progress.rs b/yazi-scheduler/src/file/progress.rs index 2c1f5e8d..1d24b9f3 100644 --- a/yazi-scheduler/src/file/progress.rs +++ b/yazi-scheduler/src/file/progress.rs @@ -1,6 +1,6 @@ use serde::Serialize; -use crate::TaskSummary; +use crate::{CleanupState, Progress, TaskSummary}; // --- Copy #[derive(Clone, Copy, Debug, Default, Eq, PartialEq, Serialize)] @@ -11,7 +11,7 @@ pub struct FileProgCopy { pub total_bytes: u64, pub processed_bytes: u64, pub collected: Option, - pub cleaned: Option, + pub cleaned: CleanupState, } impl From for TaskSummary { @@ -25,33 +25,21 @@ impl From for TaskSummary { } } -impl FileProgCopy { - pub fn cooked(self) -> bool { - self.collected == Some(true) && self.success_files == self.total_files +impl Progress for FileProgCopy { + fn running(self) -> bool { + self.cooking_or_cleaning( + self.collected.is_none() || self.success_files + self.failed_files != self.total_files, + ) } - pub fn running(self) -> bool { - self.collected.is_none() - || self.success_files + self.failed_files != self.total_files - || (self.cleaned.is_none() && self.cooked()) - } + fn cooked(self) -> bool { self.collected == Some(true) && self.success_files == self.total_files } - pub fn success(self) -> bool { self.cleaned == Some(true) && self.cooked() } + fn failed(self) -> bool { self.cleaned.is_failed() || self.collected == Some(false) } - pub fn failed(self) -> bool { self.cleaned == Some(false) || self.collected == Some(false) } + fn cleaned(self) -> Option { Some(self.cleaned) } - pub fn cleaned(self) -> Option { self.cleaned } - - pub fn percent(self) -> Option { - Some(if self.success() { - 100.0 - } else if self.failed() { - 0.0 - } else if self.total_bytes != 0 { - 99.99f32.min(self.processed_bytes as f32 / self.total_bytes as f32 * 100.0) - } else { - 99.99 - }) + fn percent(self) -> Option { + Some(self.byte_percent(self.processed_bytes, self.total_bytes)) } } @@ -64,7 +52,7 @@ pub struct FileProgCut { pub total_bytes: u64, pub processed_bytes: u64, pub collected: Option, - pub cleaned: Option, + pub cleaned: CleanupState, } impl From for TaskSummary { @@ -78,33 +66,21 @@ impl From for TaskSummary { } } -impl FileProgCut { - pub fn cooked(self) -> bool { - self.collected == Some(true) && self.success_files == self.total_files +impl Progress for FileProgCut { + fn running(self) -> bool { + self.cooking_or_cleaning( + self.collected.is_none() || self.success_files + self.failed_files != self.total_files, + ) } - pub fn running(self) -> bool { - self.collected.is_none() - || self.success_files + self.failed_files != self.total_files - || (self.cleaned.is_none() && self.cooked()) - } + fn cooked(self) -> bool { self.collected == Some(true) && self.success_files == self.total_files } - pub fn success(self) -> bool { self.cleaned == Some(true) && self.cooked() } + fn failed(self) -> bool { self.cleaned.is_failed() || self.collected == Some(false) } - pub fn failed(self) -> bool { self.cleaned == Some(false) || self.collected == Some(false) } + fn cleaned(self) -> Option { Some(self.cleaned) } - pub fn cleaned(self) -> Option { self.cleaned } - - pub fn percent(self) -> Option { - Some(if self.success() { - 100.0 - } else if self.failed() { - 0.0 - } else if self.total_bytes != 0 { - 99.99f32.min(self.processed_bytes as f32 / self.total_bytes as f32 * 100.0) - } else { - 99.99 - }) + fn percent(self) -> Option { + Some(self.byte_percent(self.processed_bytes, self.total_bytes)) } } @@ -112,7 +88,7 @@ impl FileProgCut { #[derive(Clone, Copy, Debug, Default, Eq, PartialEq, Serialize)] pub struct FileProgLink { pub state: Option, - pub cleaned: Option, + pub cleaned: CleanupState, } impl From for TaskSummary { @@ -126,18 +102,14 @@ impl From for TaskSummary { } } -impl FileProgLink { - pub fn cooked(self) -> bool { self.state == Some(true) } +impl Progress for FileProgLink { + fn running(self) -> bool { self.cooking_or_cleaning(self.state.is_none()) } - pub fn running(self) -> bool { self.state.is_none() || (self.cleaned.is_none() && self.cooked()) } + fn cooked(self) -> bool { self.state == Some(true) } - pub fn success(self) -> bool { self.cleaned == Some(true) && self.cooked() } + fn failed(self) -> bool { self.cleaned.is_failed() || self.state == Some(false) } - 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 } + fn cleaned(self) -> Option { Some(self.cleaned) } } // --- Hardlink @@ -147,7 +119,7 @@ pub struct FileProgHardlink { pub success: u32, pub failed: u32, pub collected: Option, - pub cleaned: Option, + pub cleaned: CleanupState, } impl From for TaskSummary { @@ -161,22 +133,16 @@ 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 - || (self.cleaned.is_none() && self.cooked()) +impl Progress for FileProgHardlink { + fn running(self) -> bool { + self.cooking_or_cleaning(self.collected.is_none() || self.success + self.failed != self.total) } - pub fn success(self) -> bool { self.cleaned == Some(true) && self.cooked() } + fn cooked(self) -> bool { self.collected == Some(true) && self.success == self.total } - pub fn failed(self) -> bool { self.cleaned == Some(false) || self.collected == Some(false) } + fn failed(self) -> bool { self.cleaned.is_failed() || self.collected == Some(false) } - pub fn cleaned(self) -> Option { self.cleaned } - - pub fn percent(self) -> Option { None } + fn cleaned(self) -> Option { Some(self.cleaned) } } // --- Delete @@ -188,7 +154,7 @@ pub struct FileProgDelete { pub total_bytes: u64, pub processed_bytes: u64, pub collected: Option, - pub cleaned: Option, + pub cleaned: CleanupState, } impl From for TaskSummary { @@ -202,33 +168,21 @@ impl From for TaskSummary { } } -impl FileProgDelete { - pub fn cooked(self) -> bool { - self.collected == Some(true) && self.success_files == self.total_files +impl Progress for FileProgDelete { + fn running(self) -> bool { + self.cooking_or_cleaning( + self.collected.is_none() || self.success_files + self.failed_files != self.total_files, + ) } - pub fn running(self) -> bool { - self.collected.is_none() - || self.success_files + self.failed_files != self.total_files - || (self.cleaned.is_none() && self.cooked()) - } + fn cooked(self) -> bool { self.collected == Some(true) && self.success_files == self.total_files } - pub fn success(self) -> bool { self.cleaned == Some(true) && self.cooked() } + fn failed(self) -> bool { self.cleaned.is_failed() || self.collected == Some(false) } - pub fn failed(self) -> bool { self.cleaned == Some(false) || self.collected == Some(false) } + fn cleaned(self) -> Option { Some(self.cleaned) } - pub fn cleaned(self) -> Option { self.cleaned } - - pub fn percent(self) -> Option { - Some(if self.success() { - 100.0 - } else if self.failed() { - 0.0 - } else if self.total_bytes != 0 { - 99.99f32.min(self.processed_bytes as f32 / self.total_bytes as f32 * 100.0) - } else { - 99.99 - }) + fn percent(self) -> Option { + Some(self.byte_percent(self.processed_bytes, self.total_bytes)) } } @@ -236,7 +190,7 @@ impl FileProgDelete { #[derive(Clone, Copy, Debug, Default, Eq, PartialEq, Serialize)] pub struct FileProgTrash { pub state: Option, - pub cleaned: Option, + pub cleaned: CleanupState, } impl From for TaskSummary { @@ -250,18 +204,14 @@ impl From for TaskSummary { } } -impl FileProgTrash { - pub fn cooked(self) -> bool { self.state == Some(true) } +impl Progress for FileProgTrash { + fn running(self) -> bool { self.cooking_or_cleaning(self.state.is_none()) } - pub fn running(self) -> bool { self.state.is_none() || (self.cleaned.is_none() && self.cooked()) } + fn cooked(self) -> bool { self.state == Some(true) } - pub fn success(self) -> bool { self.cleaned == Some(true) && self.cooked() } + fn failed(self) -> bool { self.cleaned.is_failed() || self.state == Some(false) } - 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 } + fn cleaned(self) -> Option { Some(self.cleaned) } } // --- Download @@ -273,7 +223,7 @@ pub struct FileProgDownload { pub total_bytes: u64, pub processed_bytes: u64, pub collected: Option, - pub cleaned: Option, + pub cleaned: CleanupState, } impl From for TaskSummary { @@ -287,33 +237,21 @@ impl From for TaskSummary { } } -impl FileProgDownload { - pub fn cooked(self) -> bool { - self.collected == Some(true) && self.success_files == self.total_files +impl Progress for FileProgDownload { + fn running(self) -> bool { + self.cooking_or_cleaning( + self.collected.is_none() || self.success_files + self.failed_files != self.total_files, + ) } - pub fn running(self) -> bool { - self.collected.is_none() - || self.success_files + self.failed_files != self.total_files - || (self.cleaned.is_none() && self.cooked()) - } + fn cooked(self) -> bool { self.collected == Some(true) && self.success_files == self.total_files } - pub fn success(self) -> bool { self.cleaned == Some(true) && self.cooked() } + fn failed(self) -> bool { self.cleaned.is_failed() || self.collected == Some(false) } - pub fn failed(self) -> bool { self.cleaned == Some(false) || self.collected == Some(false) } + fn cleaned(self) -> Option { Some(self.cleaned) } - pub fn cleaned(self) -> Option { self.cleaned } - - pub fn percent(self) -> Option { - Some(if self.success() { - 100.0 - } else if self.failed() { - 0.0 - } else if self.total_bytes != 0 { - 99.99f32.min(self.processed_bytes as f32 / self.total_bytes as f32 * 100.0) - } else { - 99.99 - }) + fn percent(self) -> Option { + Some(self.byte_percent(self.processed_bytes, self.total_bytes)) } } @@ -326,7 +264,7 @@ pub struct FileProgUpload { pub total_bytes: u64, pub processed_bytes: u64, pub collected: Option, - pub cleaned: Option, + pub cleaned: CleanupState, } impl From for TaskSummary { @@ -340,32 +278,20 @@ impl From for TaskSummary { } } -impl FileProgUpload { - pub fn cooked(self) -> bool { - self.collected == Some(true) && self.success_files == self.total_files +impl Progress for FileProgUpload { + fn running(self) -> bool { + self.cooking_or_cleaning( + self.collected.is_none() || self.success_files + self.failed_files != self.total_files, + ) } - pub fn running(self) -> bool { - self.collected.is_none() - || self.success_files + self.failed_files != self.total_files - || (self.cleaned.is_none() && self.cooked()) - } + fn cooked(self) -> bool { self.collected == Some(true) && self.success_files == self.total_files } - pub fn success(self) -> bool { self.cleaned == Some(true) && self.cooked() } + fn failed(self) -> bool { self.cleaned.is_failed() || self.collected == Some(false) } - pub fn failed(self) -> bool { self.cleaned == Some(false) || self.collected == Some(false) } + fn cleaned(self) -> Option { Some(self.cleaned) } - pub fn cleaned(self) -> Option { self.cleaned } - - pub fn percent(self) -> Option { - Some(if self.success() { - 100.0 - } else if self.failed() { - 0.0 - } else if self.total_bytes != 0 { - 99.99f32.min(self.processed_bytes as f32 / self.total_bytes as f32 * 100.0) - } else { - 99.99 - }) + fn percent(self) -> Option { + Some(self.byte_percent(self.processed_bytes, self.total_bytes)) } } diff --git a/yazi-scheduler/src/lib.rs b/yazi-scheduler/src/lib.rs index 9ea9dfc7..8dd69f54 100644 --- a/yazi-scheduler/src/lib.rs +++ b/yazi-scheduler/src/lib.rs @@ -2,7 +2,7 @@ mod macros; yazi_macro::mod_pub!(fetch file hook plugin preload process size); -yazi_macro::mod_flat!(behavior ongoing op out progress proxy scheduler snap summary task worker); +yazi_macro::mod_flat!(behavior cleanup ongoing op out progress proxy scheduler snap summary task worker); const LOW: u8 = yazi_config::Priority::Low as u8; const NORMAL: u8 = yazi_config::Priority::Normal as u8; diff --git a/yazi-scheduler/src/macros.rs b/yazi-scheduler/src/macros.rs index 826b4b0a..ef43cd05 100644 --- a/yazi-scheduler/src/macros.rs +++ b/yazi-scheduler/src/macros.rs @@ -45,3 +45,30 @@ macro_rules! impl_from_prog { )* }; } + +#[macro_export] +macro_rules! dispatch_progress { + ($value:expr, $method:ident) => { + match $value { + // File + $crate::TaskProg::FileCopy(p) => $crate::Progress::$method(p), + $crate::TaskProg::FileCut(p) => $crate::Progress::$method(p), + $crate::TaskProg::FileLink(p) => $crate::Progress::$method(p), + $crate::TaskProg::FileHardlink(p) => $crate::Progress::$method(p), + $crate::TaskProg::FileDelete(p) => $crate::Progress::$method(p), + $crate::TaskProg::FileTrash(p) => $crate::Progress::$method(p), + $crate::TaskProg::FileDownload(p) => $crate::Progress::$method(p), + $crate::TaskProg::FileUpload(p) => $crate::Progress::$method(p), + // Plugin + $crate::TaskProg::PluginEntry(p) => $crate::Progress::$method(p), + // Prework + $crate::TaskProg::Fetch(p) => $crate::Progress::$method(p), + $crate::TaskProg::Preload(p) => $crate::Progress::$method(p), + $crate::TaskProg::Size(p) => $crate::Progress::$method(p), + // Process + $crate::TaskProg::ProcessBlock(p) => $crate::Progress::$method(p), + $crate::TaskProg::ProcessOrphan(p) => $crate::Progress::$method(p), + $crate::TaskProg::ProcessBg(p) => $crate::Progress::$method(p), + } + }; +} diff --git a/yazi-scheduler/src/plugin/progress.rs b/yazi-scheduler/src/plugin/progress.rs index a2f1d71c..2fba592e 100644 --- a/yazi-scheduler/src/plugin/progress.rs +++ b/yazi-scheduler/src/plugin/progress.rs @@ -1,6 +1,6 @@ use serde::Serialize; -use crate::TaskSummary; +use crate::{Progress, TaskSummary}; // --- Entry #[derive(Clone, Copy, Debug, Default, Eq, PartialEq, Serialize)] @@ -19,16 +19,10 @@ impl From for TaskSummary { } } -impl PluginProgEntry { - pub fn cooked(self) -> bool { self.state == Some(true) } +impl Progress for PluginProgEntry { + fn running(self) -> bool { self.state.is_none() } - pub fn running(self) -> bool { self.state.is_none() } + fn cooked(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) -> Option { None } - - pub fn percent(self) -> Option { None } + fn failed(self) -> bool { self.state == Some(false) } } diff --git a/yazi-scheduler/src/preload/out.rs b/yazi-scheduler/src/preload/out.rs index 9cef4d44..c15068c4 100644 --- a/yazi-scheduler/src/preload/out.rs +++ b/yazi-scheduler/src/preload/out.rs @@ -1,6 +1,6 @@ use yazi_runner::preloader::PreloadError; -use crate::{Task, TaskProg}; +use crate::{CleanupState, Task, TaskProg}; #[derive(Debug)] pub(crate) enum PreloadOut { @@ -25,7 +25,7 @@ impl PreloadOut { task.log(reason); } Self::Clean => { - prog.cleaned = Some(true); + prog.cleaned = CleanupState::Success; } } } diff --git a/yazi-scheduler/src/preload/progress.rs b/yazi-scheduler/src/preload/progress.rs index e28c8969..4c505fc8 100644 --- a/yazi-scheduler/src/preload/progress.rs +++ b/yazi-scheduler/src/preload/progress.rs @@ -1,11 +1,11 @@ use serde::Serialize; -use crate::TaskSummary; +use crate::{CleanupState, Progress, TaskSummary}; #[derive(Clone, Copy, Debug, Default, Eq, PartialEq, Serialize)] pub struct PreloadProg { pub state: Option, - pub cleaned: Option, + pub cleaned: CleanupState, } impl From for TaskSummary { @@ -19,16 +19,12 @@ impl From for TaskSummary { } } -impl PreloadProg { - pub fn cooked(self) -> bool { self.state == Some(true) } +impl Progress for PreloadProg { + fn running(self) -> bool { self.cooking_or_cleaning(self.state.is_none()) } - pub fn running(self) -> bool { self.state.is_none() || (self.cleaned.is_none() && self.cooked()) } + fn cooked(self) -> bool { self.state == Some(true) } - pub fn success(self) -> bool { self.cleaned == Some(true) && self.cooked() } + fn failed(self) -> bool { self.cleaned.is_failed() || self.state == Some(false) } - 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 } + fn cleaned(self) -> Option { Some(self.cleaned) } } diff --git a/yazi-scheduler/src/process/progress.rs b/yazi-scheduler/src/process/progress.rs index 69c8357e..91bdef0c 100644 --- a/yazi-scheduler/src/process/progress.rs +++ b/yazi-scheduler/src/process/progress.rs @@ -1,6 +1,6 @@ use serde::Serialize; -use crate::TaskSummary; +use crate::{Progress, TaskSummary}; // --- Block #[derive(Clone, Copy, Debug, Default, Eq, PartialEq, Serialize)] @@ -19,18 +19,12 @@ impl From for TaskSummary { } } -impl ProcessProgBlock { - pub fn cooked(self) -> bool { self.state == Some(true) } +impl Progress for ProcessProgBlock { + fn running(self) -> bool { self.state.is_none() } - pub fn running(self) -> bool { self.state.is_none() } + fn cooked(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) -> Option { None } - - pub fn percent(self) -> Option { None } + fn failed(self) -> bool { self.state == Some(false) } } // --- Orphan @@ -50,18 +44,12 @@ impl From for TaskSummary { } } -impl ProcessProgOrphan { - pub fn cooked(self) -> bool { self.state == Some(true) } +impl Progress for ProcessProgOrphan { + fn running(self) -> bool { self.state.is_none() } - pub fn running(self) -> bool { self.state.is_none() } + fn cooked(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) -> Option { None } - - pub fn percent(self) -> Option { None } + fn failed(self) -> bool { self.state == Some(false) } } // --- Bg @@ -81,16 +69,10 @@ impl From for TaskSummary { } } -impl ProcessProgBg { - pub fn cooked(self) -> bool { self.state == Some(true) } +impl Progress for ProcessProgBg { + fn running(self) -> bool { self.state.is_none() } - pub fn running(self) -> bool { self.state.is_none() } + fn cooked(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) -> Option { None } - - pub fn percent(self) -> Option { None } + fn failed(self) -> bool { self.state == Some(false) } } diff --git a/yazi-scheduler/src/progress.rs b/yazi-scheduler/src/progress.rs index 3546e18b..8cd02c34 100644 --- a/yazi-scheduler/src/progress.rs +++ b/yazi-scheduler/src/progress.rs @@ -1,7 +1,57 @@ use serde::Serialize; -use crate::{TaskSummary, fetch::FetchProg, file::{FileProgCopy, FileProgCut, FileProgDelete, FileProgDownload, FileProgHardlink, FileProgLink, FileProgTrash, FileProgUpload}, impl_from_prog, plugin::PluginProgEntry, preload::PreloadProg, process::{ProcessProgBg, ProcessProgBlock, ProcessProgOrphan}, size::SizeProg}; +use crate::{CleanupState, TaskSummary, dispatch_progress, fetch::FetchProg, file::{FileProgCopy, FileProgCut, FileProgDelete, FileProgDownload, FileProgHardlink, FileProgLink, FileProgTrash, FileProgUpload}, impl_from_prog, plugin::PluginProgEntry, preload::PreloadProg, process::{ProcessProgBg, ProcessProgBlock, ProcessProgOrphan}, size::SizeProg}; +pub trait Progress: Copy { + // Whether the task is still cooking or cleaning. + fn running(self) -> bool; + + // Whether the task succeeded, regardless cleanup. + // For tasks without a cleanup, this is the same as `success()`. + fn cooked(self) -> bool; + + // Whether the task fully succeeded, including cleanup if applicable. + fn success(self) -> bool { + match self.cleaned() { + None | Some(CleanupState::Success) => self.cooked(), + Some(CleanupState::Pending | CleanupState::Failed) => false, + } + } + + // Whether the task fully failed. + // + // For tasks with a collect phase, e.g. gathering files to copy: + // collect failed, or cleanup failed if applicable, regardless main work. + // For tasks without a collect phase: + // main work failed, or cleanup failed if applicable. + fn failed(self) -> bool; + + // Cleanup state if the task has a cleanup phase, otherwise `None`. + fn cleaned(self) -> Option { None } + + // Optional percentage for UI display. + fn percent(self) -> Option { None } + + // Helper for tasks that are still cooking or cleaning. + fn cooking_or_cleaning(self, cooking: bool) -> bool { + cooking || (self.cooked() && self.cleaned() == Some(CleanupState::Pending)) + } + + // Helper for byte-based progress calculations used by file transfer tasks. + fn byte_percent(self, processed_bytes: u64, total_bytes: u64) -> f32 { + if self.success() { + 100.0 + } else if self.failed() { + 0.0 + } else if total_bytes != 0 { + 99.99f32.min(processed_bytes as f32 / total_bytes as f32 * 100.0) + } else { + 99.99 + } + } +} + +// --- TaskProg #[derive(Clone, Copy, Debug, Eq, PartialEq, Serialize)] #[serde(tag = "kind")] pub enum TaskProg { @@ -69,151 +119,21 @@ impl From for TaskSummary { } } +impl Progress for TaskProg { + fn running(self) -> bool { dispatch_progress!(self, running) } + + fn cooked(self) -> bool { dispatch_progress!(self, cooked) } + + fn success(self) -> bool { dispatch_progress!(self, success) } + + fn failed(self) -> bool { dispatch_progress!(self, failed) } + + fn cleaned(self) -> Option { dispatch_progress!(self, cleaned) } + + fn percent(self) -> Option { dispatch_progress!(self, percent) } +} + 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::Fetch(p) => p.cooked(), - Self::Preload(p) => p.cooked(), - Self::Size(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 - Self::FileCopy(p) => p.running(), - Self::FileCut(p) => p.running(), - Self::FileLink(p) => p.running(), - Self::FileHardlink(p) => p.running(), - Self::FileDelete(p) => p.running(), - Self::FileTrash(p) => p.running(), - Self::FileDownload(p) => p.running(), - Self::FileUpload(p) => p.running(), - // Plugin - Self::PluginEntry(p) => p.running(), - // Prework - Self::Fetch(p) => p.running(), - Self::Preload(p) => p.running(), - Self::Size(p) => p.running(), - // Process - Self::ProcessBlock(p) => p.running(), - Self::ProcessOrphan(p) => p.running(), - Self::ProcessBg(p) => p.running(), - } - } - - pub fn success(self) -> bool { - match self { - // File - Self::FileCopy(p) => p.success(), - Self::FileCut(p) => p.success(), - Self::FileLink(p) => p.success(), - Self::FileHardlink(p) => p.success(), - Self::FileDelete(p) => p.success(), - Self::FileTrash(p) => p.success(), - Self::FileDownload(p) => p.success(), - Self::FileUpload(p) => p.success(), - // Plugin - Self::PluginEntry(p) => p.success(), - // Prework - Self::Fetch(p) => p.success(), - Self::Preload(p) => p.success(), - Self::Size(p) => p.success(), - // Process - Self::ProcessBlock(p) => p.success(), - Self::ProcessOrphan(p) => p.success(), - Self::ProcessBg(p) => p.success(), - } - } - - pub fn failed(self) -> bool { - match self { - // File - Self::FileCopy(p) => p.failed(), - Self::FileCut(p) => p.failed(), - Self::FileLink(p) => p.failed(), - Self::FileHardlink(p) => p.failed(), - Self::FileDelete(p) => p.failed(), - Self::FileTrash(p) => p.failed(), - Self::FileDownload(p) => p.failed(), - Self::FileUpload(p) => p.failed(), - // Plugin - Self::PluginEntry(p) => p.failed(), - // Prework - Self::Fetch(p) => p.failed(), - Self::Preload(p) => p.failed(), - Self::Size(p) => p.failed(), - // Process - Self::ProcessBlock(p) => p.failed(), - Self::ProcessOrphan(p) => p.failed(), - Self::ProcessBg(p) => p.failed(), - } - } - - pub fn cleaned(self) -> Option { - match self { - // File - Self::FileCopy(p) => p.cleaned(), - Self::FileCut(p) => p.cleaned(), - Self::FileLink(p) => p.cleaned(), - Self::FileHardlink(p) => p.cleaned(), - Self::FileDelete(p) => p.cleaned(), - Self::FileTrash(p) => p.cleaned(), - Self::FileDownload(p) => p.cleaned(), - Self::FileUpload(p) => p.cleaned(), - // Plugin - Self::PluginEntry(p) => p.cleaned(), - // Prework - Self::Fetch(p) => p.cleaned(), - Self::Preload(p) => p.cleaned(), - Self::Size(p) => p.cleaned(), - // Process - Self::ProcessBlock(p) => p.cleaned(), - Self::ProcessOrphan(p) => p.cleaned(), - Self::ProcessBg(p) => p.cleaned(), - } - } - - pub fn percent(self) -> Option { - match self { - // File - Self::FileCopy(p) => p.percent(), - Self::FileCut(p) => p.percent(), - Self::FileLink(p) => p.percent(), - Self::FileHardlink(p) => p.percent(), - Self::FileDelete(p) => p.percent(), - Self::FileTrash(p) => p.percent(), - Self::FileDownload(p) => p.percent(), - Self::FileUpload(p) => p.percent(), - // Plugin - Self::PluginEntry(p) => p.percent(), - // Prework - Self::Fetch(p) => p.percent(), - Self::Preload(p) => p.percent(), - Self::Size(p) => p.percent(), - // Process - Self::ProcessBlock(p) => p.percent(), - Self::ProcessOrphan(p) => p.percent(), - Self::ProcessBg(p) => p.percent(), - } - } - pub(crate) fn is_user(self) -> bool { match self { // File diff --git a/yazi-scheduler/src/size/progress.rs b/yazi-scheduler/src/size/progress.rs index a4ba42f0..d199f6d2 100644 --- a/yazi-scheduler/src/size/progress.rs +++ b/yazi-scheduler/src/size/progress.rs @@ -1,6 +1,6 @@ use serde::Serialize; -use crate::TaskSummary; +use crate::{Progress, TaskSummary}; #[derive(Clone, Copy, Debug, Default, Eq, PartialEq, Serialize)] pub struct SizeProg { @@ -18,16 +18,10 @@ impl From for TaskSummary { } } -impl SizeProg { - pub fn cooked(self) -> bool { self.done } +impl Progress for SizeProg { + fn running(self) -> bool { !self.done } - pub fn running(self) -> bool { !self.done } + fn cooked(self) -> bool { self.done } - pub fn success(self) -> bool { self.cooked() } - - pub fn failed(self) -> bool { false } - - pub fn cleaned(self) -> Option { None } - - pub fn percent(self) -> Option { None } + fn failed(self) -> bool { false } } diff --git a/yazi-scheduler/src/summary.rs b/yazi-scheduler/src/summary.rs index 703b293f..4e0134c6 100644 --- a/yazi-scheduler/src/summary.rs +++ b/yazi-scheduler/src/summary.rs @@ -1,7 +1,7 @@ use ordered_float::OrderedFloat; use serde::Serialize; -use crate::Ongoing; +use crate::{Ongoing, Progress}; #[derive(Clone, Copy, Debug, Default, Eq, PartialEq, Serialize)] pub struct TaskSummary { diff --git a/yazi-scheduler/src/worker.rs b/yazi-scheduler/src/worker.rs index b0679164..ff89ebe5 100644 --- a/yazi-scheduler/src/worker.rs +++ b/yazi-scheduler/src/worker.rs @@ -4,7 +4,7 @@ use parking_lot::Mutex; use tokio::{select, sync::mpsc, task::JoinHandle}; use yazi_config::YAZI; -use crate::{LOW, Ongoing, TaskOp, TaskOps, TaskOut, fetch::{Fetch, FetchIn}, file::{File, FileIn}, hook::{Hook, HookIn}, plugin::{Plugin, PluginIn}, preload::{Preload, PreloadIn}, process::{Process, ProcessIn}, size::{Size, SizeIn}}; +use crate::{CleanupState, LOW, Ongoing, Progress, TaskOp, TaskOps, TaskOut, fetch::{Fetch, FetchIn}, file::{File, FileIn}, hook::{Hook, HookIn}, plugin::{Plugin, PluginIn}, preload::{Preload, PreloadIn}, process::{Process, ProcessIn}, size::{Size, SizeIn}}; #[derive(Clone)] pub struct Worker { @@ -282,7 +282,7 @@ impl Worker { op.out.reduce(task); if !task.prog.cooked() && task.done.completed() != Some(false) { continue; // Not cooked yet, also not canceled - } else if task.prog.cleaned() == Some(false) { + } else if task.prog.cleaned() == Some(CleanupState::Failed) { continue; // Failed to clean up } else if let Some(hook) = task.hook.take() { me.hook.submit(hook, LOW);