From 4b08e8d899068f351a8d9eace3621233080388ba Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=89=E5=92=B2=E9=9B=85=20misaki=20masa?= Date: Tue, 21 Oct 2025 15:14:08 +0800 Subject: [PATCH] feat: new mimetype fetcher dedicated to remote files (#3268) --- yazi-actor/src/cmp/trigger.rs | 4 +- yazi-actor/src/mgr/download.rs | 4 +- yazi-binding/src/cha.rs | 9 ++- yazi-binding/src/scheme.rs | 10 ++- yazi-binding/src/url.rs | 10 ++- yazi-config/preset/theme-dark.toml | 2 +- yazi-config/preset/theme-light.toml | 2 +- yazi-config/preset/yazi-default.toml | 7 +- yazi-config/src/pattern.rs | 76 +++++++++++++++---- yazi-fs/src/hash.rs | 3 +- yazi-fs/src/lib.rs | 2 +- yazi-fs/src/provider/local/gate.rs | 6 +- yazi-fs/src/provider/local/local.rs | 6 +- yazi-fs/src/scheme.rs | 27 +++++++ yazi-fs/src/url.rs | 26 +------ yazi-plugin/preset/plugins/file.lua | 7 +- .../plugins/{mime-file.lua => mime-local.lua} | 22 +----- yazi-plugin/preset/plugins/mime-remote.lua | 59 ++++++++++++++ yazi-plugin/preset/plugins/mime.lua | 4 +- yazi-plugin/src/loader/loader.rs | 3 +- yazi-scheduler/src/file/file.rs | 2 +- yazi-scheduler/src/scheduler.rs | 6 +- yazi-shared/src/scheme/cow.rs | 45 ++++------- yazi-shared/src/scheme/mod.rs | 2 +- yazi-shared/src/scheme/ref.rs | 35 +++++---- yazi-shared/src/scheme/scheme.rs | 23 +----- yazi-shared/src/scheme/traits.rs | 63 +++++++++++++++ yazi-shared/src/url/buf.rs | 4 +- yazi-shared/src/url/component.rs | 2 +- yazi-shared/src/url/cow.rs | 6 +- yazi-shared/src/url/traits.rs | 9 +-- yazi-shared/src/url/url.rs | 2 +- 32 files changed, 320 insertions(+), 168 deletions(-) create mode 100644 yazi-fs/src/scheme.rs rename yazi-plugin/preset/plugins/{mime-file.lua => mime-local.lua} (73%) create mode 100644 yazi-plugin/preset/plugins/mime-remote.lua create mode 100644 yazi-shared/src/scheme/traits.rs diff --git a/yazi-actor/src/cmp/trigger.rs b/yazi-actor/src/cmp/trigger.rs index 5e77b227..0a39868a 100644 --- a/yazi-actor/src/cmp/trigger.rs +++ b/yazi-actor/src/cmp/trigger.rs @@ -5,7 +5,7 @@ use yazi_fs::{CWD, path::expand_url, provider::{DirReader, FileHolder}}; use yazi_macro::{act, render, succ}; use yazi_parser::cmp::{CmpItem, ShowOpt, TriggerOpt}; use yazi_proxy::CmpProxy; -use yazi_shared::{OsStrSplit, data::Data, natsort, url::{UrlBuf, UrlCow, UrnBuf}}; +use yazi_shared::{OsStrSplit, data::Data, natsort, scheme::SchemeLike, url::{UrlBuf, UrlCow, UrnBuf}}; use yazi_vfs::provider; use crate::{Actor, Ctx}; @@ -70,7 +70,7 @@ impl Trigger { fn split_url(s: &str) -> Option<(UrlBuf, UrnBuf)> { let (scheme, path, ..) = UrlCow::parse(s.as_bytes()).ok()?; - if !scheme.is_virtual() && path.as_os_str() == "~" { + if scheme.is_local() && path.as_os_str() == "~" { return None; // We don't autocomplete a `~`, but `~/` } diff --git a/yazi-actor/src/mgr/download.rs b/yazi-actor/src/mgr/download.rs index ed10d07d..a0c1090b 100644 --- a/yazi-actor/src/mgr/download.rs +++ b/yazi-actor/src/mgr/download.rs @@ -4,7 +4,7 @@ use anyhow::Result; use futures::{StreamExt, stream::FuturesUnordered}; use hashbrown::HashSet; use tokio::sync::oneshot; -use yazi_fs::{File, FsUrl, provider::{Provider, local::Local}}; +use yazi_fs::{File, FsScheme, provider::{Provider, local::Local}}; use yazi_macro::succ; use yazi_parser::mgr::{DownloadOpt, OpenOpt}; use yazi_proxy::MgrProxy; @@ -75,7 +75,7 @@ impl Actor for Download { impl Download { async fn prepare(urls: &[UrlCow<'_>]) { - let roots: HashSet<_> = urls.iter().filter_map(|u| u.cache_root()).collect(); + let roots: HashSet<_> = urls.iter().filter_map(|u| u.scheme().cache()).collect(); for mut root in roots { root.push("%lock"); Local.create_dir_all(root).await.ok(); diff --git a/yazi-binding/src/cha.rs b/yazi-binding/src/cha.rs index 9d8f66e4..75e54d0f 100644 --- a/yazi-binding/src/cha.rs +++ b/yazi-binding/src/cha.rs @@ -1,7 +1,7 @@ use std::{ops::Deref, time::{Duration, SystemTime}}; use mlua::{ExternalError, FromLua, IntoLua, Lua, Table, UserData, UserDataFields, UserDataMethods}; -use yazi_fs::cha::{ChaKind, ChaMode}; +use yazi_fs::{FsHash128, cha::{ChaKind, ChaMode}}; #[derive(Clone, Copy, FromLua)] pub struct Cha(pub yazi_fs::cha::Cha); @@ -76,6 +76,13 @@ impl UserData for Cha { } fn add_methods>(methods: &mut M) { + methods.add_method("hash", |_, me, long: Option| { + Ok(if long.unwrap_or(false) { + format!("{:x}", me.hash_u128()) + } else { + Err("Short hash not supported".into_lua_err())? + }) + }); methods.add_method("perm", |lua, _me, ()| { Ok( #[cfg(unix)] diff --git a/yazi-binding/src/scheme.rs b/yazi-binding/src/scheme.rs index 73532b62..defec5fc 100644 --- a/yazi-binding/src/scheme.rs +++ b/yazi-binding/src/scheme.rs @@ -1,13 +1,16 @@ use std::ops::Deref; use mlua::{UserData, UserDataFields, Value}; +use yazi_fs::FsScheme; +use yazi_shared::scheme::SchemeLike; -use crate::cached_field; +use crate::{Url, cached_field}; pub struct Scheme { inner: yazi_shared::scheme::Scheme, - v_kind: Option, + v_kind: Option, + v_cache: Option, } impl Deref for Scheme { @@ -18,13 +21,14 @@ impl Deref for Scheme { impl Scheme { pub fn new(scheme: &yazi_shared::scheme::Scheme) -> Self { - Self { inner: scheme.clone(), v_kind: None } + Self { inner: scheme.clone(), v_kind: None, v_cache: None } } } impl UserData for Scheme { fn add_fields>(fields: &mut F) { cached_field!(fields, kind, |_, me| Ok(me.kind())); + cached_field!(fields, cache, |_, me| Ok(me.cache().map(Url::new))); fields.add_field_method_get("is_virtual", |_, me| Ok(me.is_virtual())); } diff --git a/yazi-binding/src/url.rs b/yazi-binding/src/url.rs index d0611f39..f1856eaa 100644 --- a/yazi-binding/src/url.rs +++ b/yazi-binding/src/url.rs @@ -1,7 +1,8 @@ use std::ops::Deref; use mlua::{AnyUserData, ExternalError, FromLua, Lua, MetaMethod, UserData, UserDataFields, UserDataMethods, UserDataRef, Value}; -use yazi_shared::{IntoOsStr, url::{AsUrl, UrlCow, UrlLike}}; +use yazi_fs::{FsHash64, FsHash128}; +use yazi_shared::{IntoOsStr, scheme::SchemeLike, url::{AsUrl, UrlCow, UrlLike}}; use crate::{Scheme, Urn, cached_field, deprecate}; @@ -128,6 +129,13 @@ impl UserData for Url { } fn add_methods>(methods: &mut M) { + methods.add_method("hash", |_, me, long: Option| { + Ok(if long.unwrap_or(false) { + format!("{:x}", me.hash_u128()) + } else { + format!("{:x}", me.hash_u64()) + }) + }); methods.add_method("join", |_, me, other: Value| { Ok(Self::new(match other { Value::String(s) => me.join(s.as_bytes().into_os_str()?), diff --git a/yazi-config/preset/theme-dark.toml b/yazi-config/preset/theme-dark.toml index ffff1428..913d41bb 100644 --- a/yazi-config/preset/theme-dark.toml +++ b/yazi-config/preset/theme-dark.toml @@ -232,7 +232,7 @@ rules = [ # Document { mime = "application/{pdf,doc,rtf}", fg = "cyan" }, # Virtual file system - { mime = "vfs/*", fg = "gray" }, + { mime = "vfs/{absent,stale}", fg = "gray" }, # Special file { url = "*", is = "orphan", bg = "red" }, { url = "*", is = "exec" , fg = "green" }, diff --git a/yazi-config/preset/theme-light.toml b/yazi-config/preset/theme-light.toml index 0ea9e200..ca547cf7 100644 --- a/yazi-config/preset/theme-light.toml +++ b/yazi-config/preset/theme-light.toml @@ -232,7 +232,7 @@ rules = [ # Document { mime = "application/{pdf,doc,rtf}", fg = "cyan" }, # Virtual file system - { mime = "vfs/*", fg = "darkgray" }, + { mime = "vfs/{absent,stale}", fg = "darkgray" }, # Special file { url = "*", is = "orphan", bg = "red" }, { url = "*", is = "exec" , fg = "green" }, diff --git a/yazi-config/preset/yazi-default.toml b/yazi-config/preset/yazi-default.toml index 6c42dde3..038efea9 100644 --- a/yazi-config/preset/yazi-default.toml +++ b/yazi-config/preset/yazi-default.toml @@ -81,7 +81,7 @@ rules = [ # Empty file { mime = "inode/empty", use = [ "edit", "reveal" ] }, # Virtual file system - { mime = "vfs/*", use = "download" }, + { mime = "vfs/{absent,stale}", use = "download" }, # Fallback { url = "*", use = [ "open", "reveal" ] }, ] @@ -97,8 +97,9 @@ suppress_preload = false [plugin] fetchers = [ # Mimetype - { id = "mime", url = "*/", run = "mime.dir", prio = "high" }, - { id = "mime", url = "*", run = "mime.file", prio = "high" }, + { id = "mime", url = "*/", run = "mime.dir", prio = "high" }, + { id = "mime", url = "local://*", run = "mime.local", prio = "high" }, + { id = "mime", url = "remote://*", run = "mime.remote", prio = "high" }, ] spotters = [ { url = "*/", run = "folder" }, diff --git a/yazi-config/src/pattern.rs b/yazi-config/src/pattern.rs index b3f337a1..ab33b88c 100644 --- a/yazi-config/src/pattern.rs +++ b/yazi-config/src/pattern.rs @@ -1,11 +1,11 @@ -use std::str::FromStr; +use std::{fmt::Debug, str::FromStr}; -use anyhow::Result; +use anyhow::{Result, bail}; use globset::GlobBuilder; use serde::Deserialize; -use yazi_shared::{scheme::{SchemeCow, SchemeRef}, url::AsUrl}; +use yazi_shared::{scheme::SchemeRef, url::AsUrl}; -#[derive(Debug, Deserialize)] +#[derive(Deserialize)] #[serde(try_from = "String")] pub struct Pattern { inner: globset::GlobMatcher, @@ -16,16 +16,27 @@ pub struct Pattern { sep_lit: bool, } +impl Debug for Pattern { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.debug_struct("Pattern") + .field("regex", &self.inner.glob().regex()) + .field("scheme", &self.scheme) + .field("is_dir", &self.is_dir) + .field("is_star", &self.is_star) + .finish() + } +} + impl Pattern { pub fn match_url(&self, url: impl AsUrl, is_dir: bool) -> bool { let url = url.as_url(); if is_dir != self.is_dir { return false; - } else if self.is_star { - return true; } else if !self.scheme.matches(url.scheme) { return false; + } else if self.is_star { + return true; } #[cfg(unix)] @@ -95,26 +106,59 @@ impl TryFrom for Pattern { } // --- Scheme -#[derive(Debug)] -struct PatternScheme(Option<&'static str>); +#[derive(Clone, Copy, Debug)] +enum PatternScheme { + Any, + Local, + Remote, + Virtual, + + Regular, + Search, + Archive, + Sftp, +} impl PatternScheme { fn parse(s: &str) -> Result<(Self, usize)> { - let mut me = Self(None); let Some((protocol, _)) = s.split_once("://") else { - return Ok((me, 0)); + return Ok((Self::Any, 0)); }; - if protocol != "*" { - me.0 = Some(SchemeCow::parse_kind(protocol.as_bytes())?); - } + let scheme = match protocol { + "*" => Self::Any, + "local" => Self::Local, + "remote" => Self::Remote, + "virtual" => Self::Virtual, - Ok((me, protocol.len() + 3)) + "regular" => Self::Regular, + "search" => Self::Search, + "archive" => Self::Archive, + "sftp" => Self::Sftp, + + "" => bail!("Invalid URL pattern: protocol is empty"), + _ => bail!("Unknown protocol in URL pattern: {protocol}"), + }; + + Ok((scheme, protocol.len() + 3)) } #[inline] - fn matches<'a>(&self, scheme: impl Into>) -> bool { - self.0.is_none_or(|s| s == scheme.into().kind()) + fn matches(self, scheme: SchemeRef) -> bool { + use SchemeRef as S; + match (self, scheme) { + (Self::Any, _) => true, + (Self::Local, s) => s.is_local(), + (Self::Remote, s) => s.is_remote(), + (Self::Virtual, s) => s.is_virtual(), + + (Self::Regular, S::Regular) => true, + (Self::Search, S::Search(_)) => true, + (Self::Archive, S::Archive(_)) => true, + (Self::Sftp, S::Sftp(_)) => true, + + _ => false, + } } } diff --git a/yazi-fs/src/hash.rs b/yazi-fs/src/hash.rs index a9147f99..f41df019 100644 --- a/yazi-fs/src/hash.rs +++ b/yazi-fs/src/hash.rs @@ -30,9 +30,8 @@ impl FsHash128 for Cha { self.mode.bits().hash(&mut h); self.len.hash(&mut h); - self.mtime_dur().ok().map(|d| d.as_nanos()).hash(&mut h); self.btime_dur().ok().map(|d| d.as_nanos()).hash(&mut h); - self.ctime_dur().ok().map(|d| d.as_nanos()).hash(&mut h); + self.mtime_dur().ok().map(|d| d.as_nanos()).hash(&mut h); h.finish_128() } diff --git a/yazi-fs/src/lib.rs b/yazi-fs/src/lib.rs index eb70639c..bec80dd3 100644 --- a/yazi-fs/src/lib.rs +++ b/yazi-fs/src/lib.rs @@ -2,7 +2,7 @@ yazi_macro::mod_pub!(cha error mounts path provider); -yazi_macro::mod_flat!(cwd file files filter fns hash op sorter sorting splatter stage url xdg); +yazi_macro::mod_flat!(cwd file files filter fns hash op scheme sorter sorting splatter stage url xdg); pub fn init() { CWD.init(<_>::default()); diff --git a/yazi-fs/src/provider/local/gate.rs b/yazi-fs/src/provider/local/gate.rs index eabe87e1..dfa21949 100644 --- a/yazi-fs/src/provider/local/gate.rs +++ b/yazi-fs/src/provider/local/gate.rs @@ -34,10 +34,10 @@ impl FileBuilder for Gate { } async fn new(scheme: SchemeRef<'_>) -> io::Result { - if scheme.is_virtual() { - Err(io::Error::new(io::ErrorKind::InvalidInput, "Not a local filesystem"))? - } else { + if scheme.is_local() { Ok(Self::default()) + } else { + Err(io::Error::new(io::ErrorKind::InvalidInput, "Not a local filesystem"))? } } diff --git a/yazi-fs/src/provider/local/local.rs b/yazi-fs/src/provider/local/local.rs index 4815ee30..49d297c2 100644 --- a/yazi-fs/src/provider/local/local.rs +++ b/yazi-fs/src/provider/local/local.rs @@ -17,10 +17,10 @@ impl Provider for Local { U: AsUrl, { let url = url.as_url(); - if url.scheme.is_virtual() { - Err(io::Error::new(io::ErrorKind::InvalidInput, "Not a local URL")) - } else { + if url.scheme.is_local() { Ok(absolute_url(url)) + } else { + Err(io::Error::new(io::ErrorKind::InvalidInput, "Not a local URL")) } } diff --git a/yazi-fs/src/scheme.rs b/yazi-fs/src/scheme.rs new file mode 100644 index 00000000..f6a6d8d1 --- /dev/null +++ b/yazi-fs/src/scheme.rs @@ -0,0 +1,27 @@ +use std::path::PathBuf; + +use yazi_shared::scheme::{AsScheme, Scheme, SchemeRef}; + +use crate::Xdg; + +pub trait FsScheme { + fn cache(&self) -> Option; +} + +impl FsScheme for SchemeRef<'_> { + fn cache(&self) -> Option { + match self { + SchemeRef::Regular | SchemeRef::Search(_) => None, + SchemeRef::Archive(name) => { + Some(Xdg::cache_dir().join(format!("archive-{}", yazi_shared::url::Encode::domain(name)))) + } + SchemeRef::Sftp(name) => { + Some(Xdg::cache_dir().join(format!("sftp-{}", yazi_shared::url::Encode::domain(name)))) + } + } + } +} + +impl FsScheme for Scheme { + fn cache(&self) -> Option { self.as_scheme().cache() } +} diff --git a/yazi-fs/src/url.rs b/yazi-fs/src/url.rs index 4a3ceaa8..a7c39489 100644 --- a/yazi-fs/src/url.rs +++ b/yazi-fs/src/url.rs @@ -1,16 +1,14 @@ use std::{borrow::Cow, ffi::OsStr, path::{Path, PathBuf}}; -use yazi_shared::{loc::Loc, scheme::SchemeRef, url::{AsUrl, Url, UrlBuf, UrlCow}}; +use yazi_shared::{loc::Loc, url::{AsUrl, Url, UrlBuf, UrlCow}}; -use crate::{FsHash128, Xdg, path::PercentEncoding}; +use crate::{FsHash128, FsScheme, path::PercentEncoding}; pub trait FsUrl<'a> { fn cache(&self) -> Option; fn cache_lock(&self) -> Option; - fn cache_root(&self) -> Option; - fn unified_path(self) -> Cow<'a, Path>; fn unified_path_str(self) -> Cow<'a, OsStr> @@ -37,29 +35,17 @@ impl<'a> FsUrl<'a> for Url<'a> { path } - self.cache_root().map(|root| with_loc(self.loc, root)) + self.scheme.cache().map(|root| with_loc(self.loc, root)) } fn cache_lock(&self) -> Option { - self.cache_root().map(|mut root| { + self.scheme.cache().map(|mut root| { root.push("%lock"); root.push(format!("{:x}", self.hash_u128())); root }) } - fn cache_root(&self) -> Option { - match self.scheme { - SchemeRef::Regular | SchemeRef::Search(_) => None, - SchemeRef::Archive(name) => { - Some(Xdg::cache_dir().join(format!("archive-{}", yazi_shared::url::Encode::domain(name)))) - } - SchemeRef::Sftp(name) => { - Some(Xdg::cache_dir().join(format!("sftp-{}", yazi_shared::url::Encode::domain(name)))) - } - } - } - fn unified_path(self) -> Cow<'a, Path> { self.cache().map(Cow::Owned).unwrap_or_else(|| Cow::Borrowed(self.loc.as_path())) } @@ -70,8 +56,6 @@ impl FsUrl<'_> for UrlBuf { fn cache_lock(&self) -> Option { self.as_url().cache_lock() } - fn cache_root(&self) -> Option { self.as_url().cache_root() } - fn unified_path(self) -> Cow<'static, Path> { self.cache().unwrap_or_else(|| self.loc.into_path()).into() } @@ -82,8 +66,6 @@ impl<'a> FsUrl<'a> for UrlCow<'a> { fn cache_lock(&self) -> Option { self.as_url().cache_lock() } - fn cache_root(&self) -> Option { self.as_url().cache_root() } - fn unified_path(self) -> Cow<'a, Path> { match (self.cache(), self) { (None, UrlCow::Borrowed { loc, .. }) => loc.as_path().into(), diff --git a/yazi-plugin/preset/plugins/file.lua b/yazi-plugin/preset/plugins/file.lua index 64fc7774..fc0e1009 100644 --- a/yazi-plugin/preset/plugins/file.lua +++ b/yazi-plugin/preset/plugins/file.lua @@ -40,9 +40,12 @@ function M:spot_base(job) for i, v in ipairs(fetchers) do fetchers[i] = v.cmd end + fetchers = #fetchers ~= 0 and fetchers or { "-" } + for i, v in ipairs(preloaders) do preloaders[i] = v.cmd end + preloaders = #preloaders ~= 0 and preloaders or { "-" } return { ui.Row({ "Base" }):style(ui.Style():fg("green")), @@ -54,8 +57,8 @@ function M:spot_base(job) ui.Row({ "Plugins" }):style(ui.Style():fg("green")), ui.Row { " Spotter:", spotter and spotter.cmd or "-" }, ui.Row { " Previewer:", previewer and previewer.cmd or "-" }, - ui.Row { " Fetchers:", #fetchers ~= 0 and fetchers or "-" }, - ui.Row { " Preloaders:", #preloaders ~= 0 and preloaders or "-" }, + ui.Row({ " Fetchers:", fetchers }):height(#fetchers), + ui.Row({ " Preloaders:", preloaders }):height(#preloaders), } end diff --git a/yazi-plugin/preset/plugins/mime-file.lua b/yazi-plugin/preset/plugins/mime-local.lua similarity index 73% rename from yazi-plugin/preset/plugins/mime-file.lua rename to yazi-plugin/preset/plugins/mime-local.lua index 4c7f45ef..c7b9d087 100644 --- a/yazi-plugin/preset/plugins/mime-file.lua +++ b/yazi-plugin/preset/plugins/mime-local.lua @@ -15,20 +15,11 @@ local function match_mimetype(line) end end -local function miss_cache(cache, line) - if line:match("^cannot open `.+' %(No such file or directory%)%s+$") then - return true - else - local _, err = fs.cha(Url(cache)) - return err and err.code == 2 - end -end - function M:fetch(job) - local paths, origins = {}, {} + local urls, paths = {}, {} for i, file in ipairs(job.files) do if file.cache then - paths[i], origins[i] = tostring(file.cache), tostring(file.url) + urls[i], paths[i] = tostring(file.url), tostring(file.cache) else paths[i] = tostring(file.url) end @@ -63,14 +54,9 @@ function M:fetch(job) match, ignore = match_mimetype(line) if match then - updates[origins[i] or paths[i]], state[i], i = match, true, i + 1 + updates[urls[i] or paths[i]], state[i], i = match, true, i + 1 flush(false) - elseif ignore then - goto continue - elseif origins[i] and miss_cache(paths[i], line) then - updates[origins[i]], state[i], i = "vfs/todo", true, i + 1 - flush(false) - else + elseif not ignore then state[i], i = false, i + 1 end ::continue:: diff --git a/yazi-plugin/preset/plugins/mime-remote.lua b/yazi-plugin/preset/plugins/mime-remote.lua new file mode 100644 index 00000000..581e675d --- /dev/null +++ b/yazi-plugin/preset/plugins/mime-remote.lua @@ -0,0 +1,59 @@ +local M = {} + +local function stale_cache(file) + local url = file.url + local lock = url.scheme.cache:join(string.format("%%lock/%s", url:hash(true))) + + local f = io.open(tostring(lock), "r") + if not f then + return true + end + + local hash = f:read(32) + f:close() + return hash ~= file.cha:hash(true) +end + +function M:fetch(job) + local updates, unknown, state = {}, {}, {} + for i, file in ipairs(job.files) do + if not file.cache then + unknown[#unknown + 1] = file + elseif not fs.cha(file.cache) then + updates[file.url], state[i] = "vfs/absent", true + elseif stale_cache(file) then + updates[file.url], state[i] = "vfs/stale", true + else + unknown[#unknown + 1] = file + end + end + + if next(updates) then + ya.emit("update_mimes", { updates = updates }) + end + + if #unknown == 0 then + return state + else + return self.fallback_local(job, unknown, state) + end +end + +function M.fallback_local(job, unknown, state) + local indices = {} + for i, f in ipairs(job.files) do + indices[f:hash()] = i + end + + local result = require("mime.local"):fetch(ya.dict_merge(job, { files = unknown })) + for i, f in ipairs(unknown) do + if type(result) == "table" then + state[indices[f:hash()]] = result[i] + else + state[indices[f:hash()]] = result + end + end + return state +end + +return M diff --git a/yazi-plugin/preset/plugins/mime.lua b/yazi-plugin/preset/plugins/mime.lua index e9ac3634..37f82421 100644 --- a/yazi-plugin/preset/plugins/mime.lua +++ b/yazi-plugin/preset/plugins/mime.lua @@ -1,12 +1,12 @@ local function fetch(_, job) ya.notify { title = "Deprecated plugin", - content = "The `mime` fetcher is deprecated, use `mime.file` instead in your `yazi.toml`\n\nSee https://github.com/sxyazi/yazi/pull/3222 for more details.", + content = "The `mime` fetcher is deprecated, use `mime.local` instead in your `yazi.toml`\n\nSee https://github.com/sxyazi/yazi/pull/3222 for more details.", timeout = 15, level = "warn", } - return require("mime.file"):fetch(job) + return require("mime.local"):fetch(job) end return { fetch = fetch } diff --git a/yazi-plugin/src/loader/loader.rs b/yazi-plugin/src/loader/loader.rs index 8aa1d6ed..af53db93 100644 --- a/yazi-plugin/src/loader/loader.rs +++ b/yazi-plugin/src/loader/loader.rs @@ -40,7 +40,8 @@ impl Default for Loader { ("magick".to_owned(), preset!("plugins/magick").into()), ("mime".to_owned(), preset!("plugins/mime").into()), // TODO: remove this ("mime.dir".to_owned(), preset!("plugins/mime-dir").into()), - ("mime.file".to_owned(), preset!("plugins/mime-file").into()), + ("mime.local".to_owned(), preset!("plugins/mime-local").into()), + ("mime.remote".to_owned(), preset!("plugins/mime-remote").into()), ("noop".to_owned(), preset!("plugins/noop").into()), ("pdf".to_owned(), preset!("plugins/pdf").into()), ("session".to_owned(), preset!("plugins/session").into()), diff --git a/yazi-scheduler/src/file/file.rs b/yazi-scheduler/src/file/file.rs index 2ed60dea..fab2ceba 100644 --- a/yazi-scheduler/src/file/file.rs +++ b/yazi-scheduler/src/file/file.rs @@ -6,7 +6,7 @@ use tracing::warn; use yazi_config::YAZI; use yazi_fs::{Cwd, FsHash128, FsUrl, cha::Cha, ok_or_not_found, path::{path_relative_to, skip_url}, provider::{DirReader, FileHolder, Provider, local::Local}}; use yazi_macro::ok_or_not_found; -use yazi_shared::{timestamp_us, url::{AsUrl, UrlBuf, UrlCow, UrlLike}}; +use yazi_shared::{scheme::SchemeLike, timestamp_us, url::{AsUrl, UrlBuf, UrlCow, UrlLike}}; use yazi_vfs::{VfsCha, copy_with_progress, maybe_exists, provider::{self, DirEntry}, unique_name}; use super::{FileInDelete, FileInHardlink, FileInLink, FileInPaste, FileInTrash}; diff --git a/yazi-scheduler/src/scheduler.rs b/yazi-scheduler/src/scheduler.rs index fd5bc69e..0459b95b 100644 --- a/yazi-scheduler/src/scheduler.rs +++ b/yazi-scheduler/src/scheduler.rs @@ -8,7 +8,7 @@ use yazi_config::{YAZI, plugin::{Fetcher, Preloader}}; use yazi_dds::Pump; use yazi_parser::{app::PluginOpt, tasks::ProcessOpenOpt}; use yazi_proxy::TasksProxy; -use yazi_shared::{Id, Throttle, url::{UrlBuf, UrlLike}}; +use yazi_shared::{Id, Throttle, scheme::SchemeLike, url::{UrlBuf, UrlLike}}; use yazi_vfs::{must_be_dir, provider, unique_name}; use super::{Ongoing, TaskOp}; @@ -215,7 +215,7 @@ impl Scheduler { ongoing.hooks.add_sync(id, move |canceled| _ = tx.send(canceled)); } - if !url.scheme.is_virtual() { + if !url.scheme.is_remote() { return self.ops.out(id, FileOutDownload::Fail("Cannot download non-remote file".to_owned())); }; @@ -229,7 +229,7 @@ impl Scheduler { let mut ongoing = self.ongoing.lock(); let id = ongoing.add::(format!("Upload {}", url.display())); - if !url.scheme.is_virtual() { + if !url.scheme.is_remote() { return self.ops.out(id, FileOutUpload::Fail("Cannot upload non-remote file".to_owned())); }; diff --git a/yazi-shared/src/scheme/cow.rs b/yazi-shared/src/scheme/cow.rs index 217410bc..7f35a927 100644 --- a/yazi-shared/src/scheme/cow.rs +++ b/yazi-shared/src/scheme/cow.rs @@ -3,7 +3,7 @@ use std::{borrow::Cow, ops::Not, path::Path}; use anyhow::{Result, bail, ensure}; use percent_encoding::percent_decode; -use crate::{BytesExt, pool::InternStr, scheme::{Scheme, SchemeRef}}; +use crate::{BytesExt, pool::InternStr, scheme::{AsScheme, Scheme, SchemeRef}}; #[derive(Clone, Debug)] pub enum SchemeCow<'a> { @@ -15,18 +15,21 @@ impl Default for SchemeCow<'_> { fn default() -> Self { Self::Borrowed(SchemeRef::Regular) } } -impl From for SchemeCow<'_> { - fn from(value: Scheme) -> Self { Self::Owned(value) } -} - -impl<'a> From<&'a Scheme> for SchemeCow<'a> { - fn from(value: &'a Scheme) -> Self { Self::Borrowed(value.as_ref()) } -} - impl<'a> From> for SchemeCow<'a> { fn from(value: SchemeRef<'a>) -> Self { Self::Borrowed(value) } } +impl<'a, T> From<&'a T> for SchemeCow<'a> +where + T: AsScheme + ?Sized, +{ + fn from(value: &'a T) -> Self { Self::Borrowed(value.as_scheme()) } +} + +impl From for SchemeCow<'_> { + fn from(value: Scheme) -> Self { Self::Owned(value) } +} + impl From> for Scheme { fn from(value: SchemeCow<'_>) -> Self { match value { @@ -58,14 +61,6 @@ impl<'a> SchemeCow<'a> { } } - #[inline] - pub fn as_ref(&self) -> SchemeRef<'_> { - match self { - Self::Borrowed(s) => *s, - Self::Owned(s) => s.into(), - } - } - pub(crate) fn parse( bytes: &'a [u8], skip: &mut usize, @@ -103,17 +98,6 @@ impl<'a> SchemeCow<'a> { Ok((scheme, tilde, uri, urn)) } - #[inline] - pub fn parse_kind(bytes: &[u8]) -> Result<&'static str> { - match bytes { - b"regular" => Ok("regular"), - b"search" => Ok("search"), - b"archive" => Ok("archive"), - b"sftp" => Ok("sftp"), - _ => bail!("Could not parse protocol from URL: {}", String::from_utf8_lossy(bytes)), - } - } - fn decode_param( bytes: &'a [u8], skip: &mut usize, @@ -154,7 +138,7 @@ impl<'a> SchemeCow<'a> { urn: Option, path: &Path, ) -> Result> { - Ok(match self.as_ref() { + Ok(match self.as_scheme() { SchemeRef::Regular => { ensure!(uri.is_none() && urn.is_none(), "Regular scheme cannot have ports"); None @@ -175,9 +159,6 @@ impl<'a> SchemeCow<'a> { } impl SchemeCow<'_> { - #[inline] - pub fn is_virtual(&self) -> bool { self.as_ref().is_virtual() } - #[inline] pub fn into_owned(self) -> Scheme { self.into() } } diff --git a/yazi-shared/src/scheme/mod.rs b/yazi-shared/src/scheme/mod.rs index adafbcf4..60bc0744 100644 --- a/yazi-shared/src/scheme/mod.rs +++ b/yazi-shared/src/scheme/mod.rs @@ -1 +1 @@ -yazi_macro::mod_flat!(cow r#ref scheme); +yazi_macro::mod_flat!(cow r#ref scheme traits); diff --git a/yazi-shared/src/scheme/ref.rs b/yazi-shared/src/scheme/ref.rs index 341c774a..06cf2961 100644 --- a/yazi-shared/src/scheme/ref.rs +++ b/yazi-shared/src/scheme/ref.rs @@ -1,4 +1,4 @@ -use crate::{pool::InternStr, scheme::Scheme}; +use crate::{pool::InternStr, scheme::{AsScheme, Scheme}}; #[derive(Clone, Copy, Debug, Default, Eq, Hash, PartialEq)] pub enum SchemeRef<'a> { @@ -12,17 +12,6 @@ pub enum SchemeRef<'a> { Sftp(&'a str), } -impl<'a> From<&'a Scheme> for SchemeRef<'a> { - fn from(value: &'a Scheme) -> Self { - match value { - Scheme::Regular => Self::Regular, - Scheme::Search(d) => Self::Search(d), - Scheme::Archive(d) => Self::Archive(d), - Scheme::Sftp(d) => Self::Sftp(d), - } - } -} - impl From> for Scheme { fn from(value: SchemeRef) -> Self { match value { @@ -54,13 +43,29 @@ impl<'a> SchemeRef<'a> { } #[inline] - pub fn covariant(self, other: impl Into) -> bool { - let other = other.into(); + pub fn covariant(self, other: impl AsScheme) -> bool { + let other = other.as_scheme(); if self.is_virtual() || other.is_virtual() { self == other } else { true } } #[inline] - pub fn is_virtual(&self) -> bool { + pub fn is_local(self) -> bool { + match self { + Self::Regular | Self::Search(_) => true, + Self::Archive(_) | Self::Sftp(_) => false, + } + } + + #[inline] + pub fn is_remote(self) -> bool { + match self { + Self::Regular | Self::Search(_) | Self::Archive(_) => false, + Self::Sftp(_) => true, + } + } + + #[inline] + pub fn is_virtual(self) -> bool { match self { Self::Regular | Self::Search(_) => false, Self::Archive(_) | Self::Sftp(_) => true, diff --git a/yazi-shared/src/scheme/scheme.rs b/yazi-shared/src/scheme/scheme.rs index 03fccada..a7d54fe6 100644 --- a/yazi-shared/src/scheme/scheme.rs +++ b/yazi-shared/src/scheme/scheme.rs @@ -1,6 +1,6 @@ use std::hash::{Hash, Hasher}; -use crate::{pool::Symbol, scheme::SchemeRef}; +use crate::{pool::Symbol, scheme::{AsScheme, SchemeRef}}; #[derive(Clone, Debug, Default, Eq, PartialEq)] pub enum Scheme { @@ -15,26 +15,9 @@ pub enum Scheme { } impl Hash for Scheme { - fn hash(&self, state: &mut H) { self.as_ref().hash(state); } + fn hash(&self, state: &mut H) { self.as_scheme().hash(state); } } impl PartialEq> for Scheme { - fn eq(&self, other: &SchemeRef<'_>) -> bool { self.as_ref() == *other } -} - -impl Scheme { - #[inline] - pub fn as_ref(&self) -> SchemeRef<'_> { self.into() } - - #[inline] - pub fn kind(&self) -> &'static str { self.as_ref().kind() } - - #[inline] - pub fn domain(&self) -> Option<&str> { self.as_ref().domain() } - - #[inline] - pub fn covariant(&self, other: &Self) -> bool { self.as_ref().covariant(other) } - - #[inline] - pub fn is_virtual(&self) -> bool { self.as_ref().is_virtual() } + fn eq(&self, other: &SchemeRef<'_>) -> bool { self.as_scheme() == *other } } diff --git a/yazi-shared/src/scheme/traits.rs b/yazi-shared/src/scheme/traits.rs new file mode 100644 index 00000000..e4a2b477 --- /dev/null +++ b/yazi-shared/src/scheme/traits.rs @@ -0,0 +1,63 @@ +use crate::scheme::{Scheme, SchemeCow, SchemeRef}; + +pub trait AsScheme { + fn as_scheme(&self) -> SchemeRef<'_>; +} + +impl AsScheme for SchemeRef<'_> { + #[inline] + fn as_scheme(&self) -> SchemeRef<'_> { *self } +} + +impl AsScheme for Scheme { + #[inline] + fn as_scheme(&self) -> SchemeRef<'_> { + match self { + Scheme::Regular => SchemeRef::Regular, + Scheme::Search(d) => SchemeRef::Search(d), + Scheme::Archive(d) => SchemeRef::Archive(d), + Scheme::Sftp(d) => SchemeRef::Sftp(d), + } + } +} + +impl AsScheme for &Scheme { + #[inline] + fn as_scheme(&self) -> SchemeRef<'_> { (**self).as_scheme() } +} + +impl AsScheme for SchemeCow<'_> { + #[inline] + fn as_scheme(&self) -> SchemeRef<'_> { + match self { + SchemeCow::Borrowed(s) => *s, + SchemeCow::Owned(s) => s.as_scheme(), + } + } +} + +impl AsScheme for &SchemeCow<'_> { + #[inline] + fn as_scheme(&self) -> SchemeRef<'_> { (**self).as_scheme() } +} + +// --- SchemeLike +pub trait SchemeLike +where + Self: AsScheme + Sized, +{ + fn kind(&self) -> &'static str { self.as_scheme().kind() } + + fn domain(&self) -> Option<&str> { self.as_scheme().domain() } + + fn covariant(&self, other: impl AsScheme) -> bool { self.as_scheme().covariant(other) } + + fn is_local(&self) -> bool { self.as_scheme().is_local() } + + fn is_remote(&self) -> bool { self.as_scheme().is_remote() } + + fn is_virtual(&self) -> bool { self.as_scheme().is_virtual() } +} + +impl SchemeLike for Scheme {} +impl SchemeLike for SchemeCow<'_> {} diff --git a/yazi-shared/src/url/buf.rs b/yazi-shared/src/url/buf.rs index 0b13341c..69ca532c 100644 --- a/yazi-shared/src/url/buf.rs +++ b/yazi-shared/src/url/buf.rs @@ -3,7 +3,7 @@ use std::{borrow::Cow, ffi::OsStr, fmt::{Debug, Formatter}, path::{Path, PathBuf use anyhow::Result; use serde::{Deserialize, Serialize}; -use crate::{loc::LocBuf, pool::Pool, scheme::Scheme, url::{AsUrl, Encode, EncodeTilded, Url, UrlCow}}; +use crate::{loc::LocBuf, pool::Pool, scheme::{Scheme, SchemeLike}, url::{AsUrl, Encode, EncodeTilded, Url, UrlCow}}; #[derive(Clone, Default, Eq, Hash, PartialEq)] pub struct UrlBuf { @@ -91,7 +91,7 @@ impl UrlBuf { #[inline] pub fn into_path(self) -> Option { - Some(self.loc.into_path()).filter(|_| !self.scheme.is_virtual()) + Some(self.loc.into_path()).filter(|_| self.scheme.is_local()) } #[inline] diff --git a/yazi-shared/src/url/component.rs b/yazi-shared/src/url/component.rs index 4869194e..acc8f025 100644 --- a/yazi-shared/src/url/component.rs +++ b/yazi-shared/src/url/component.rs @@ -77,7 +77,7 @@ impl<'a> From> for Components<'a> { impl<'a> Components<'a> { pub fn os_str(&self) -> Cow<'a, OsStr> { let path = self.inner.as_path(); - if !self.url.scheme.is_virtual() || self.scheme_yielded { + if self.url.scheme.is_local() || self.scheme_yielded { return path.as_os_str().into(); } diff --git a/yazi-shared/src/url/cow.rs b/yazi-shared/src/url/cow.rs index 8f482655..e7330797 100644 --- a/yazi-shared/src/url/cow.rs +++ b/yazi-shared/src/url/cow.rs @@ -3,7 +3,7 @@ use std::{borrow::Cow, path::{Path, PathBuf}}; use anyhow::Result; use percent_encoding::percent_decode; -use crate::{IntoOsStr, loc::{Loc, LocBuf}, scheme::{SchemeCow, SchemeRef}, url::{AsUrl, Url, UrlBuf}}; +use crate::{IntoOsStr, loc::{Loc, LocBuf}, scheme::{AsScheme, SchemeCow, SchemeRef}, url::{AsUrl, Url, UrlBuf}}; #[derive(Clone, Debug)] pub enum UrlCow<'a> { @@ -109,8 +109,8 @@ impl<'a> UrlCow<'a> { #[inline] pub fn scheme(&self) -> SchemeRef<'_> { match self { - UrlCow::Borrowed { scheme, .. } => scheme.as_ref(), - UrlCow::Owned { scheme, .. } => scheme.as_ref(), + UrlCow::Borrowed { scheme, .. } => scheme.as_scheme(), + UrlCow::Owned { scheme, .. } => scheme.as_scheme(), } } diff --git a/yazi-shared/src/url/traits.rs b/yazi-shared/src/url/traits.rs index c2030f9a..2e0ad99c 100644 --- a/yazi-shared/src/url/traits.rs +++ b/yazi-shared/src/url/traits.rs @@ -1,6 +1,6 @@ use std::{borrow::Cow, ffi::OsStr, path::{Path, PathBuf}}; -use crate::{scheme::SchemeRef, url::{Components, Display, Uri, Url, UrlBuf, UrlCow, Urn}}; +use crate::{scheme::{AsScheme, SchemeRef}, url::{Components, Display, Uri, Url, UrlBuf, UrlCow, Urn}}; // --- AsUrl pub trait AsUrl { @@ -34,7 +34,7 @@ impl AsUrl for Url<'_> { impl AsUrl for UrlBuf { #[inline] - fn as_url(&self) -> Url<'_> { Url { loc: self.loc.as_loc(), scheme: self.scheme.as_ref() } } + fn as_url(&self) -> Url<'_> { Url { loc: self.loc.as_loc(), scheme: self.scheme.as_scheme() } } } impl AsUrl for &UrlBuf { @@ -51,8 +51,8 @@ impl AsUrl for UrlCow<'_> { #[inline] fn as_url(&self) -> Url<'_> { match self { - UrlCow::Borrowed { loc, scheme } => Url { loc: *loc, scheme: scheme.as_ref() }, - UrlCow::Owned { loc, scheme } => Url { loc: loc.as_loc(), scheme: scheme.as_ref() }, + UrlCow::Borrowed { loc, scheme } => Url { loc: *loc, scheme: scheme.as_scheme() }, + UrlCow::Owned { loc, scheme } => Url { loc: loc.as_loc(), scheme: scheme.as_scheme() }, } } } @@ -122,6 +122,5 @@ where fn urn(&self) -> &Urn { self.as_url().urn() } } -impl UrlLike for Url<'_> {} impl UrlLike for UrlBuf {} impl UrlLike for UrlCow<'_> {} diff --git a/yazi-shared/src/url/url.rs b/yazi-shared/src/url/url.rs index eaade3f6..aca0d8a6 100644 --- a/yazi-shared/src/url/url.rs +++ b/yazi-shared/src/url/url.rs @@ -200,7 +200,7 @@ impl<'a> Url<'a> { #[inline] pub fn as_path(self) -> Option<&'a Path> { - Some(self.loc.as_path()).filter(|_| !self.scheme.is_virtual()) + Some(self.loc.as_path()).filter(|_| self.scheme.is_local()) } #[inline]