mirror of
https://github.com/sxyazi/yazi.git
synced 2026-07-21 23:01:05 +00:00
feat: allow preloaders to return an optional Error to describe the failure (#2253)
This commit is contained in:
parent
da36cd6ab8
commit
c061397a09
42 changed files with 297 additions and 230 deletions
2
Cargo.lock
generated
2
Cargo.lock
generated
|
|
@ -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",
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
|
|
|
|||
|
|
@ -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" },
|
||||
|
|
|
|||
|
|
@ -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<Condition>,
|
||||
pub name: Option<Pattern>,
|
||||
pub mime: Option<Pattern>,
|
||||
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))
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -20,11 +20,10 @@ impl Plugin {
|
|||
&'b self,
|
||||
path: &'a Path,
|
||||
mime: &'a str,
|
||||
factor: impl Fn(&str) -> bool + Copy + 'a,
|
||||
) -> impl Iterator<Item = &'b Fetcher> + '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<File>) -> impl Iterator<Item = (&Fetcher, Vec<File>)> {
|
||||
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))
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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!();
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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}");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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<F: UserDataFields<Self>>(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()));
|
||||
|
|
|
|||
|
|
@ -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<Iter<Keys<yazi_shared::url::Url, u64>, _>>| {
|
||||
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)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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<Iter<hash_set::Iter<yazi_shared::url::Url>, _>>| {
|
||||
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)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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 }
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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 {}
|
||||
|
|
|
|||
|
|
@ -1,5 +0,0 @@
|
|||
use mlua::Lua;
|
||||
|
||||
pub trait Cast<T> {
|
||||
fn cast(lua: &Lua, data: T) -> mlua::Result<mlua::AnyUserData>;
|
||||
}
|
||||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -8,12 +8,7 @@ pub(super) struct Plugin;
|
|||
impl Plugin {
|
||||
pub(super) fn fetchers(lua: &Lua) -> mlua::Result<Function> {
|
||||
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))
|
||||
})
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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<Cell>,
|
||||
height: u16,
|
||||
|
|
@ -34,6 +36,23 @@ impl From<Row> for ratatui::widgets::Row<'static> {
|
|||
}
|
||||
}
|
||||
|
||||
impl TryFrom<Value> for Row {
|
||||
type Error = mlua::Error;
|
||||
|
||||
fn try_from(value: Value) -> Result<Self, Self::Error> {
|
||||
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<M: mlua::UserDataMethods<Self>>(methods: &mut M) {
|
||||
crate::impl_style_method!(methods, style);
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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<mlua::Table> {
|
||||
let new = lua.create_function(|_, (_, rows): (mlua::Table, Vec<Row>)| {
|
||||
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::<Value>() {
|
||||
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<M: mlua::UserDataMethods<Self>>(methods: &mut M) {
|
||||
crate::impl_area_method!(methods);
|
||||
|
||||
methods.add_function_mut("header", |_, (ud, row): (AnyUserData, Row)| {
|
||||
ud.borrow_mut::<Self>()?.header = Some(row.into());
|
||||
methods.add_function_mut("header", |_, (ud, value): (AnyUserData, Value)| {
|
||||
ud.borrow_mut::<Self>()?.header = Some(Row::try_from(value)?.into());
|
||||
Ok(ud)
|
||||
});
|
||||
methods.add_function_mut("footer", |_, (ud, row): (AnyUserData, Row)| {
|
||||
ud.borrow_mut::<Self>()?.footer = Some(row.into());
|
||||
methods.add_function_mut("footer", |_, (ud, value): (AnyUserData, Value)| {
|
||||
ud.borrow_mut::<Self>()?.footer = Some(Row::try_from(value)?.into());
|
||||
Ok(ud)
|
||||
});
|
||||
methods.add_function_mut("widths", |_, (ud, widths): (AnyUserData, Vec<Constraint>)| {
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
||||
|
|
|
|||
|
|
@ -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<Self> {
|
||||
match value {
|
||||
Value::UserData(ud) => ud.take(),
|
||||
_ => Err(EXPECTED.into_lua_err()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl UserData for Error {
|
||||
fn add_fields<F: UserDataFields<Self>>(fields: &mut F) {
|
||||
fields.add_field_method_get("code", |_, me| {
|
||||
|
|
|
|||
|
|
@ -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<yazi_fs::File>;
|
||||
|
||||
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::<AnyUserData>("url")?.take()?,
|
||||
cha: *t.raw_get::<Cha>("cha")?,
|
||||
..Default::default()
|
||||
})
|
||||
}))
|
||||
})?,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Into<yazi_fs::File>> Cast<T> for File {
|
||||
fn cast(lua: &Lua, data: T) -> mlua::Result<AnyUserData> { lua.create_any_userdata(data.into()) }
|
||||
impl IntoLua for File {
|
||||
fn into_lua(self, lua: &Lua) -> mlua::Result<mlua::Value> {
|
||||
lua.create_any_userdata(self.0)?.into_lua(lua)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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<Value> {
|
||||
Composer::make(lua, 10, |lua, key| {
|
||||
|
|
@ -24,7 +24,7 @@ pub fn compose(lua: &Lua) -> mlua::Result<Value> {
|
|||
|
||||
fn cwd(lua: &Lua) -> mlua::Result<Function> {
|
||||
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<Function> {
|
|||
} 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<Function> {
|
|||
fn unique_name(lua: &Lua) -> mlua::Result<Function> {
|
||||
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),
|
||||
}
|
||||
})
|
||||
|
|
|
|||
|
|
@ -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<yazi_fs::File>) -> mlua::Result<u8> {
|
||||
pub async fn fetch(
|
||||
cmd: CmdCow,
|
||||
files: Vec<yazi_fs::File>,
|
||||
) -> mlua::Result<(FetchState, Option<Error>)> {
|
||||
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<yazi_fs::File>) -> mlua::Result<u8> {
|
|||
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<bool>),
|
||||
}
|
||||
|
||||
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<Self> {
|
||||
Ok(match value {
|
||||
Value::Boolean(b) => Self::Bool(b),
|
||||
Value::Table(tbl) => Self::Vec(tbl.sequence_values().collect::<mlua::Result<_>>()?),
|
||||
_ => Err(mlua::Error::FromLuaConversionError {
|
||||
from: value.type_name(),
|
||||
to: "FetchState".to_owned(),
|
||||
message: Some("expected a boolean or a table of booleans".to_owned()),
|
||||
})?,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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)?),
|
||||
])?;
|
||||
|
|
|
|||
|
|
@ -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<u8> {
|
||||
pub async fn preload(
|
||||
cmd: &'static Cmd,
|
||||
file: yazi_fs::File,
|
||||
) -> mlua::Result<(bool, Option<Error>)> {
|
||||
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<u8>
|
|||
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<Error>) =
|
||||
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()?
|
||||
|
|
|
|||
|
|
@ -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)?),
|
||||
])?;
|
||||
|
|
|
|||
|
|
@ -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)?),
|
||||
])?;
|
||||
|
|
|
|||
|
|
@ -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())
|
||||
|
|
|
|||
|
|
@ -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<yazi_shared::url::Url>;
|
||||
|
||||
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::<yazi_shared::url::Url>()?),
|
||||
_ => 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::<yazi_shared::url::Url>()?),
|
||||
_ => 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<T: Into<yazi_shared::url::Url>> Cast<T> for Url {
|
||||
fn cast(lua: &Lua, data: T) -> mlua::Result<AnyUserData> { lua.create_any_userdata(data.into()) }
|
||||
impl<T: Into<yazi_shared::url::Url>> From<T> for Url {
|
||||
fn from(value: T) -> Self { Self(value.into()) }
|
||||
}
|
||||
|
||||
impl IntoLua for Url {
|
||||
fn into_lua(self, lua: &Lua) -> mlua::Result<Value> {
|
||||
lua.create_any_userdata(self.0)?.into_lua(lua)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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<Function> {
|
||||
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))))
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -8,6 +8,7 @@ pub fn compose(lua: &Lua, isolate: bool) -> mlua::Result<Value> {
|
|||
Composer::make(lua, 40, move |lua, key| {
|
||||
match key {
|
||||
// App
|
||||
b"__250127" => Utils::hide(lua)?, // TODO: remove this
|
||||
b"hide" => Utils::hide(lua)?,
|
||||
|
||||
// Cache
|
||||
|
|
|
|||
|
|
@ -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 }
|
||||
|
|
|
|||
|
|
@ -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<TaskOp, u8>,
|
||||
prog: mpsc::UnboundedSender<TaskProg>,
|
||||
|
||||
pub loaded: Mutex<HashMap<Url, u32>>,
|
||||
pub loaded: Mutex<LruCache<u64, u32>>,
|
||||
pub size_loading: RwLock<HashSet<Url>>,
|
||||
}
|
||||
|
||||
|
|
@ -25,60 +26,49 @@ impl Prework {
|
|||
macro_: async_priority_channel::Sender<TaskOp, u8>,
|
||||
prog: mpsc::UnboundedSender<TaskProg>,
|
||||
) -> 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::<Vec<_>>().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::<Vec<_>>().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))?;
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue