refactor: simplify the remote progressive file copier (#3852)

This commit is contained in:
三咲雅 misaki masa 2026-04-04 21:28:57 +08:00 committed by GitHub
parent 0cedbd9c7b
commit 4bb4f37555
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
47 changed files with 486 additions and 353 deletions

View file

@ -1,21 +0,0 @@
use mlua::{ExternalResult, ObjectLike};
use tokio::runtime::Handle;
use yazi_dds::Sendable;
use crate::{Runner, loader::LOADER, plugin::PluginOpt};
impl Runner {
pub async fn entry(&'static self, opt: PluginOpt) -> mlua::Result<()> {
LOADER.ensure(&opt.id, |_| ()).await.into_lua_err()?;
tokio::task::spawn_blocking(move || {
let lua = self.spawn(&opt.id)?;
let job = lua.create_table_from([("args", Sendable::args_to_table(&lua, opt.args)?)])?;
Handle::current()
.block_on(async { LOADER.load(&lua, &opt.id).await?.call_async_method("entry", job).await })
})
.await
.into_lua_err()?
}
}

View file

@ -0,0 +1,19 @@
use mlua::{ExternalResult, ObjectLike};
use tokio::runtime::Handle;
use crate::{Runner, entry::EntryJob, loader::LOADER};
impl Runner {
pub async fn entry(&'static self, job: EntryJob) -> mlua::Result<()> {
LOADER.ensure(&job.plugin, |_| ()).await.into_lua_err()?;
tokio::task::spawn_blocking(move || {
let lua = self.spawn(&job.plugin)?;
Handle::current().block_on(async {
LOADER.load(&lua, &job.plugin).await?.call_async_method("entry", job).await
})
})
.await
.into_lua_err()?
}
}

View file

@ -0,0 +1,22 @@
use hashbrown::HashMap;
use mlua::{IntoLua, Lua, Value};
use yazi_dds::Sendable;
use yazi_shared::{Id, SStr, data::{Data, DataKey}};
#[derive(Clone, Debug, Default)]
pub struct EntryJob {
pub id: Id,
pub args: HashMap<DataKey, Data>,
pub plugin: SStr,
}
impl IntoLua for EntryJob {
fn into_lua(self, lua: &Lua) -> mlua::Result<Value> {
lua
.create_table_from([
("id", self.id.get().into_lua(lua)?),
("args", Sendable::args_to_table(lua, self.args)?.into_lua(lua)?),
])?
.into_lua(lua)
}
}

View file

@ -0,0 +1 @@
yazi_macro::mod_flat!(entry job);

View file

@ -1,6 +1,6 @@
yazi_macro::mod_pub!(fetcher loader plugin previewer);
yazi_macro::mod_pub!(entry fetcher loader preloader previewer);
yazi_macro::mod_flat!(entry preload runner spot);
yazi_macro::mod_flat!(runner spot);
pub static RUNNER: yazi_shared::RoCell<Runner> = yazi_shared::RoCell::new();

View file

@ -1 +0,0 @@
yazi_macro::mod_flat!(option);

View file

@ -1,99 +0,0 @@
use std::{borrow::Cow, fmt, fmt::Debug};
use anyhow::bail;
use dyn_clone::DynClone;
use hashbrown::HashMap;
use mlua::{Lua, Table};
use serde::Deserialize;
use strum::EnumString;
use yazi_shared::{SStr, data::{Data, DataKey}, event::{Action, ActionCow}};
#[derive(Clone, Debug, Default)]
pub struct PluginOpt {
pub id: SStr,
pub args: HashMap<DataKey, Data>,
pub mode: PluginMode,
pub callback: Option<Box<dyn PluginCallback>>,
}
impl TryFrom<ActionCow> for PluginOpt {
type Error = anyhow::Error;
fn try_from(mut a: ActionCow) -> Result<Self, Self::Error> {
let Some(id) = a.take_first::<SStr>().ok().filter(|s| !s.is_empty()) else {
bail!("plugin id cannot be empty");
};
let args = if let Ok(s) = a.second() {
let (words, last) = yazi_shared::shell::unix::split(s, true)?;
Action::parse_args(words, last)?
} else {
Default::default()
};
let mode = a.str("mode").parse().unwrap_or_default();
Ok(Self { id: Self::normalize_id(id), args, mode, callback: a.take_any("callback") })
}
}
impl PluginOpt {
pub fn new_callback(id: impl Into<SStr>, f: impl PluginCallback) -> Self {
Self {
id: Self::normalize_id(id.into()),
mode: PluginMode::Sync,
callback: Some(Box::new(f)),
..Default::default()
}
}
fn normalize_id(s: SStr) -> SStr {
match s {
Cow::Borrowed(s) => s.trim_end_matches(".main").into(),
Cow::Owned(mut s) => {
s.truncate(s.trim_end_matches(".main").len());
s.into()
}
}
}
}
// --- Mode
#[derive(Clone, Copy, Debug, Default, Deserialize, EnumString, Eq, PartialEq)]
#[serde(rename_all = "kebab-case")]
#[strum(serialize_all = "kebab-case")]
pub enum PluginMode {
#[default]
Auto,
Sync,
Async,
}
impl PluginMode {
pub fn auto_then(self, sync: bool) -> Self {
if self != Self::Auto {
return self;
}
if sync { Self::Sync } else { Self::Async }
}
}
// --- Callback
pub trait PluginCallback:
FnOnce(&Lua, Table) -> mlua::Result<()> + Send + Sync + DynClone + 'static
{
}
impl<T> PluginCallback for T where
T: FnOnce(&Lua, Table) -> mlua::Result<()> + Send + Sync + DynClone + 'static
{
}
impl Clone for Box<dyn PluginCallback> {
fn clone(&self) -> Self { dyn_clone::clone_box(&**self) }
}
impl fmt::Debug for dyn PluginCallback {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("PluginCallback").finish_non_exhaustive()
}
}

View file

@ -1,63 +0,0 @@
use mlua::{ExternalError, ExternalResult, HookTriggers, IntoLua, ObjectLike, VmState};
use tokio::{runtime::Handle, select};
use tokio_util::sync::CancellationToken;
use yazi_binding::{Error, File, elements::Rect};
use yazi_config::LAYOUT;
use yazi_dds::Sendable;
use yazi_shared::event::Action;
use crate::{Runner, loader::LOADER};
impl Runner {
pub async fn preload(
&'static self,
action: &'static Action,
file: yazi_fs::File,
ct: CancellationToken,
) -> mlua::Result<(bool, Option<Error>)> {
let ct_ = ct.clone();
tokio::task::spawn_blocking(move || {
let future = async {
LOADER.ensure(&action.name, |_| ()).await.into_lua_err()?;
let lua = self.spawn(&action.name)?;
lua.set_hook(
HookTriggers::new().on_calls().on_returns().every_nth_instruction(2000),
move |_, dbg| {
if ct.is_cancelled() && dbg.source().what != "C" {
Err("Preload task cancelled".into_lua_err())
} else {
Ok(VmState::Continue)
}
},
)?;
let plugin = LOADER.load(&lua, &action.name).await?;
let job = lua.create_table_from([
("area", Rect::from(LAYOUT.get().preview).into_lua(&lua)?),
("args", Sendable::args_to_table_ref(&lua, &action.args)?.into_lua(&lua)?),
("file", File::new(file).into_lua(&lua)?),
("skip", 0.into_lua(&lua)?),
])?;
if ct_.is_cancelled() {
Ok((false, None))
} else {
plugin.call_async_method("preload", job).await
}
};
Handle::current().block_on(async {
select! {
_ = ct_.cancelled() => Ok((false, None)),
r = future => match r {
Err(e) if e.to_string().contains("Preload task cancelled") => Ok((false, None)),
Ok(_) | Err(_) => r,
},
}
})
})
.await
.into_lua_err()?
}
}

View file

@ -0,0 +1,17 @@
use std::sync::Arc;
use thiserror::Error;
#[derive(Clone, Debug, Error)]
pub enum PreloadError {
#[error("Preload task cancelled")]
Cancelled,
#[error("Lua error during preload: {0}")]
Lua(#[from] mlua::Error),
#[error("Unexpected error during preload: {0}")]
Unexpected(Arc<anyhow::Error>),
}
impl From<anyhow::Error> for PreloadError {
fn from(e: anyhow::Error) -> Self { Self::Unexpected(e.into()) }
}

View file

@ -0,0 +1,23 @@
use mlua::{IntoLua, Lua, Value};
use yazi_binding::{File, elements::Rect};
use yazi_config::LAYOUT;
use yazi_dds::Sendable;
use yazi_shared::event::Action;
pub struct PreloadJob {
pub action: &'static Action,
pub file: yazi_fs::File,
}
impl IntoLua for PreloadJob {
fn into_lua(self, lua: &Lua) -> mlua::Result<Value> {
lua
.create_table_from([
("area", Rect::from(LAYOUT.get().preview).into_lua(lua)?),
("args", Sendable::args_to_table_ref(lua, &self.action.args)?.into_lua(lua)?),
("file", File::new(self.file).into_lua(lua)?),
("skip", 0.into_lua(lua)?),
])?
.into_lua(lua)
}
}

View file

@ -0,0 +1 @@
yazi_macro::mod_flat!(error job preloader state);

View file

@ -0,0 +1,64 @@
use mlua::{ExternalError, HookTriggers, ObjectLike, VmState};
use tokio::{runtime::Handle, select, sync::mpsc};
use crate::{Runner, loader::LOADER, preloader::{PreloadError, PreloadJob, PreloadState}};
impl Runner {
pub async fn preload(
&'static self,
job: PreloadJob,
) -> mpsc::Receiver<Result<PreloadState, PreloadError>> {
let (tx, rx) = mpsc::channel(1);
match LOADER.ensure(&job.action.name, |_| ()).await {
Ok(()) => self.preload_do(job, tx),
Err(e) => _ = tx.try_send(Err(e.into())),
};
rx
}
fn preload_do(
&'static self,
job: PreloadJob,
tx: mpsc::Sender<Result<PreloadState, PreloadError>>,
) {
let tx_ = tx.clone();
tokio::task::spawn_blocking(move || {
let future = async {
let lua = self.spawn(&job.action.name)?;
lua.set_hook(
HookTriggers::new().on_calls().on_returns().every_nth_instruction(2000),
move |_, dbg| {
if tx.is_closed() && dbg.source().what != "C" {
Err(PreloadError::Cancelled.into_lua_err())
} else {
Ok(VmState::Continue)
}
},
)?;
let plugin = LOADER.load(&lua, &job.action.name).await?;
if tx_.is_closed() {
Err(PreloadError::Cancelled.into_lua_err())
} else {
plugin.call_async_method("preload", job).await
}
};
Handle::current().block_on(async {
select! {
_ = tx_.closed() => {},
r = future => match r {
Ok(state) => _ = tx_.send(Ok(state)).await,
Err(err) => {
if let Some(e) = err.downcast_ref::<PreloadError>() {
tx_.send(Err(e.clone())).await.ok();
} else {
tx_.send(Err(err.into())).await.ok();
}
},
},
}
})
});
}
}

View file

@ -0,0 +1,14 @@
use mlua::{FromLuaMulti, Lua, MultiValue};
#[derive(Default)]
pub struct PreloadState {
pub complete: bool,
pub error: Option<yazi_binding::Error>,
}
impl FromLuaMulti for PreloadState {
fn from_lua_multi(values: MultiValue, lua: &Lua) -> mlua::Result<Self> {
let (complete, error) = FromLuaMulti::from_lua_multi(values, lua)?;
Ok(Self { complete, error })
}
}