refactor: unify progress representation and add cleanup state (#3853)

This commit is contained in:
sxyazi 2026-04-05 14:05:42 +08:00
parent 4bb4f37555
commit ec178fdb52
No known key found for this signature in database
19 changed files with 663 additions and 661 deletions

692
Cargo.lock generated

File diff suppressed because it is too large Load diff

View file

@ -37,7 +37,7 @@ debug = false
[workspace.dependencies]
ansi-to-tui = "8.0.1"
anyhow = "1.0.102"
arc-swap = "1.9.0"
arc-swap = "1.9.1"
base64 = "0.22.1"
bitflags = { version = "2.11.0", features = [ "serde" ] }
chrono = "0.4.44"
@ -60,10 +60,10 @@ ordered-float = { version = "5.3.0", features = [ "serde" ] }
parking_lot = "0.12.5"
paste = "1.0.15"
percent-encoding = "2.3.2"
rand = { version = "0.9.2", default-features = false, features = [ "os_rng", "small_rng", "std" ] }
rand = { version = "0.10.0", default-features = false, features = [ "std", "sys_rng" ] }
ratatui = { version = "0.30.0", features = [ "serde", "unstable-rendered-line-info", "unstable-widget-ref" ] }
regex = "1.12.3"
russh = { version = "0.59.0", default-features = false, features = [ "ring", "rsa" ] }
russh = { version = "0.60.0", default-features = false, features = [ "ring", "rsa" ] }
scopeguard = "1.2.0"
serde = { version = "1.0.228", features = [ "derive" ] }
serde_json = "1.0.149"
@ -71,7 +71,7 @@ serde_with = "3.18.0"
strum = { version = "0.28.0", features = [ "derive" ] }
syntect = { version = "5.3.0", default-features = false, features = [ "parsing", "plist-load", "regex-onig" ] }
thiserror = "2.0.18"
tokio = { version = "1.50.0", features = [ "full" ] }
tokio = { version = "1.51.0", features = [ "full" ] }
tokio-stream = "0.1.18"
tokio-util = "0.7.18"
toml = { version = "1.1.2" }

View file

@ -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()));

View file

@ -1,7 +1,7 @@
use std::cmp::Ordering;
use hashbrown::HashMap;
use rand::{RngCore, SeedableRng, rngs::SmallRng};
use rand::{Rng, make_rng, rngs::SmallRng};
use yazi_shared::{natsort, path::PathBufDyn, translit::Transliterator, url::UrlLike};
use crate::{File, SortBy, SortFallback};
@ -72,7 +72,7 @@ impl FilesSorter {
self.fallback(a, b, self.cmp(aa.unwrap_or(a.len), bb.unwrap_or(b.len)))
}),
SortBy::Random => {
let mut rng = SmallRng::from_os_rng();
let mut rng = make_rng::<SmallRng>();
items.sort_unstable_by(|a, b| {
promote!(a, b);
self.cmp(rng.next_u64(), rng.next_u64())

View file

@ -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 }

View file

@ -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,
}

View file

@ -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<FetchProg> 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<bool> { None }
pub fn percent(self) -> Option<f32> { None }
fn failed(self) -> bool { self.state == Some(false) }
}

View file

@ -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;
}
}
}

View file

@ -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<bool>,
pub cleaned: Option<bool>,
pub cleaned: CleanupState,
}
impl From<FileProgCopy> for TaskSummary {
@ -25,33 +25,21 @@ impl From<FileProgCopy> 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<CleanupState> { Some(self.cleaned) }
pub fn cleaned(self) -> Option<bool> { self.cleaned }
pub fn percent(self) -> Option<f32> {
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<f32> {
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<bool>,
pub cleaned: Option<bool>,
pub cleaned: CleanupState,
}
impl From<FileProgCut> for TaskSummary {
@ -78,33 +66,21 @@ impl From<FileProgCut> 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<CleanupState> { Some(self.cleaned) }
pub fn cleaned(self) -> Option<bool> { self.cleaned }
pub fn percent(self) -> Option<f32> {
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<f32> {
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<bool>,
pub cleaned: Option<bool>,
pub cleaned: CleanupState,
}
impl From<FileProgLink> for TaskSummary {
@ -126,18 +102,14 @@ impl From<FileProgLink> 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<bool> { self.cleaned }
pub fn percent(self) -> Option<f32> { None }
fn cleaned(self) -> Option<CleanupState> { Some(self.cleaned) }
}
// --- Hardlink
@ -147,7 +119,7 @@ pub struct FileProgHardlink {
pub success: u32,
pub failed: u32,
pub collected: Option<bool>,
pub cleaned: Option<bool>,
pub cleaned: CleanupState,
}
impl From<FileProgHardlink> for TaskSummary {
@ -161,22 +133,16 @@ impl From<FileProgHardlink> 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<bool> { self.cleaned }
pub fn percent(self) -> Option<f32> { None }
fn cleaned(self) -> Option<CleanupState> { Some(self.cleaned) }
}
// --- Delete
@ -188,7 +154,7 @@ pub struct FileProgDelete {
pub total_bytes: u64,
pub processed_bytes: u64,
pub collected: Option<bool>,
pub cleaned: Option<bool>,
pub cleaned: CleanupState,
}
impl From<FileProgDelete> for TaskSummary {
@ -202,33 +168,21 @@ impl From<FileProgDelete> 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<CleanupState> { Some(self.cleaned) }
pub fn cleaned(self) -> Option<bool> { self.cleaned }
pub fn percent(self) -> Option<f32> {
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<f32> {
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<bool>,
pub cleaned: Option<bool>,
pub cleaned: CleanupState,
}
impl From<FileProgTrash> for TaskSummary {
@ -250,18 +204,14 @@ impl From<FileProgTrash> 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<bool> { self.cleaned }
pub fn percent(self) -> Option<f32> { None }
fn cleaned(self) -> Option<CleanupState> { Some(self.cleaned) }
}
// --- Download
@ -273,7 +223,7 @@ pub struct FileProgDownload {
pub total_bytes: u64,
pub processed_bytes: u64,
pub collected: Option<bool>,
pub cleaned: Option<bool>,
pub cleaned: CleanupState,
}
impl From<FileProgDownload> for TaskSummary {
@ -287,33 +237,21 @@ impl From<FileProgDownload> 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<CleanupState> { Some(self.cleaned) }
pub fn cleaned(self) -> Option<bool> { self.cleaned }
pub fn percent(self) -> Option<f32> {
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<f32> {
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<bool>,
pub cleaned: Option<bool>,
pub cleaned: CleanupState,
}
impl From<FileProgUpload> for TaskSummary {
@ -340,32 +278,20 @@ impl From<FileProgUpload> 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<CleanupState> { Some(self.cleaned) }
pub fn cleaned(self) -> Option<bool> { self.cleaned }
pub fn percent(self) -> Option<f32> {
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<f32> {
Some(self.byte_percent(self.processed_bytes, self.total_bytes))
}
}

View file

@ -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;

View file

@ -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),
}
};
}

View file

@ -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<PluginProgEntry> 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<bool> { None }
pub fn percent(self) -> Option<f32> { None }
fn failed(self) -> bool { self.state == Some(false) }
}

View file

@ -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;
}
}
}

View file

@ -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<bool>,
pub cleaned: Option<bool>,
pub cleaned: CleanupState,
}
impl From<PreloadProg> for TaskSummary {
@ -19,16 +19,12 @@ impl From<PreloadProg> 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<bool> { self.cleaned }
pub fn percent(self) -> Option<f32> { None }
fn cleaned(self) -> Option<CleanupState> { Some(self.cleaned) }
}

View file

@ -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<ProcessProgBlock> 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<bool> { None }
pub fn percent(self) -> Option<f32> { None }
fn failed(self) -> bool { self.state == Some(false) }
}
// --- Orphan
@ -50,18 +44,12 @@ impl From<ProcessProgOrphan> 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<bool> { None }
pub fn percent(self) -> Option<f32> { None }
fn failed(self) -> bool { self.state == Some(false) }
}
// --- Bg
@ -81,16 +69,10 @@ impl From<ProcessProgBg> 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<bool> { None }
pub fn percent(self) -> Option<f32> { None }
fn failed(self) -> bool { self.state == Some(false) }
}

View file

@ -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<CleanupState> { None }
// Optional percentage for UI display.
fn percent(self) -> Option<f32> { 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<TaskProg> 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<CleanupState> { dispatch_progress!(self, cleaned) }
fn percent(self) -> Option<f32> { 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<bool> {
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<f32> {
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

View file

@ -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<SizeProg> 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<bool> { None }
pub fn percent(self) -> Option<f32> { None }
fn failed(self) -> bool { false }
}

View file

@ -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 {

View file

@ -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);