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, name, |lua, me| lua.create_string(&me.name));
cached_field!(fields, prog, |lua, me| lua.to_value_with(&me.prog, SER_OPT)); 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("running", |_, me| Ok(me.prog.running()));
fields.add_field_method_get("success", |_, me| Ok(me.prog.success())); 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("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(); let root = self.url().to_owned();
async move { async move {
let mut stack = vec![(root, false)]; let mut stack = vec![(root, false)];
let mut result = Ok(());
while let Some((dir, visited)) = stack.pop() { while let Some((dir, visited)) = stack.pop() {
let Ok(provider) = Self::new(dir.as_url()).await else { let Ok(provider) = Self::new(dir.as_url()).await else {
@ -138,7 +139,7 @@ pub trait Provider: Sized {
}; };
if visited { if visited {
provider.remove_dir().await.ok(); result = provider.remove_dir().await;
} else if let Ok(mut it) = provider.read_dir().await { } else if let Ok(mut it) = provider.read_dir().await {
stack.push((dir, true)); stack.push((dir, true));
while let Ok(Some(ent)) = it.next().await { 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" or kind == "FileUpload"
then then
local percent local percent
if snap.success then if snap.cooked then
percent = "Cleaning…" percent = "Cleaning…"
else else
percent = string.format("%3d%%", math.floor(snap.percent)) percent = string.format("%3d%%", math.floor(snap.percent))
@ -101,7 +101,7 @@ function Tasks:progress_redraw(snap, y)
) )
local style = th.status.progress_normal 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 style = th.status.progress_error
end end
@ -119,10 +119,10 @@ function Tasks:progress_redraw(snap, y)
} }
else else
local text local text
if snap.running then if snap.cooked then
text = "Running…"
elseif snap.success then
text = "Cleaning…" text = "Cleaning…"
elseif snap.running then
text = "Running…"
else else
text = "Failed, press Enter to view log…" text = "Failed, press Enter to view log…"
end end

View file

@ -1,3 +1,5 @@
use std::io;
use crate::{Task, TaskProg}; use crate::{Task, TaskProg};
// --- Copy // --- Copy
@ -82,7 +84,7 @@ pub(crate) enum FileOutCut {
Deform(String), Deform(String),
Succ, Succ,
Fail(String), Fail(String),
Clean, Clean(io::Result<()>),
} }
impl From<anyhow::Error> for FileOutCut { impl From<anyhow::Error> for FileOutCut {
@ -113,8 +115,12 @@ impl FileOutCut {
prog.collected = Some(false); prog.collected = Some(false);
task.log(reason); task.log(reason);
} }
Self::Clean => { Self::Clean(Ok(())) => {
prog.cleaned = true; 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), New(u64),
Succ, Succ,
Fail(String), Fail(String),
Clean, Clean(io::Result<()>),
} }
impl From<anyhow::Error> for FileOutDelete { impl From<anyhow::Error> for FileOutDelete {
@ -295,8 +301,12 @@ impl FileOutDelete {
prog.collected = Some(false); prog.collected = Some(false);
task.log(reason); task.log(reason);
} }
Self::Clean => { Self::Clean(Ok(())) => {
prog.cleaned = true; 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); task.log(reason);
} }
Self::Clean => { Self::Clean => {
prog.cleaned = true; prog.cleaned = Some(true);
} }
} }
} }
@ -394,7 +404,7 @@ impl FileOutDownload {
task.log(reason); task.log(reason);
} }
Self::Clean => { Self::Clean => {
prog.cleaned = true; prog.cleaned = Some(true);
} }
} }
} }

View file

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

View file

@ -3,6 +3,7 @@ use std::sync::Arc;
use parking_lot::Mutex; use parking_lot::Mutex;
use tokio::sync::mpsc; use tokio::sync::mpsc;
use yazi_dds::Pump; use yazi_dds::Pump;
use yazi_fs::ok_or_not_found;
use yazi_proxy::TasksProxy; use yazi_proxy::TasksProxy;
use yazi_vfs::provider; use yazi_vfs::provider;
@ -20,22 +21,26 @@ impl Hook {
// --- File // --- File
pub(crate) async fn cut(&self, task: HookInOutCut) { pub(crate) async fn cut(&self, task: HookInOutCut) {
let intact = self.ongoing.lock().intact(task.id); if !self.ongoing.lock().intact(task.id) {
if intact { return self.ops.out(task.id, FileOutCut::Clean(Ok(())));
provider::remove_dir_clean(&task.from).await.ok();
Pump::push_move(&task.from, &task.to);
} }
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) { pub(crate) async fn delete(&self, task: HookInOutDelete) {
let intact = self.ongoing.lock().intact(task.id); if !self.ongoing.lock().intact(task.id) {
if intact { return self.ops.out(task.id, FileOutDelete::Clean(Ok(())));
provider::remove_dir_all(&task.target).await.ok();
TasksProxy::update_succeed(&task.target);
Pump::push_delete(&task.target);
} }
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) { pub(crate) async fn trash(&self, task: HookInOutTrash) {

View file

@ -19,13 +19,15 @@ impl From<PluginProgEntry> for TaskSummary {
} }
impl PluginProgEntry { impl PluginProgEntry {
pub fn cooked(self) -> bool { self.state == Some(true) }
pub fn running(self) -> bool { self.state.is_none() } 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 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 } pub fn percent(self) -> Option<f32> { None }
} }

View file

@ -19,13 +19,15 @@ impl From<PreworkProgFetch> for TaskSummary {
} }
impl PreworkProgFetch { impl PreworkProgFetch {
pub fn cooked(self) -> bool { self.state == Some(true) }
pub fn running(self) -> bool { self.state.is_none() } 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 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 } pub fn percent(self) -> Option<f32> { None }
} }
@ -48,13 +50,15 @@ impl From<PreworkProgLoad> for TaskSummary {
} }
impl PreworkProgLoad { impl PreworkProgLoad {
pub fn cooked(self) -> bool { self.state == Some(true) }
pub fn running(self) -> bool { self.state.is_none() } 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 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 } pub fn percent(self) -> Option<f32> { None }
} }
@ -77,13 +81,15 @@ impl From<PreworkProgSize> for TaskSummary {
} }
impl PreworkProgSize { impl PreworkProgSize {
pub fn cooked(self) -> bool { self.done }
pub fn running(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 failed(self) -> bool { false }
pub fn cleaned(self) -> bool { false } pub fn cleaned(self) -> Option<bool> { None }
pub fn percent(self) -> Option<f32> { None } pub fn percent(self) -> Option<f32> { None }
} }

View file

@ -19,13 +19,15 @@ impl From<ProcessProgBlock> for TaskSummary {
} }
impl ProcessProgBlock { impl ProcessProgBlock {
pub fn cooked(self) -> bool { self.state == Some(true) }
pub fn running(self) -> bool { self.state.is_none() } 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 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 } pub fn percent(self) -> Option<f32> { None }
} }
@ -48,13 +50,15 @@ impl From<ProcessProgOrphan> for TaskSummary {
} }
impl ProcessProgOrphan { impl ProcessProgOrphan {
pub fn cooked(self) -> bool { self.state == Some(true) }
pub fn running(self) -> bool { self.state.is_none() } 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 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 } pub fn percent(self) -> Option<f32> { None }
} }
@ -77,13 +81,15 @@ impl From<ProcessProgBg> for TaskSummary {
} }
impl ProcessProgBg { impl ProcessProgBg {
pub fn cooked(self) -> bool { self.state == Some(true) }
pub fn running(self) -> bool { self.state.is_none() } 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 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 } pub fn percent(self) -> Option<f32> { None }
} }

View file

@ -65,6 +65,30 @@ impl From<TaskProg> for TaskSummary {
} }
impl TaskProg { 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 { pub fn running(self) -> bool {
match self { match self {
// File // File
@ -137,7 +161,7 @@ impl TaskProg {
} }
} }
pub fn cleaned(self) -> bool { pub fn cleaned(self) -> Option<bool> {
match self { match self {
// File // File
Self::FileCopy(p) => p.cleaned(), Self::FileCopy(p) => p.cleaned(),

View file

@ -392,8 +392,10 @@ impl Scheduler {
let Some(task) = ongoing.get_mut(op.id) else { continue }; let Some(task) = ongoing.get_mut(op.id) else { continue };
op.out.reduce(task); op.out.reduce(task);
if !task.prog.success() && !task.prog.cleaned() { if !task.prog.cooked() {
continue; continue; // Not cooked yet
} else if task.prog.cleaned() == Some(false) {
continue; // Failed to clean up
} else if let Some(hook) = task.hook.take() { } else if let Some(hook) = task.hook.take() {
micro.try_send(hook, LOW).ok(); micro.try_send(hook, LOW).ok();
} else { } else {

View file

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