feat: make task post-hooks stateful (#3454)

This commit is contained in:
sxyazi 2025-12-24 01:15:18 +08:00
parent 329c5d28f0
commit 6e3c96289e
No known key found for this signature in database
12 changed files with 165 additions and 85 deletions

View file

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

View file

@ -126,11 +126,12 @@ pub trait Provider: Sized {
}
}
fn remove_dir_clean(&self) -> impl Future<Output = ()> {
fn remove_dir_clean(&self) -> impl Future<Output = io::Result<()>> {
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
}
}

View file

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

View file

@ -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<anyhow::Error> 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<anyhow::Error> 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);
}
}
}

View file

@ -24,17 +24,19 @@ impl From<FileProgCopy> 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<bool> { None }
pub fn percent(self) -> Option<f32> {
Some(if self.success() {
@ -58,7 +60,7 @@ pub struct FileProgCut {
pub total_bytes: u64,
pub processed_bytes: u64,
pub collected: Option<bool>,
pub cleaned: bool,
pub cleaned: Option<bool>,
}
impl From<FileProgCut> for TaskSummary {
@ -73,17 +75,21 @@ impl From<FileProgCut> 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<bool> { self.cleaned }
pub fn percent(self) -> Option<f32> {
Some(if self.success() {
@ -116,13 +122,15 @@ impl From<FileProgLink> 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<bool> { None }
pub fn percent(self) -> Option<f32> { None }
}
@ -148,15 +156,17 @@ 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
}
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<bool> { None }
pub fn percent(self) -> Option<f32> { None }
}
@ -170,7 +180,7 @@ pub struct FileProgDelete {
pub total_bytes: u64,
pub processed_bytes: u64,
pub collected: Option<bool>,
pub cleaned: bool,
pub cleaned: Option<bool>,
}
impl From<FileProgDelete> for TaskSummary {
@ -185,17 +195,21 @@ impl From<FileProgDelete> 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<bool> { self.cleaned }
pub fn percent(self) -> Option<f32> {
Some(if self.success() {
@ -214,7 +228,7 @@ impl FileProgDelete {
#[derive(Clone, Copy, Debug, Default, Eq, PartialEq, Serialize)]
pub struct FileProgTrash {
pub state: Option<bool>,
pub cleaned: bool,
pub cleaned: Option<bool>,
}
impl From<FileProgTrash> for TaskSummary {
@ -229,13 +243,15 @@ impl From<FileProgTrash> 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<bool> { self.cleaned }
pub fn percent(self) -> Option<f32> { None }
}
@ -249,7 +265,7 @@ pub struct FileProgDownload {
pub total_bytes: u64,
pub processed_bytes: u64,
pub collected: Option<bool>,
pub cleaned: bool,
pub cleaned: Option<bool>,
}
impl From<FileProgDownload> for TaskSummary {
@ -264,17 +280,21 @@ impl From<FileProgDownload> 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<bool> { self.cleaned }
pub fn percent(self) -> Option<f32> {
Some(if self.success() {
@ -312,17 +332,19 @@ impl From<FileProgUpload> 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<bool> { None }
pub fn percent(self) -> Option<f32> {
Some(if self.success() {

View file

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

View file

@ -19,13 +19,15 @@ impl From<PluginProgEntry> 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<bool> { None }
pub fn percent(self) -> Option<f32> { None }
}

View file

@ -19,13 +19,15 @@ impl From<PreworkProgFetch> 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<bool> { None }
pub fn percent(self) -> Option<f32> { None }
}
@ -48,13 +50,15 @@ impl From<PreworkProgLoad> 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<bool> { None }
pub fn percent(self) -> Option<f32> { None }
}
@ -77,13 +81,15 @@ impl From<PreworkProgSize> 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<bool> { None }
pub fn percent(self) -> Option<f32> { None }
}

View file

@ -19,13 +19,15 @@ impl From<ProcessProgBlock> 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<bool> { None }
pub fn percent(self) -> Option<f32> { None }
}
@ -48,13 +50,15 @@ impl From<ProcessProgOrphan> 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<bool> { None }
pub fn percent(self) -> Option<f32> { None }
}
@ -77,13 +81,15 @@ impl From<ProcessProgBg> 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<bool> { None }
pub fn percent(self) -> Option<f32> { None }
}

View file

@ -65,6 +65,30 @@ impl From<TaskProg> 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<bool> {
match self {
// File
Self::FileCopy(p) => p.cleaned(),

View file

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

View file

@ -185,7 +185,7 @@ pub async fn remove_dir_clean<U>(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<U>(url: U) -> io::Result<()>