diff --git a/Cargo.lock b/Cargo.lock index 62e1fc2c..5e6572be 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3510,6 +3510,7 @@ dependencies = [ "bitflags 2.8.0", "core-foundation-sys", "dirs", + "foldhash", "futures", "libc", "objc", @@ -3592,6 +3593,7 @@ dependencies = [ "async-priority-channel", "futures", "libc", + "lru", "parking_lot", "scopeguard", "tokio", diff --git a/Cargo.toml b/Cargo.toml index 73e0e93e..5191183b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -18,10 +18,12 @@ clap = { version = "4.5.27", features = [ "derive" ] } core-foundation-sys = "0.8.7" crossterm = { version = "0.28.1", features = [ "event-stream" ] } dirs = "6.0.0" +foldhash = "0.1.4" futures = "0.3.31" globset = "0.4.15" indexmap = { version = "2.7.1", features = [ "serde" ] } libc = "0.2.169" +lru = "0.12.5" md-5 = "0.10.6" mlua = { version = "0.10.2", features = [ "anyhow", "async", "error-send", "lua54", "macros", "serialize" ] } objc = "0.2.7" diff --git a/yazi-config/preset/yazi-default.toml b/yazi-config/preset/yazi-default.toml index 414c7692..ca924498 100644 --- a/yazi-config/preset/yazi-default.toml +++ b/yazi-config/preset/yazi-default.toml @@ -91,7 +91,7 @@ suppress_preload = false fetchers = [ # Mimetype - { id = "mime", name = "*", run = "mime", if = "!mime", prio = "high" }, + { id = "mime", name = "*", run = "mime", prio = "high" }, ] spotters = [ { name = "*/", run = "folder" }, diff --git a/yazi-config/src/plugin/fetcher.rs b/yazi-config/src/plugin/fetcher.rs index fe4a3fd5..1dfeefa3 100644 --- a/yazi-config/src/plugin/fetcher.rs +++ b/yazi-config/src/plugin/fetcher.rs @@ -1,7 +1,7 @@ use std::path::Path; use serde::Deserialize; -use yazi_shared::{Condition, MIME_DIR, event::Cmd}; +use yazi_shared::{MIME_DIR, event::Cmd}; use crate::{Pattern, Priority}; @@ -11,8 +11,6 @@ pub struct Fetcher { pub idx: u8, pub id: String, - #[serde(rename = "if")] - pub if_: Option, pub name: Option, pub mime: Option, pub run: Cmd, @@ -22,9 +20,8 @@ pub struct Fetcher { impl Fetcher { #[inline] - pub fn matches(&self, path: &Path, mime: &str, f: impl Fn(&str) -> bool + Copy) -> bool { - self.if_.as_ref().and_then(|c| c.eval(f)) != Some(false) - && (self.mime.as_ref().is_some_and(|p| p.match_mime(mime)) - || self.name.as_ref().is_some_and(|p| p.match_path(path, mime == MIME_DIR))) + pub fn matches(&self, path: &Path, mime: &str) -> bool { + self.mime.as_ref().is_some_and(|p| p.match_mime(mime)) + || self.name.as_ref().is_some_and(|p| p.match_path(path, mime == MIME_DIR)) } } diff --git a/yazi-config/src/plugin/plugin.rs b/yazi-config/src/plugin/plugin.rs index e713e338..b6c8029d 100644 --- a/yazi-config/src/plugin/plugin.rs +++ b/yazi-config/src/plugin/plugin.rs @@ -20,11 +20,10 @@ impl Plugin { &'b self, path: &'a Path, mime: &'a str, - factor: impl Fn(&str) -> bool + Copy + 'a, ) -> impl Iterator + 'a { let mut seen = HashSet::new(); self.fetchers.iter().filter(move |&f| { - if seen.contains(&f.id) || !f.matches(path, mime, factor) { + if seen.contains(&f.id) || !f.matches(path, mime) { return false; } seen.insert(&f.id); @@ -35,12 +34,7 @@ impl Plugin { pub fn mime_fetchers(&self, files: Vec) -> impl Iterator)> { let mut tasks: [Vec<_>; MAX_PREWORKERS as usize] = Default::default(); for f in files { - let factors = |s: &str| match s { - "dummy" => f.cha.is_dummy(), - _ => false, - }; - - let found = self.fetchers.iter().find(|&g| g.id == "mime" && g.matches(&f.url, "", factors)); + let found = self.fetchers.iter().find(|&g| g.id == "mime" && g.matches(&f.url, "")); if let Some(g) = found { tasks[g.idx as usize].push(f); } else { @@ -53,11 +47,6 @@ impl Plugin { }) } - #[inline] - pub fn fetchers_mask(&self) -> u32 { - self.fetchers.iter().fold(0, |n, f| if f.mime.is_some() { n } else { n | 1 << f.idx as u32 }) - } - pub fn spotter(&self, path: &Path, mime: &str) -> Option<&Spotter> { self.spotters.iter().find(|&p| p.matches(path, mime)) } diff --git a/yazi-core/src/manager/commands/update_mimes.rs b/yazi-core/src/manager/commands/update_mimes.rs index dffb2e68..f3180a44 100644 --- a/yazi-core/src/manager/commands/update_mimes.rs +++ b/yazi-core/src/manager/commands/update_mimes.rs @@ -57,7 +57,9 @@ impl Manager { if repeek { self.peek(false); } - tasks.prework_affected(&affected, &self.mimetype); + tasks.fetch_paged(&affected, &self.mimetype); + tasks.preload_paged(&affected, &self.mimetype); + render!(); } } diff --git a/yazi-core/src/manager/watcher.rs b/yazi-core/src/manager/watcher.rs index 3fea308b..300198fb 100644 --- a/yazi-core/src/manager/watcher.rs +++ b/yazi-core/src/manager/watcher.rs @@ -6,12 +6,10 @@ use parking_lot::RwLock; use tokio::{fs, pin, sync::{mpsc::{self, UnboundedReceiver}, watch}}; use tokio_stream::{StreamExt, wrappers::UnboundedReceiverStream}; use tracing::error; -use yazi_config::PLUGIN; use yazi_dds::Pubsub; use yazi_fs::{Cha, File, Files, FilesOp, mounts::PARTITIONS, realname_unchecked}; -use yazi_plugin::isolate; use yazi_proxy::WATCHER; -use yazi_shared::{RoCell, event::CmdCow, url::Url}; +use yazi_shared::{RoCell, url::Url}; use super::Linked; use crate::tab::Folder; @@ -129,7 +127,6 @@ impl Watcher { let _permit = WATCHER.acquire().await.unwrap(); let mut ops = Vec::with_capacity(urls.len()); - let mut reload = Vec::with_capacity(urls.len()); for u in urls { let Some((parent, urn)) = u.pair() else { continue }; @@ -147,18 +144,10 @@ impl Watcher { continue; } - if !file.is_dir() { - reload.push(file.clone()); - } ops.push(FilesOp::Upserting(parent, HashMap::from_iter([(urn, file)]))); } FilesOp::mutate(ops); - for (fetcher, files) in PLUGIN.mime_fetchers(reload) { - if let Err(e) = isolate::fetch(CmdCow::from(&fetcher.run), files).await { - error!("Fetch mime failed in watcher: {e}"); - } - } } } diff --git a/yazi-core/src/tasks/preload.rs b/yazi-core/src/tasks/preload.rs index 58b76047..f5bf79d5 100644 --- a/yazi-core/src/tasks/preload.rs +++ b/yazi-core/src/tasks/preload.rs @@ -9,18 +9,12 @@ impl Tasks { let mut loaded = self.scheduler.prework.loaded.lock(); let mut tasks: [Vec<_>; MAX_PREWORKERS as usize] = Default::default(); for f in paged { - let mime = mimetype.by_file(f).unwrap_or_default(); - let factors = |s: &str| match s { - "mime" => !mime.is_empty(), - "dummy" => f.cha.is_dummy(), - _ => false, - }; - - for g in PLUGIN.fetchers(&f.url, mime, factors) { - match loaded.get_mut(&f.url) { + let hash = f.hash(); + for g in PLUGIN.fetchers(&f.url, mimetype.by_file(f).unwrap_or_default()) { + match loaded.get_mut(&hash) { Some(n) if *n & (1 << g.idx) != 0 => continue, Some(n) => *n |= 1 << g.idx, - None => _ = loaded.insert(f.url_owned(), 1 << g.idx), + None => _ = loaded.put(hash, 1 << g.idx), } tasks[g.idx as usize].push(f.clone()); } @@ -37,31 +31,18 @@ impl Tasks { pub fn preload_paged(&self, paged: &[File], mimetype: &Mimetype) { let mut loaded = self.scheduler.prework.loaded.lock(); for f in paged { - let mime = mimetype.by_file(f).unwrap_or_default(); - for p in PLUGIN.preloaders(&f.url, mime) { - match loaded.get_mut(&f.url) { + let hash = f.hash(); + for p in PLUGIN.preloaders(&f.url, mimetype.by_file(f).unwrap_or_default()) { + match loaded.get_mut(&hash) { Some(n) if *n & (1 << p.idx) != 0 => continue, Some(n) => *n |= 1 << p.idx, - None => _ = loaded.insert(f.url_owned(), 1 << p.idx), + None => _ = loaded.put(hash, 1 << p.idx), } self.scheduler.preload_paged(p, f); } } } - pub fn prework_affected(&self, affected: &[File], mimetype: &Mimetype) { - let mask = PLUGIN.fetchers_mask(); - { - let mut loaded = self.scheduler.prework.loaded.lock(); - for f in affected { - loaded.get_mut(&f.url).map(|n| *n &= mask); - } - } - - self.fetch_paged(affected, mimetype); - self.preload_paged(affected, mimetype); - } - pub fn prework_sorted(&self, targets: &Files) { if targets.sorter().by != SortBy::Size { return; diff --git a/yazi-fm/src/lives/folder.rs b/yazi-fm/src/lives/folder.rs index 92270d57..1d8a3da1 100644 --- a/yazi-fm/src/lives/folder.rs +++ b/yazi-fm/src/lives/folder.rs @@ -2,7 +2,7 @@ use std::ops::{Deref, Range}; use mlua::{AnyUserData, Lua, UserData, UserDataFields}; use yazi_config::LAYOUT; -use yazi_plugin::{bindings::Cast, url::Url}; +use yazi_plugin::url::Url; use super::{File, Files, Lives}; @@ -50,7 +50,7 @@ impl Folder { impl UserData for Folder { fn add_fields>(fields: &mut F) { - fields.add_field_method_get("cwd", |lua, me| Url::cast(lua, me.url.to_owned())); + fields.add_field_method_get("cwd", |_, me| Ok(Url(me.url.to_owned()))); fields.add_field_method_get("files", |_, me| Files::make(0..me.files.len(), me, me.tab())); fields.add_field_method_get("stage", |lua, me| lua.create_any_userdata(me.stage)); fields.add_field_method_get("window", |_, me| Files::make(me.window.clone(), me, me.tab())); diff --git a/yazi-fm/src/lives/selected.rs b/yazi-fm/src/lives/selected.rs index c2af5465..a6c7bb3c 100644 --- a/yazi-fm/src/lives/selected.rs +++ b/yazi-fm/src/lives/selected.rs @@ -2,7 +2,7 @@ use std::ops::Deref; use indexmap::{IndexMap, map::Keys}; use mlua::{AnyUserData, IntoLuaMulti, MetaMethod, UserData, UserDataMethods, UserDataRefMut}; -use yazi_plugin::{bindings::Cast, url::Url}; +use yazi_plugin::url::Url; use super::{Iter, Lives}; @@ -35,7 +35,7 @@ impl UserData for Selected { let iter = lua.create_function( |lua, mut iter: UserDataRefMut, _>>| { if let Some(next) = iter.next() { - (next.0, Url::cast(lua, next.1.clone())?).into_lua_multi(lua) + (next.0, Url(next.1.clone())).into_lua_multi(lua) } else { ().into_lua_multi(lua) } diff --git a/yazi-fm/src/lives/yanked.rs b/yazi-fm/src/lives/yanked.rs index e0316dc4..b5bd1f9c 100644 --- a/yazi-fm/src/lives/yanked.rs +++ b/yazi-fm/src/lives/yanked.rs @@ -1,7 +1,7 @@ use std::{collections::hash_set, ops::Deref}; use mlua::{AnyUserData, IntoLuaMulti, MetaMethod, UserData, UserDataFields, UserDataMethods, UserDataRefMut}; -use yazi_plugin::{bindings::Cast, url::Url}; +use yazi_plugin::url::Url; use super::{Iter, Lives}; @@ -37,7 +37,7 @@ impl UserData for Yanked { let iter = lua.create_function( |lua, mut iter: UserDataRefMut, _>>| { if let Some(next) = iter.next() { - (next.0, Url::cast(lua, next.1.clone())?).into_lua_multi(lua) + (next.0, Url(next.1.clone())).into_lua_multi(lua) } else { ().into_lua_multi(lua) } diff --git a/yazi-fs/Cargo.toml b/yazi-fs/Cargo.toml index 490e8382..df3f3c60 100644 --- a/yazi-fs/Cargo.toml +++ b/yazi-fs/Cargo.toml @@ -18,6 +18,7 @@ anyhow = { workspace = true } arc-swap = "1.7.1" bitflags = { workspace = true } dirs = { workspace = true } +foldhash = { workspace = true } futures = { workspace = true } parking_lot = { workspace = true } regex = { workspace = true } diff --git a/yazi-fs/src/file.rs b/yazi-fs/src/file.rs index 01a3c982..2c1467ee 100644 --- a/yazi-fs/src/file.rs +++ b/yazi-fs/src/file.rs @@ -1,4 +1,4 @@ -use std::{ffi::OsStr, fs::{FileType, Metadata}, ops::Deref}; +use std::{ffi::OsStr, fs::{FileType, Metadata}, hash::{BuildHasher, Hash, Hasher}, ops::Deref}; use anyhow::Result; use tokio::fs; @@ -48,6 +48,19 @@ impl File { } } + #[inline] + pub fn hash(&self) -> u64 { + let mut h = foldhash::fast::FixedState::default().build_hasher(); + self.url.hash(&mut h); + h.write_u8(0); + self.cha.len.hash(&mut h); + h.write_u8(0); + self.cha.mtime.hash(&mut h); + h.write_u8(0); + self.cha.btime.hash(&mut h); + h.finish() + } + #[inline] pub fn rebase(&self, parent: &Url) -> Self { Self { diff --git a/yazi-plugin/preset/plugins/font.lua b/yazi-plugin/preset/plugins/font.lua index 935e7dbe..1aae3fe4 100644 --- a/yazi-plugin/preset/plugins/font.lua +++ b/yazi-plugin/preset/plugins/font.lua @@ -4,7 +4,12 @@ local M = {} function M:peek(job) local start, cache = os.clock(), ya.file_cache(job) - if not cache or self:preload(job) ~= 1 then + if not cache then + return + end + + local ok, err = self:preload(job) + if not ok or err then return end @@ -18,7 +23,7 @@ function M:seek() end function M:preload(job) local cache = ya.file_cache(job) if not cache or fs.cha(cache) then - return 1 + return true end local status, err = Command("magick"):args({ @@ -40,10 +45,9 @@ function M:preload(job) }):status() if status then - return status.success and 1 or 2 + return status.success else - ya.err("Failed to start `magick`, error: " .. err) - return 0 + return true, Err("Failed to start `magick`, error: %s", err) end end diff --git a/yazi-plugin/preset/plugins/image.lua b/yazi-plugin/preset/plugins/image.lua index b76fd51a..f20577e8 100644 --- a/yazi-plugin/preset/plugins/image.lua +++ b/yazi-plugin/preset/plugins/image.lua @@ -16,10 +16,10 @@ function M:seek() end function M:preload(job) local cache = ya.file_cache(job) if not cache or fs.cha(cache) then - return 1 + return true end - return ya.image_precache(job.file.url, cache) and 1 or 2 + return ya.image_precache(job.file.url, cache) end function M:spot(job) diff --git a/yazi-plugin/preset/plugins/magick.lua b/yazi-plugin/preset/plugins/magick.lua index 19d7f640..01d41830 100644 --- a/yazi-plugin/preset/plugins/magick.lua +++ b/yazi-plugin/preset/plugins/magick.lua @@ -2,7 +2,12 @@ local M = {} function M:peek(job) local start, cache = os.clock(), ya.file_cache(job) - if not cache or self:preload(job) ~= 1 then + if not cache then + return + end + + local ok, err = self:preload(job) + if not ok or err then return end @@ -16,7 +21,7 @@ function M:seek() end function M:preload(job) local cache = ya.file_cache(job) if not cache or fs.cha(cache) then - return 1 + return true end local status, err = Command("magick") @@ -36,10 +41,9 @@ function M:preload(job) :status() if status then - return status.success and 1 or 2 + return status.success else - ya.err("Failed to start `magick`, error: " .. err) - return 0 + return true, Err("Failed to start `magick`, error: %s", err) end end diff --git a/yazi-plugin/preset/plugins/mime.lua b/yazi-plugin/preset/plugins/mime.lua index 43ebc92e..70cfb63a 100644 --- a/yazi-plugin/preset/plugins/mime.lua +++ b/yazi-plugin/preset/plugins/mime.lua @@ -18,8 +18,7 @@ function M:fetch(job) local cmd = os.getenv("YAZI_FILE_ONE") or "file" local child, err = Command(cmd):args({ "-bL", "--mime-type", "--" }):args(urls):stdout(Command.PIPED):spawn() if not child then - ya.err(string.format("Failed to start `%s`, error: %s", cmd, err)) - return 0 + return true, Err("Failed to start `%s`, error: %s", cmd, err) end local updates, last = {}, ya.time() @@ -33,7 +32,7 @@ function M:fetch(job) end end - local i, j, valid = 1, 0, nil + local i, valid, state = 1, nil, {} repeat local line, event = child:read_line_with { timeout = 300 } if event == 3 then @@ -45,8 +44,10 @@ function M:fetch(job) valid = match_mimetype(line) if valid then - j, updates[urls[i]] = j + 1, valid + updates[urls[i]], state[i] = valid, true flush(false) + else + state[i] = false end i = i + 1 @@ -54,7 +55,7 @@ function M:fetch(job) until i > #urls flush(true) - return j == #urls and 3 or 2 + return state end return M diff --git a/yazi-plugin/preset/plugins/noop.lua b/yazi-plugin/preset/plugins/noop.lua index c52e8bcf..0ec794ee 100644 --- a/yazi-plugin/preset/plugins/noop.lua +++ b/yazi-plugin/preset/plugins/noop.lua @@ -4,9 +4,9 @@ function M:peek() end function M:seek() end -function M:fetch() return 1 end +function M:fetch() return true end -function M:preload() return 1 end +function M:preload() return true end function M:spot() end diff --git a/yazi-plugin/preset/plugins/pdf.lua b/yazi-plugin/preset/plugins/pdf.lua index 32507130..5ecf43da 100644 --- a/yazi-plugin/preset/plugins/pdf.lua +++ b/yazi-plugin/preset/plugins/pdf.lua @@ -2,7 +2,12 @@ local M = {} function M:peek(job) local start, cache = os.clock(), ya.file_cache(job) - if not cache or self:preload(job) ~= 1 then + if not cache then + return + end + + local ok, err = self:preload(job) + if not ok or err then return end @@ -22,10 +27,10 @@ end function M:preload(job) local cache = ya.file_cache(job) if not cache or fs.cha(cache) then - return 1 + return true end - local output = Command("pdftoppm") + local output, err = Command("pdftoppm") :args({ "-singlefile", "-jpeg", @@ -40,16 +45,16 @@ function M:preload(job) :output() if not output then - return 0 + return true, Err("Failed to start `pdftoppm`, error: %s", err) elseif not output.status.success then local pages = tonumber(output.stderr:match("the last page %((%d+)%)")) or 0 if job.skip > 0 and pages > 0 then ya.manager_emit("peek", { math.max(0, pages - 1), only_if = job.file.url, upper_bound = true }) end - return 0 + return true, Err("Failed to convert PDF to image, stderr: %s", output.stderr) end - return fs.write(cache, output.stdout) and 1 or 2 + return fs.write(cache, output.stdout) end return M diff --git a/yazi-plugin/preset/plugins/video.lua b/yazi-plugin/preset/plugins/video.lua index 4ecb757d..36afd310 100644 --- a/yazi-plugin/preset/plugins/video.lua +++ b/yazi-plugin/preset/plugins/video.lua @@ -2,7 +2,12 @@ local M = {} function M:peek(job) local start, cache = os.clock(), ya.file_cache(job) - if not cache or self:preload(job) ~= 1 then + if not cache then + return + end + + local ok, err = self:preload(job) + if not ok or err then return end @@ -25,25 +30,24 @@ function M:preload(job) local percent = 5 + job.skip if percent > 95 then ya.manager_emit("peek", { 90, only_if = job.file.url, upper_bound = true }) - return 2 + return false end local cache = ya.file_cache(job) if not cache then - return 1 + return true end local cha = fs.cha(cache) if cha and cha.len > 0 then - return 1 + return true end local meta, err = self.list_meta(job.file.url, "format=duration") if not meta then - ya.err(tostring(err)) - return 0 + return true, err elseif not meta.format.duration then - return 0 + return true, Err("Failed to get video duration") end local ss = math.floor(meta.format.duration * percent / 100) @@ -62,10 +66,9 @@ function M:preload(job) }):status() if status then - return status.success and 1 or 2 + return status.success else - ya.err("Failed to start `ffmpeg`, error: " .. err) - return 0 + return true, Err("Failed to start `ffmpeg`, error: %s", err) end end @@ -115,14 +118,14 @@ function M.list_meta(url, entries) local output, err = Command("ffprobe"):args({ "-v", "quiet", "-show_entries", entries, "-of", "json=c=1", tostring(url) }):output() if not output then - return nil, Err("Failed to start `ffprobe`, error: " .. err) + return nil, Err("Failed to start `ffprobe`, error: %s", err) end local t = ya.json_decode(output.stdout) if not t then - return nil, Err("Failed to decode `ffprobe` output: " .. output.stdout) + return nil, Err("Failed to decode `ffprobe` output: %s", output.stdout) elseif type(t) ~= "table" then - return nil, Err("Invalid `ffprobe` output: " .. output.stdout) + return nil, Err("Invalid `ffprobe` output: %s", output.stdout) end t.format = t.format or {} diff --git a/yazi-plugin/src/bindings/bindings.rs b/yazi-plugin/src/bindings/bindings.rs deleted file mode 100644 index 91caff8d..00000000 --- a/yazi-plugin/src/bindings/bindings.rs +++ /dev/null @@ -1,5 +0,0 @@ -use mlua::Lua; - -pub trait Cast { - fn cast(lua: &Lua, data: T) -> mlua::Result; -} diff --git a/yazi-plugin/src/bindings/mod.rs b/yazi-plugin/src/bindings/mod.rs index 450fc82b..2d4a0019 100644 --- a/yazi-plugin/src/bindings/mod.rs +++ b/yazi-plugin/src/bindings/mod.rs @@ -1,3 +1,3 @@ #![allow(clippy::module_inception)] -yazi_macro::mod_flat!(bindings cha chan icon image input layer mouse permit range window); +yazi_macro::mod_flat!(cha chan icon image input layer mouse permit range window); diff --git a/yazi-plugin/src/config/plugin.rs b/yazi-plugin/src/config/plugin.rs index aa9c2f91..4b15bd3a 100644 --- a/yazi-plugin/src/config/plugin.rs +++ b/yazi-plugin/src/config/plugin.rs @@ -8,12 +8,7 @@ pub(super) struct Plugin; impl Plugin { pub(super) fn fetchers(lua: &Lua) -> mlua::Result { lua.create_function(|lua, (file, mime): (FileRef, mlua::String)| { - let factors = |s: &str| match s { - "mime" => !mime.as_bytes().is_empty(), - "dummy" => file.cha.is_dummy(), - _ => false, - }; - lua.create_sequence_from(PLUGIN.fetchers(&file.url, &mime.to_str()?, factors).map(Fetcher)) + lua.create_sequence_from(PLUGIN.fetchers(&file.url, &mime.to_str()?).map(Fetcher)) }) } diff --git a/yazi-plugin/src/elements/line.rs b/yazi-plugin/src/elements/line.rs index ea8ae592..48ed4e57 100644 --- a/yazi-plugin/src/elements/line.rs +++ b/yazi-plugin/src/elements/line.rs @@ -1,7 +1,7 @@ use std::mem; use ansi_to_tui::IntoText; -use mlua::{AnyUserData, ExternalError, ExternalResult, FromLua, IntoLua, Lua, MetaMethod, Table, UserData, UserDataMethods, Value}; +use mlua::{AnyUserData, ExternalError, ExternalResult, IntoLua, Lua, MetaMethod, Table, UserData, UserDataMethods, Value}; use unicode_width::UnicodeWidthChar; use super::Span; @@ -12,7 +12,6 @@ const RIGHT: u8 = 2; const EXPECTED: &str = "expected a string, Span, Line, or a table of them"; -#[derive(Clone, FromLua)] pub struct Line(pub(super) ratatui::text::Line<'static>); impl Line { diff --git a/yazi-plugin/src/elements/row.rs b/yazi-plugin/src/elements/row.rs index 39922b7c..5ac080e9 100644 --- a/yazi-plugin/src/elements/row.rs +++ b/yazi-plugin/src/elements/row.rs @@ -1,8 +1,10 @@ -use mlua::{AnyUserData, FromLua, Lua, MetaMethod, Table, UserData}; +use mlua::{AnyUserData, ExternalError, Lua, MetaMethod, Table, UserData, Value}; use super::Cell; -#[derive(Clone, Debug, Default, FromLua)] +const EXPECTED: &str = "expected a Row"; + +#[derive(Clone, Debug, Default)] pub struct Row { pub(super) cells: Vec, height: u16, @@ -34,6 +36,23 @@ impl From for ratatui::widgets::Row<'static> { } } +impl TryFrom for Row { + type Error = mlua::Error; + + fn try_from(value: Value) -> Result { + Ok(match value { + Value::UserData(ud) => { + if let Ok(row) = ud.take() { + row + } else { + Err(EXPECTED.into_lua_err())? + } + } + _ => Err(EXPECTED.into_lua_err())?, + }) + } +} + impl UserData for Row { fn add_methods>(methods: &mut M) { crate::impl_style_method!(methods, style); diff --git a/yazi-plugin/src/elements/span.rs b/yazi-plugin/src/elements/span.rs index 139c4383..fd9bc909 100644 --- a/yazi-plugin/src/elements/span.rs +++ b/yazi-plugin/src/elements/span.rs @@ -1,9 +1,8 @@ -use mlua::{ExternalError, FromLua, Lua, MetaMethod, Table, UserData, UserDataMethods, Value}; +use mlua::{ExternalError, Lua, MetaMethod, Table, UserData, UserDataMethods, Value}; use unicode_width::UnicodeWidthChar; const EXPECTED: &str = "expected a string or Span"; -#[derive(Clone, FromLua)] pub struct Span(pub(super) ratatui::text::Span<'static>); impl Span { diff --git a/yazi-plugin/src/elements/table.rs b/yazi-plugin/src/elements/table.rs index 80861e4c..d31d9d04 100644 --- a/yazi-plugin/src/elements/table.rs +++ b/yazi-plugin/src/elements/table.rs @@ -1,9 +1,11 @@ -use mlua::{AnyUserData, Lua, MetaMethod, UserData, Value}; +use mlua::{AnyUserData, ExternalError, Lua, MetaMethod, UserData, Value}; use ratatui::widgets::StatefulWidget; use super::{Area, Row}; use crate::elements::{Constraint, Style}; +const EXPECTED: &str = "expected a table of Rows"; + // --- Table #[derive(Clone, Debug, Default)] pub struct Table { @@ -31,7 +33,12 @@ pub struct Table { impl Table { pub fn compose(lua: &Lua) -> mlua::Result { - let new = lua.create_function(|_, (_, rows): (mlua::Table, Vec)| { + let new = lua.create_function(|_, (_, seq): (mlua::Table, mlua::Table)| { + let mut rows = Vec::with_capacity(seq.raw_len()); + for v in seq.sequence_values::() { + rows.push(Row::try_from(v?).map_err(|_| EXPECTED.into_lua_err())?); + } + Ok(Self { rows, ..Default::default() }) })?; @@ -96,12 +103,12 @@ impl UserData for Table { fn add_methods>(methods: &mut M) { crate::impl_area_method!(methods); - methods.add_function_mut("header", |_, (ud, row): (AnyUserData, Row)| { - ud.borrow_mut::()?.header = Some(row.into()); + methods.add_function_mut("header", |_, (ud, value): (AnyUserData, Value)| { + ud.borrow_mut::()?.header = Some(Row::try_from(value)?.into()); Ok(ud) }); - methods.add_function_mut("footer", |_, (ud, row): (AnyUserData, Row)| { - ud.borrow_mut::()?.footer = Some(row.into()); + methods.add_function_mut("footer", |_, (ud, value): (AnyUserData, Value)| { + ud.borrow_mut::()?.footer = Some(Row::try_from(value)?.into()); Ok(ud) }); methods.add_function_mut("widths", |_, (ud, widths): (AnyUserData, Vec)| { diff --git a/yazi-plugin/src/elements/text.rs b/yazi-plugin/src/elements/text.rs index 2f905015..d9014dbe 100644 --- a/yazi-plugin/src/elements/text.rs +++ b/yazi-plugin/src/elements/text.rs @@ -1,7 +1,7 @@ use std::mem; use ansi_to_tui::IntoText; -use mlua::{ExternalError, ExternalResult, FromLua, IntoLua, Lua, MetaMethod, Table, UserData, Value}; +use mlua::{ExternalError, ExternalResult, IntoLua, Lua, MetaMethod, Table, UserData, Value}; use ratatui::widgets::Widget; use super::{Area, Line, Span}; @@ -18,7 +18,7 @@ pub const WRAP_TRIM: u8 = 2; const EXPECTED: &str = "expected a string, Line, Span, or a table of them"; -#[derive(Clone, Debug, Default, FromLua)] +#[derive(Clone, Debug, Default)] pub struct Text { pub area: Area, diff --git a/yazi-plugin/src/error.rs b/yazi-plugin/src/error.rs index a31a2d1d..0a9598b2 100644 --- a/yazi-plugin/src/error.rs +++ b/yazi-plugin/src/error.rs @@ -1,6 +1,8 @@ -use std::borrow::Cow; +use std::{borrow::Cow, fmt::Display}; -use mlua::{ExternalError, Lua, MetaMethod, UserData, UserDataFields, UserDataMethods, Value}; +use mlua::{ExternalError, FromLua, Lua, MetaMethod, UserData, UserDataFields, UserDataMethods, Value}; + +const EXPECTED: &str = "expected a Error"; pub enum Error { Io(std::io::Error), @@ -24,6 +26,25 @@ impl Error { } } +impl Display for Error { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + Error::Io(e) => write!(f, "{e}"), + Error::Serde(e) => write!(f, "{e}"), + Error::Custom(s) => write!(f, "{s}"), + } + } +} + +impl FromLua for Error { + fn from_lua(value: Value, _: &Lua) -> mlua::Result { + match value { + Value::UserData(ud) => ud.take(), + _ => Err(EXPECTED.into_lua_err()), + } + } +} + impl UserData for Error { fn add_fields>(fields: &mut F) { fields.add_field_method_get("code", |_, me| { diff --git a/yazi-plugin/src/file/file.rs b/yazi-plugin/src/file/file.rs index dbabc8cc..bf2f5a8b 100644 --- a/yazi-plugin/src/file/file.rs +++ b/yazi-plugin/src/file/file.rs @@ -1,10 +1,10 @@ -use mlua::{AnyUserData, Lua, Table, UserDataRef}; +use mlua::{AnyUserData, IntoLua, Lua, Table, UserDataRef}; -use crate::{bindings::{Cast, Cha}, impl_file_fields, impl_file_methods}; +use crate::{bindings::Cha, impl_file_fields, impl_file_methods}; pub type FileRef = UserDataRef; -pub struct File; +pub struct File(pub yazi_fs::File); impl File { #[inline] @@ -18,17 +18,19 @@ impl File { pub fn install(lua: &Lua) -> mlua::Result<()> { lua.globals().raw_set( "File", - lua.create_function(|lua, t: Table| { - Self::cast(lua, yazi_fs::File { + lua.create_function(|_, t: Table| { + Ok(Self(yazi_fs::File { url: t.raw_get::("url")?.take()?, cha: *t.raw_get::("cha")?, ..Default::default() - }) + })) })?, ) } } -impl> Cast for File { - fn cast(lua: &Lua, data: T) -> mlua::Result { lua.create_any_userdata(data.into()) } +impl IntoLua for File { + fn into_lua(self, lua: &Lua) -> mlua::Result { + lua.create_any_userdata(self.0)?.into_lua(lua) + } } diff --git a/yazi-plugin/src/fs/fs.rs b/yazi-plugin/src/fs/fs.rs index d9eab082..c2543491 100644 --- a/yazi-plugin/src/fs/fs.rs +++ b/yazi-plugin/src/fs/fs.rs @@ -3,7 +3,7 @@ use mlua::{ExternalError, ExternalResult, Function, IntoLua, IntoLuaMulti, Lua, use tokio::fs; use yazi_fs::{mounts::PARTITIONS, remove_dir_clean}; -use crate::{Composer, Error, bindings::{Cast, Cha}, file::File, url::{Url, UrlRef}}; +use crate::{Composer, Error, bindings::Cha, file::File, url::{Url, UrlRef}}; pub fn compose(lua: &Lua) -> mlua::Result { Composer::make(lua, 10, |lua, key| { @@ -24,7 +24,7 @@ pub fn compose(lua: &Lua) -> mlua::Result { fn cwd(lua: &Lua) -> mlua::Result { lua.create_function(|lua, ()| match std::env::current_dir() { - Ok(p) => (Url::cast(lua, p)?, Value::Nil).into_lua_multi(lua), + Ok(p) => (Url::from(p), Value::Nil).into_lua_multi(lua), Err(e) => (Value::Nil, Error::Io(e)).into_lua_multi(lua), }) } @@ -129,7 +129,7 @@ fn read_dir(lua: &Lua) -> mlua::Result { } else { yazi_fs::File::from_dummy(url, next.file_type().await.ok()) }; - files.push(File::cast(&lua, file)?); + files.push(File(file)); } let tbl = lua.create_table_with_capacity(files.len(), 0)?; @@ -144,7 +144,7 @@ fn read_dir(lua: &Lua) -> mlua::Result { fn unique_name(lua: &Lua) -> mlua::Result { lua.create_async_function(|lua, url: UrlRef| async move { match yazi_fs::unique_name(url.clone(), async { false }).await { - Ok(u) => (Url::cast(&lua, u)?, Value::Nil).into_lua_multi(&lua), + Ok(u) => (Url(u), Value::Nil).into_lua_multi(&lua), Err(e) => (Value::Nil, Error::Io(e)).into_lua_multi(&lua), } }) diff --git a/yazi-plugin/src/isolate/fetch.rs b/yazi-plugin/src/isolate/fetch.rs index 32c9252a..24f2ed53 100644 --- a/yazi-plugin/src/isolate/fetch.rs +++ b/yazi-plugin/src/isolate/fetch.rs @@ -1,15 +1,18 @@ -use mlua::{ExternalError, ExternalResult, IntoLua, ObjectLike, Table}; +use mlua::{ExternalError, ExternalResult, FromLua, IntoLua, Lua, ObjectLike, Table, Value}; use tokio::runtime::Handle; use yazi_config::LAYOUT; use yazi_dds::Sendable; use yazi_shared::event::CmdCow; use super::slim_lua; -use crate::{bindings::Cast, elements::Rect, file::File, loader::LOADER}; +use crate::{Error, elements::Rect, file::File, loader::LOADER}; -pub async fn fetch(cmd: CmdCow, files: Vec) -> mlua::Result { +pub async fn fetch( + cmd: CmdCow, + files: Vec, +) -> mlua::Result<(FetchState, Option)> { if files.is_empty() { - return Ok(1); + return Ok((FetchState::Bool(true), None)); } LOADER.ensure(&cmd.name).await.into_lua_err()?; @@ -21,22 +24,45 @@ pub async fn fetch(cmd: CmdCow, files: Vec) -> mlua::Result { return Err("unloaded plugin".into_lua_err()); }; - let files = - lua.create_sequence_from(files.into_iter().filter_map(|f| File::cast(&lua, f).ok()))?; - - if files.raw_len() == 0 { - return Ok(1); - } - Handle::current().block_on(plugin.call_async_method( "fetch", lua.create_table_from([ ("area", Rect::from(LAYOUT.get().preview).into_lua(&lua)?), ("args", Sendable::args_to_table_ref(&lua, &cmd.args)?.into_lua(&lua)?), - ("files", files.into_lua(&lua)?), + ("files", lua.create_sequence_from(files.into_iter().map(File))?.into_lua(&lua)?), ])?, )) }) .await .into_lua_err()? } + +// --- State +pub enum FetchState { + Bool(bool), + Vec(Vec), +} + +impl FetchState { + #[inline] + pub fn get(&self, idx: usize) -> bool { + match self { + Self::Bool(b) => *b, + Self::Vec(v) => v.get(idx).copied().unwrap_or(false), + } + } +} + +impl FromLua for FetchState { + fn from_lua(value: Value, _: &Lua) -> mlua::Result { + Ok(match value { + Value::Boolean(b) => Self::Bool(b), + Value::Table(tbl) => Self::Vec(tbl.sequence_values().collect::>()?), + _ => Err(mlua::Error::FromLuaConversionError { + from: value.type_name(), + to: "FetchState".to_owned(), + message: Some("expected a boolean or a table of booleans".to_owned()), + })?, + }) + } +} diff --git a/yazi-plugin/src/isolate/peek.rs b/yazi-plugin/src/isolate/peek.rs index 210831a7..5883d154 100644 --- a/yazi-plugin/src/isolate/peek.rs +++ b/yazi-plugin/src/isolate/peek.rs @@ -10,7 +10,7 @@ use yazi_proxy::{AppProxy, options::{PluginCallback, PluginOpt}}; use yazi_shared::event::Cmd; use super::slim_lua; -use crate::{bindings::Cast, elements::Rect, file::File, loader::LOADER}; +use crate::{elements::Rect, file::File, loader::LOADER}; pub fn peek( cmd: &'static Cmd, @@ -46,7 +46,7 @@ pub fn peek( let job = lua.create_table_from([ ("area", Rect::from(LAYOUT.get().preview).into_lua(&lua)?), ("args", Sendable::args_to_table_ref(&lua, &cmd.args)?.into_lua(&lua)?), - ("file", File::cast(&lua, file)?.into_lua(&lua)?), + ("file", File(file).into_lua(&lua)?), ("mime", mime.into_lua(&lua)?), ("skip", skip.into_lua(&lua)?), ])?; @@ -76,7 +76,7 @@ pub fn peek_sync(cmd: &'static Cmd, file: yazi_fs::File, mime: Cow<'static, str> let job = lua.create_table_from([ ("area", Rect::from(LAYOUT.get().preview).into_lua(lua)?), ("args", Sendable::args_to_table_ref(lua, &cmd.args)?.into_lua(lua)?), - ("file", File::cast(lua, file)?.into_lua(lua)?), + ("file", File(file).into_lua(lua)?), ("mime", mime.into_lua(lua)?), ("skip", skip.into_lua(lua)?), ])?; diff --git a/yazi-plugin/src/isolate/preload.rs b/yazi-plugin/src/isolate/preload.rs index 8ff0a448..2080c41f 100644 --- a/yazi-plugin/src/isolate/preload.rs +++ b/yazi-plugin/src/isolate/preload.rs @@ -1,13 +1,16 @@ -use mlua::{ExternalError, ExternalResult, IntoLua, ObjectLike, Table}; +use mlua::{ExternalError, ExternalResult, IntoLua, ObjectLike, Table, Value}; use tokio::runtime::Handle; use yazi_config::LAYOUT; use yazi_dds::Sendable; use yazi_shared::event::Cmd; use super::slim_lua; -use crate::{bindings::Cast, elements::Rect, file::File, loader::LOADER}; +use crate::{Error, deprecate, elements::Rect, file::File, loader::LOADER}; -pub async fn preload(cmd: &'static Cmd, file: yazi_fs::File) -> mlua::Result { +pub async fn preload( + cmd: &'static Cmd, + file: yazi_fs::File, +) -> mlua::Result<(bool, Option)> { LOADER.ensure(&cmd.name).await.into_lua_err()?; tokio::task::spawn_blocking(move || { @@ -21,11 +24,29 @@ pub async fn preload(cmd: &'static Cmd, file: yazi_fs::File) -> mlua::Result let job = lua.create_table_from([ ("area", Rect::from(LAYOUT.get().preview).into_lua(&lua)?), ("args", Sendable::args_to_table_ref(&lua, &cmd.args)?.into_lua(&lua)?), - ("file", File::cast(&lua, file)?.into_lua(&lua)?), + ("file", File(file).into_lua(&lua)?), ("skip", 0.into_lua(&lua)?), ])?; - Handle::current().block_on(plugin.call_async_method("preload", job)) + let (ok, mut err): (Value, Option) = + Handle::current().block_on(plugin.call_async_method("preload", job))?; + + // TODO: remove this + let ok = match ok { + Value::Boolean(b) => b, + Value::Integer(n) => { + deprecate!(lua, "The integer return value of `preload()` has been deprecated since 25.01.27, please use the new `(bool, error)` instead, in your {}. + +See #2253 for more information: https://github.com/sxyazi/yazi/pull/2253"); + if n as u8 & 1 == 0 { + err = Some(Error::Custom(format!("Returned {n} when running the preloader"))); + } + n as u8 & 1 == 1 + }, + _ => Err("The first return value of `preload()` must be a bool".into_lua_err())?, + }; + + Ok((ok, err)) }) .await .into_lua_err()? diff --git a/yazi-plugin/src/isolate/seek.rs b/yazi-plugin/src/isolate/seek.rs index 1941e8b0..a378c06d 100644 --- a/yazi-plugin/src/isolate/seek.rs +++ b/yazi-plugin/src/isolate/seek.rs @@ -3,12 +3,12 @@ use yazi_config::LAYOUT; use yazi_proxy::{AppProxy, options::{PluginCallback, PluginOpt}}; use yazi_shared::event::Cmd; -use crate::{bindings::Cast, elements::Rect, file::File}; +use crate::{elements::Rect, file::File}; pub fn seek_sync(cmd: &'static Cmd, file: yazi_fs::File, units: i16) { let cb: PluginCallback = Box::new(move |lua, plugin| { let job = lua.create_table_from([ - ("file", File::cast(lua, file)?.into_lua(lua)?), + ("file", File(file).into_lua(lua)?), ("area", Rect::from(LAYOUT.get().preview).into_lua(lua)?), ("units", units.into_lua(lua)?), ])?; diff --git a/yazi-plugin/src/isolate/spot.rs b/yazi-plugin/src/isolate/spot.rs index e238c5cc..98c2d5f7 100644 --- a/yazi-plugin/src/isolate/spot.rs +++ b/yazi-plugin/src/isolate/spot.rs @@ -8,7 +8,7 @@ use yazi_dds::Sendable; use yazi_shared::event::Cmd; use super::slim_lua; -use crate::{bindings::Cast, file::File, loader::LOADER}; +use crate::{file::File, loader::LOADER}; pub fn spot( cmd: &'static Cmd, @@ -43,7 +43,7 @@ pub fn spot( let job = lua.create_table_from([ ("args", Sendable::args_to_table_ref(&lua, &cmd.args)?.into_lua(&lua)?), - ("file", File::cast(&lua, file)?.into_lua(&lua)?), + ("file", File(file).into_lua(&lua)?), ("mime", mime.into_lua(&lua)?), ("skip", skip.into_lua(&lua)?), ])?; diff --git a/yazi-plugin/src/macros.rs b/yazi-plugin/src/macros.rs index 30fe25b3..c0ca1f83 100644 --- a/yazi-plugin/src/macros.rs +++ b/yazi-plugin/src/macros.rs @@ -82,13 +82,11 @@ macro_rules! impl_style_shorthands { macro_rules! impl_file_fields { ($fields:ident) => { use mlua::UserDataFields; - use $crate::bindings::Cast; $fields.add_field_method_get("cha", |_, me| Ok($crate::bindings::Cha::from(me.cha))); - $fields.add_field_method_get("url", |lua, me| $crate::url::Url::cast(lua, me.url_owned())); - $fields.add_field_method_get("link_to", |lua, me| { - me.link_to.clone().map(|u| $crate::url::Url::cast(lua, u)).transpose() - }); + $fields.add_field_method_get("url", |_, me| Ok($crate::url::Url(me.url_owned()))); + $fields + .add_field_method_get("link_to", |_, me| Ok(me.link_to.clone().map(|u| $crate::url::Url(u)))); $fields.add_field_method_get("name", |lua, me| { Some(me.name()) diff --git a/yazi-plugin/src/url/url.rs b/yazi-plugin/src/url/url.rs index 508e4fbd..e497d50a 100644 --- a/yazi-plugin/src/url/url.rs +++ b/yazi-plugin/src/url/url.rs @@ -1,10 +1,8 @@ -use mlua::{AnyUserData, ExternalError, Lua, MetaMethod, UserDataFields, UserDataMethods, UserDataRef, Value}; - -use crate::bindings::Cast; +use mlua::{ExternalError, IntoLua, Lua, MetaMethod, UserDataFields, UserDataMethods, UserDataRef, Value}; pub type UrlRef = UserDataRef; -pub struct Url; +pub struct Url(pub yazi_shared::url::Url); impl Url { pub fn register(lua: &Lua) -> mlua::Result<()> { @@ -25,16 +23,14 @@ impl Url { reg.add_method("ext", |lua, me, ()| { me.extension().map(|s| lua.create_string(s.as_encoded_bytes())).transpose() }); - reg.add_method("join", |lua, me, other: Value| { - Self::cast(lua, match other { + reg.add_method("join", |_, me, other: Value| { + Ok(Self(match other { Value::String(s) => me.join(s.to_str()?.as_ref()), Value::UserData(ud) => me.join(&*ud.borrow::()?), _ => Err("must be a string or a Url".into_lua_err())?, - }) - }); - reg.add_method("parent", |lua, me, ()| { - me.parent_url().map(|u| Self::cast(lua, u)).transpose() + })) }); + reg.add_method("parent", |_, me, ()| Ok(me.parent_url().map(Self))); reg.add_method("starts_with", |_, me, base: Value| { Ok(match base { Value::String(s) => me.starts_with(s.to_str()?.as_ref()), @@ -49,13 +45,13 @@ impl Url { _ => Err("must be a string or a Url".into_lua_err())?, }) }); - reg.add_method("strip_prefix", |lua, me, base: Value| { + reg.add_method("strip_prefix", |_, me, base: Value| { let path = match base { Value::String(s) => me.strip_prefix(s.to_str()?.as_ref()), Value::UserData(ud) => me.strip_prefix(&*ud.borrow::()?), _ => Err("must be a string or a Url".into_lua_err())?, }; - path.ok().map(|p| Self::cast(lua, yazi_shared::url::Url::from(p))).transpose() + Ok(path.ok().map(Self::from)) }); reg.add_meta_method(MetaMethod::Eq, |_, me, other: UrlRef| Ok(me == &*other)); @@ -71,13 +67,17 @@ impl Url { pub fn install(lua: &Lua) -> mlua::Result<()> { lua.globals().raw_set( "Url", - lua.create_function(|lua, url: mlua::String| { - Self::cast(lua, yazi_shared::url::Url::from(url.to_str()?.as_ref())) - })?, + lua.create_function(|_, url: mlua::String| Ok(Self::from(url.to_str()?.as_ref())))?, ) } } -impl> Cast for Url { - fn cast(lua: &Lua, data: T) -> mlua::Result { lua.create_any_userdata(data.into()) } +impl> From for Url { + fn from(value: T) -> Self { Self(value.into()) } +} + +impl IntoLua for Url { + fn into_lua(self, lua: &Lua) -> mlua::Result { + lua.create_any_userdata(self.0)?.into_lua(lua) + } } diff --git a/yazi-plugin/src/utils/cache.rs b/yazi-plugin/src/utils/cache.rs index 0de448b0..3436639e 100644 --- a/yazi-plugin/src/utils/cache.rs +++ b/yazi-plugin/src/utils/cache.rs @@ -3,11 +3,11 @@ use twox_hash::XxHash3_128; use yazi_config::PREVIEW; use super::Utils; -use crate::{bindings::Cast, file::FileRef, url::Url}; +use crate::{file::FileRef, url::Url}; impl Utils { pub(super) fn file_cache(lua: &Lua) -> mlua::Result { - lua.create_function(|lua, t: Table| { + lua.create_function(|_, t: Table| { let file: FileRef = t.raw_get("file")?; if file.url.parent() == Some(&PREVIEW.cache_dir) { return Ok(None); @@ -20,7 +20,7 @@ impl Utils { format!("{:x}", h.finish_128()) }; - Some(Url::cast(lua, PREVIEW.cache_dir.join(hex))).transpose() + Ok(Some(Url::from(PREVIEW.cache_dir.join(hex)))) }) } } diff --git a/yazi-plugin/src/utils/utils.rs b/yazi-plugin/src/utils/utils.rs index 71c02ffa..9cdd78e3 100644 --- a/yazi-plugin/src/utils/utils.rs +++ b/yazi-plugin/src/utils/utils.rs @@ -8,6 +8,7 @@ pub fn compose(lua: &Lua, isolate: bool) -> mlua::Result { Composer::make(lua, 40, move |lua, key| { match key { // App + b"__250127" => Utils::hide(lua)?, // TODO: remove this b"hide" => Utils::hide(lua)?, // Cache diff --git a/yazi-scheduler/Cargo.toml b/yazi-scheduler/Cargo.toml index 924a4bc2..a1eaf840 100644 --- a/yazi-scheduler/Cargo.toml +++ b/yazi-scheduler/Cargo.toml @@ -21,6 +21,7 @@ yazi-shared = { path = "../yazi-shared", version = "0.4.3" } anyhow = { workspace = true } async-priority-channel = "0.2.0" futures = { workspace = true } +lru = { workspace = true } parking_lot = { workspace = true } scopeguard = { workspace = true } tokio = { workspace = true } diff --git a/yazi-scheduler/src/prework/prework.rs b/yazi-scheduler/src/prework/prework.rs index 084b8944..7584c690 100644 --- a/yazi-scheduler/src/prework/prework.rs +++ b/yazi-scheduler/src/prework/prework.rs @@ -1,6 +1,7 @@ -use std::collections::{HashMap, HashSet}; +use std::{collections::{HashMap, HashSet}, num::NonZeroUsize}; use anyhow::{Result, anyhow}; +use lru::LruCache; use parking_lot::{Mutex, RwLock}; use tokio::sync::mpsc; use tracing::error; @@ -16,7 +17,7 @@ pub struct Prework { macro_: async_priority_channel::Sender, prog: mpsc::UnboundedSender, - pub loaded: Mutex>, + pub loaded: Mutex>, pub size_loading: RwLock>, } @@ -25,60 +26,49 @@ impl Prework { macro_: async_priority_channel::Sender, prog: mpsc::UnboundedSender, ) -> Self { - Self { macro_, prog, loaded: Default::default(), size_loading: Default::default() } + Self { + macro_, + prog, + loaded: Mutex::new(LruCache::new(NonZeroUsize::new(4096).unwrap())), + size_loading: Default::default(), + } } pub async fn work(&self, op: PreworkOp) -> Result<()> { match op { PreworkOp::Fetch(task) => { - let urls: Vec<_> = task.targets.iter().map(|f| f.url_owned()).collect(); + let hashes: Vec<_> = task.targets.iter().map(|f| f.hash()).collect(); let result = isolate::fetch(CmdCow::from(&task.plugin.run), task.targets).await; if let Err(e) = result { - self.fail( - task.id, - format!( - "Failed to run fetcher `{}` with:\n{}\n\nError message:\n{e}", - task.plugin.run.name, - urls.iter().map(ToString::to_string).collect::>().join("\n") - ), - )?; + self.fail(task.id, format!("Failed to run fetcher `{}`:\n{e}", task.plugin.run.name))?; return Err(e.into()); }; - let code = result.unwrap(); - if code & 1 == 0 { - error!( - "Returned {code} when running fetcher `{}` with:\n{}", - task.plugin.run.name, - urls.iter().map(ToString::to_string).collect::>().join("\n") - ); + let (state, err) = result.unwrap(); + let mut loaded = self.loaded.lock(); + for (_, h) in hashes.into_iter().enumerate().filter(|&(i, _)| !state.get(i)) { + loaded.get_mut(&h).map(|x| *x &= !(1 << task.plugin.idx)); } - if code & 2 != 0 { - let mut loaded = self.loaded.lock(); - for url in urls { - loaded.get_mut(&url).map(|x| *x &= !(1 << task.plugin.idx)); - } + if let Some(e) = err { + error!("Error when running fetcher `{}`:\n{e}", task.plugin.run.name); } self.prog.send(TaskProg::Adv(task.id, 1, 0))?; } PreworkOp::Load(task) => { - let url = task.target.url_owned(); + let hash = task.target.hash(); let result = isolate::preload(&task.plugin.run, task.target).await; if let Err(e) = result { - self.fail( - task.id, - format!("Failed to run preloader `{}` with `{url}`:\n{e}", task.plugin.run.name), - )?; + self + .fail(task.id, format!("Failed to run preloader `{}`:\n{e}", task.plugin.run.name))?; return Err(e.into()); }; - let code = result.unwrap(); - if code & 1 == 0 { - error!("Returned {code} when running preloader `{}` with `{url}`", task.plugin.run.name); + let (ok, err) = result.unwrap(); + if !ok { + self.loaded.lock().get_mut(&hash).map(|x| *x &= !(1 << task.plugin.idx)); } - if code & 2 != 0 { - let mut loaded = self.loaded.lock(); - loaded.get_mut(&url).map(|x| *x &= !(1 << task.plugin.idx)); + if let Some(e) = err { + error!("Error when running preloader `{}`:\n{e}", task.plugin.run.name); } self.prog.send(TaskProg::Adv(task.id, 1, 0))?; }