refactor: rename the "provider" term in VFS config to "service" (#3403)

This commit is contained in:
sxyazi 2025-12-05 15:04:39 +08:00
parent dccc9b2c2a
commit 205b7fe11f
No known key found for this signature in database
15 changed files with 119 additions and 132 deletions

8
Cargo.lock generated
View file

@ -1305,9 +1305,9 @@ checksum = "3a3076410a55c90011c298b04d0cfa770b00fa04e1e3c97d3f6c9de105a03844"
[[package]]
name = "flate2"
version = "1.1.5"
version = "1.1.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bfe33edd8e85a12a67454e37f8c75e730830d83e313556ab9ebf9ee7fbeb3bfb"
checksum = "30f4148e3c9b7dbe0cc7e842ad5a61b28f9025f201d78149383e778a08bc9215"
dependencies = [
"crc32fast",
"miniz_oxide",
@ -2136,9 +2136,9 @@ dependencies = [
[[package]]
name = "mio"
version = "1.1.0"
version = "1.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "69d83b0086dc8ecf3ce9ae2874b2d1290252e2a30720bea58a5c6639b0092873"
checksum = "a69bcab0ad47271a0234d9422b131806bf3968021e5dc9328caf2d4cd58557fc"
dependencies = [
"libc",
"log",

View file

@ -1 +1 @@
yazi_macro::mod_flat!(provider vfs);
yazi_macro::mod_flat!(service vfs);

View file

@ -5,21 +5,21 @@ use yazi_fs::path::expand_url;
#[derive(Deserialize, Serialize)]
#[serde(tag = "type", rename_all = "kebab-case")]
pub enum Provider {
Sftp(ProviderSftp),
pub enum Service {
Sftp(ServiceSftp),
}
impl TryFrom<&'static Provider> for &'static ProviderSftp {
impl TryFrom<&'static Service> for &'static ServiceSftp {
type Error = &'static str;
fn try_from(value: &'static Provider) -> Result<Self, Self::Error> {
fn try_from(value: &'static Service) -> Result<Self, Self::Error> {
match value {
Provider::Sftp(p) => Ok(p),
Service::Sftp(p) => Ok(p),
}
}
}
impl Provider {
impl Service {
pub(super) fn reshape(&mut self) -> io::Result<()> {
match self {
Self::Sftp(p) => p.reshape(),
@ -29,7 +29,7 @@ impl Provider {
// --- SFTP
#[derive(Deserialize, Eq, Hash, PartialEq, Serialize)]
pub struct ProviderSftp {
pub struct ServiceSftp {
pub host: String,
pub user: String,
pub port: u16,
@ -41,7 +41,7 @@ pub struct ProviderSftp {
pub identity_agent: PathBuf,
}
impl ProviderSftp {
impl ServiceSftp {
fn reshape(&mut self) -> io::Result<()> {
if !self.key_file.as_os_str().is_empty() {
self.key_file = expand_url(&self.key_file)

View file

@ -6,11 +6,11 @@ use tokio::sync::OnceCell;
use yazi_fs::Xdg;
use yazi_macro::ok_or_not_found;
use super::Provider;
use super::Service;
#[derive(Deserialize, Serialize)]
pub struct Vfs {
pub providers: HashMap<String, Provider>,
pub services: HashMap<String, Service>,
}
impl Vfs {
@ -27,16 +27,16 @@ impl Vfs {
LOADED.get_or_try_init(init).await
}
pub async fn provider<'a, P>(name: &str) -> io::Result<(&'a str, P)>
pub async fn service<'a, P>(name: &str) -> io::Result<(&'a str, P)>
where
P: TryFrom<&'a Provider, Error = &'static str>,
P: TryFrom<&'a Service, Error = &'static str>,
{
let Some((key, value)) = Self::load().await?.providers.get_key_value(name) else {
return Err(io::Error::other(format!("No such VFS provider: {name}")));
let Some((key, value)) = Self::load().await?.services.get_key_value(name) else {
return Err(io::Error::other(format!("No such VFS service: {name}")));
};
match value.try_into() {
Ok(p) => Ok((key.as_str(), p)),
Err(e) => Err(io::Error::other(format!("VFS provider `{key}` has wrong type: {e}"))),
Err(e) => Err(io::Error::other(format!("VFS service `{key}` has wrong type: {e}"))),
}
}
@ -48,14 +48,14 @@ impl Vfs {
}
fn reshape(mut self) -> io::Result<Self> {
for (name, provider) in &mut self.providers {
for (name, service) in &mut self.services {
if name.is_empty() || name.len() > 20 {
Err(io::Error::other(format!("VFS name `{name}` must be between 1 and 20 characters")))?;
} else if !name.bytes().all(|b| matches!(b, b'0'..=b'9' | b'a'..=b'z' | b'-')) {
Err(io::Error::other(format!("VFS name `{name}` must be in kebab-case")))?;
}
provider.reshape()?;
service.reshape()?;
}
Ok(self)

View file

@ -9,59 +9,3 @@ pub fn skip_url(url: &UrlBuf, n: usize) -> StrandCow<'_> {
}
it.strand()
}
#[cfg(test)]
mod tests {
use yazi_shared::url::{AsUrl, UrlCow};
use crate::path::url_relative_to;
#[test]
fn test_url_relative_to() {
yazi_shared::init_tests();
#[cfg(unix)]
let cases = [
// Same urls
("", "", "."),
(".", ".", "."),
("/a", "/a", "."),
("regular:///", "/", "."),
("regular://", "regular://", "."),
("regular://", "search://kw/", "search://kw/."),
("regular:///b", "search://kw//b", "search://kw/."),
// Relative urls
("foo", "bar", "../bar"),
// Absolute urls
("/a/b/c", "/a/b", ".."),
("/a/b", "/a/b/c", "c"),
("/a/b/d", "/a/b/c", "../c"),
("/a/b/c", "/a", "../.."),
("/a/b/b", "/a/a/b", "../../a/b"),
("regular:///a/b", "regular:///a/b/c", "c"),
("/a/b/c/", "search://kw//a/d/", "search://kw/../../d"),
("search://kw//a/b/c", "search://kw//a/b", "search://kw/.."),
// Different schemes
("", "sftp://test/", "sftp://test/"),
("a", "sftp://test/", "sftp://test/"),
("a", "sftp://test/b", "sftp://test/b"),
("/a", "sftp://test//b", "sftp://test//b"),
("sftp://test//a/b", "sftp://test//a/d", "sftp://test:0:0/../d"),
];
#[cfg(windows)]
let cases = [
(r"C:\a\b\c", r"C:\a\b", r".."),
(r"C:\a\b", r"C:\a\b\c", "c"),
(r"C:\a\b\d", r"C:\a\b\c", r"..\c"),
(r"C:\a\b\c", r"C:\a", r"..\.."),
(r"C:\a\b\b", r"C:\a\a\b", r"..\..\a\b"),
];
for (from, to, expected) in cases {
let from: UrlCow = from.try_into().unwrap();
let to: UrlCow = to.try_into().unwrap();
assert_eq!(format!("{:?}", url_relative_to(from, to).unwrap().as_url()), expected);
}
}
}

View file

@ -1,39 +1,35 @@
use anyhow::{Result, bail};
use yazi_shared::{path::PathBufDyn, url::{UrlCow, UrlLike}};
use yazi_shared::path::{PathBufDyn, PathCow, PathDyn, PathLike};
pub fn url_relative_to<'a, 'b, U, V>(from: U, to: V) -> Result<UrlCow<'b>>
pub fn path_relative_to<'a, 'b, P, Q>(from: P, to: Q) -> Result<PathCow<'b>>
where
U: Into<UrlCow<'a>>,
V: Into<UrlCow<'b>>,
P: Into<PathCow<'a>>,
Q: Into<PathCow<'b>>,
{
url_relative_to_(from.into(), to.into())
path_relative_to_impl(from.into(), to.into())
}
fn url_relative_to_<'a>(from: UrlCow<'_>, to: UrlCow<'a>) -> Result<UrlCow<'a>> {
use yazi_shared::url::Component::*;
fn path_relative_to_impl<'a>(from: PathCow<'_>, to: PathCow<'a>) -> Result<PathCow<'a>> {
use yazi_shared::path::Component::*;
if from.is_absolute() != to.is_absolute() {
return if to.is_absolute() {
Ok(to)
} else {
bail!("Urls must be both absolute or both relative: {from:?} and {to:?}");
bail!("Paths must be both absolute or both relative: {from:?} and {to:?}");
};
}
if from.covariant(&to) {
return UrlCow::try_from((
to.scheme().zeroed().to_owned(),
PathBufDyn::with_str(to.kind(), "."),
));
if from == to {
return Ok(PathDyn::with_str(to.kind(), ".").into());
}
let (mut f_it, mut t_it) = (from.components(), to.components());
let (f_head, t_head) = loop {
match (f_it.next(), t_it.next()) {
(Some(Scheme(a)), Some(Scheme(b))) if a.covariant(b) => {}
(Some(RootDir), Some(RootDir)) => {}
(Some(Prefix(a)), Some(Prefix(b))) if a == b => {}
(Some(Scheme(_) | Prefix(_) | RootDir), _) | (_, Some(Scheme(_) | Prefix(_) | RootDir)) => {
(Some(Prefix(_) | RootDir), _) | (_, Some(Prefix(_) | RootDir)) => {
return Ok(to);
}
(None, None) => break (None, None),
@ -45,8 +41,51 @@ fn url_relative_to_<'a>(from: UrlCow<'_>, to: UrlCow<'a>) -> Result<UrlCow<'a>>
let dots = f_head.into_iter().chain(f_it).map(|_| ParentDir);
let rest = t_head.into_iter().chain(t_it);
let iter = dots.chain(rest).map(|c| c.downgrade().expect("path component from dot or normal"));
let buf = PathBufDyn::from_components(to.kind(), iter)?;
UrlCow::try_from((to.scheme().zeroed().to_owned(), buf))
let buf = PathBufDyn::from_components(to.kind(), dots.chain(rest))?;
Ok(buf.into())
}
#[cfg(test)]
mod tests {
use yazi_shared::path::PathDyn;
use super::*;
#[test]
fn test_path_relative_to() {
yazi_shared::init_tests();
#[cfg(unix)]
let cases = [
// Same paths
("", "", "."),
(".", ".", "."),
("/", "/", "."),
("/a", "/a", "."),
// Relative paths
("foo", "bar", "../bar"),
// Absolute paths
("/a/b", "/a/b/c", "c"),
("/a/b/c", "/a/b", ".."),
("/a/b/d", "/a/b/c", "../c"),
("/a/b/c", "/a", "../.."),
("/a/b/c/", "/a/d/", "../../d"),
("/a/b/b", "/a/a/b", "../../a/b"),
];
#[cfg(windows)]
let cases = [
(r"C:\a\b", r"C:\a\b\c", "c"),
(r"C:\a\b\c", r"C:\a\b", r".."),
(r"C:\a\b\d", r"C:\a\b\c", r"..\c"),
(r"C:\a\b\c", r"C:\a", r"..\.."),
(r"C:\a\b\b", r"C:\a\a\b", r"..\..\a\b"),
];
for (from, to, expected) in cases {
let from = PathDyn::Os(from.as_ref());
let to = PathDyn::Os(to.as_ref());
assert_eq!(path_relative_to(from, to).unwrap().to_str().unwrap(), expected);
}
}
}

View file

@ -11,9 +11,12 @@ end)
function M:entry()
ya.emit("escape", { visual = true })
local _permit = ui.hide()
local cwd, selected = state()
if cwd.scheme.is_virtual then
return ya.notify { title = "Fzf", content = "Not supported under virtual filesystems", timeout = 5, level = "warn" }
end
local _permit = ui.hide()
local output, err = M.run_with(cwd, selected)
if not output then
return ya.notify { title = "Fzf", content = tostring(err), timeout = 5, level = "error" }

View file

@ -17,7 +17,9 @@ end
function M:fetch(job)
local updates, unknown, state = {}, {}, {}
for i, file in ipairs(job.files) do
if not file.cache then
if file.cha.is_dummy then
-- Skip dummy files
elseif not file.cache then
unknown[#unknown + 1] = file
elseif not fs.cha(file.cache) then
updates[file.url], state[i] = "vfs/absent", true

View file

@ -1,10 +1,10 @@
use std::{collections::VecDeque, hash::{BuildHasher, Hash, Hasher}};
use std::{collections::VecDeque, hash::{BuildHasher, Hash, Hasher}, time::Duration};
use anyhow::{Context, Result, anyhow};
use tokio::{io::{self, ErrorKind::{AlreadyExists, NotFound}}, sync::mpsc};
use tracing::warn;
use yazi_config::YAZI;
use yazi_fs::{Cwd, FsHash128, FsUrl, cha::Cha, ok_or_not_found, path::{skip_url, url_relative_to}, provider::{Attrs, DirReader, FileHolder, Provider, local::Local}};
use yazi_fs::{Cwd, FsHash128, FsUrl, cha::Cha, ok_or_not_found, path::{path_relative_to, skip_url}, provider::{Attrs, DirReader, FileHolder, Provider, local::Local}};
use yazi_macro::ok_or_not_found;
use yazi_shared::{path::PathCow, timestamp_us, url::{AsUrl, UrlBuf, UrlCow, UrlLike}};
use yazi_vfs::{VfsCha, maybe_exists, provider::{self, DirEntry}, unique_name};
@ -132,27 +132,27 @@ impl File {
}
pub(crate) async fn link_do(&self, task: FileInLink) -> Result<(), FileOutLink> {
let src: PathCow = if task.resolve {
if !task.from.scheme().covariant(task.to.scheme()) {
Err(anyhow!("Source and target must be on the same filesystem: {task:?}"))?
}
let mut src: PathCow = if task.resolve {
ok_or_not_found!(
provider::read_link(&task.from).await,
return Ok(self.ops.out(task.id, FileOutLink::Succ))
)
.into()
} else if task.from.scheme().covariant(task.to.scheme()) {
task.from.loc().into()
} else {
Err(anyhow!("Source and target must be on the same filesystem: {task:?}"))?
task.from.loc().into()
};
let src = UrlCow::try_from((task.from.scheme(), src))?;
let src = if task.relative {
url_relative_to(provider::canonicalize(task.to.parent().unwrap()).await?, &src)?
} else {
src
};
if task.relative {
let canon = provider::canonicalize(task.to.parent().unwrap()).await?;
src = path_relative_to(canon.loc(), src)?;
}
ok_or_not_found!(provider::remove_file(&task.to).await);
provider::symlink(&src, &task.to, async || {
provider::symlink(task.to, src, async || {
Ok(match task.cha {
Some(cha) => cha.is_dir(),
None => Self::cha(&task.from, task.resolve, None).await?.is_dir(),

View file

@ -27,6 +27,10 @@ impl From<PathCow<'_>> for PathBufDyn {
fn from(value: PathCow<'_>) -> Self { value.into_owned() }
}
impl PartialEq for PathCow<'_> {
fn eq(&self, other: &Self) -> bool { self.as_path() == other.as_path() }
}
impl PartialEq<&str> for PathCow<'_> {
fn eq(&self, other: &&str) -> bool {
match self {

View file

@ -201,32 +201,27 @@ where
}
}
pub async fn symlink<U, V, F>(original: U, link: V, is_dir: F) -> io::Result<()>
pub async fn symlink<U, P, F>(link: U, original: P, is_dir: F) -> io::Result<()>
where
U: AsUrl,
V: AsUrl,
P: AsPath,
F: AsyncFnOnce() -> io::Result<bool>,
{
let (original, link) = (original.as_url(), link.as_url());
if original.scheme().covariant(link.scheme()) {
Providers::new(link).await?.symlink(original.loc(), is_dir).await
} else {
Err(io::Error::from(io::ErrorKind::CrossesDevices))
}
Providers::new(link.as_url()).await?.symlink(original, is_dir).await
}
pub async fn symlink_dir<P, U>(original: P, link: U) -> io::Result<()>
pub async fn symlink_dir<U, P>(link: U, original: P) -> io::Result<()>
where
P: AsPath,
U: AsUrl,
P: AsPath,
{
Providers::new(link.as_url()).await?.symlink_dir(original).await
}
pub async fn symlink_file<P, U>(original: P, link: U) -> io::Result<()>
pub async fn symlink_file<U, P>(link: U, original: P) -> io::Result<()>
where
P: AsPath,
U: AsUrl,
P: AsPath,
{
Providers::new(link.as_url()).await?.symlink_file(original).await
}

View file

@ -1,13 +1,13 @@
use std::{io, sync::Arc};
use russh::keys::PrivateKeyWithHashAlg;
use yazi_config::vfs::ProviderSftp;
use yazi_config::vfs::ServiceSftp;
use yazi_fs::provider::local::Local;
#[derive(Clone, Copy)]
pub(super) struct Conn {
pub(super) name: &'static str,
pub(super) config: &'static ProviderSftp,
pub(super) config: &'static ServiceSftp,
}
impl russh::client::Handler for Conn {

View file

@ -1,6 +1,6 @@
use std::io;
use yazi_config::vfs::{ProviderSftp, Vfs};
use yazi_config::vfs::{ServiceSftp, Vfs};
use yazi_fs::provider::{Attrs, FileBuilder};
use yazi_sftp::fs::Flags;
use yazi_shared::url::{AsUrl, Url};
@ -64,7 +64,7 @@ impl FileBuilder for Gate {
{
let url = url.as_url();
let (path, (name, config)) = match url {
Url::Sftp { loc, domain } => (*loc, Vfs::provider::<&ProviderSftp>(domain).await?),
Url::Sftp { loc, domain } => (*loc, Vfs::service::<&ServiceSftp>(domain).await?),
_ => Err(io::Error::new(io::ErrorKind::InvalidInput, format!("Not an SFTP URL: {url:?}")))?,
};

View file

@ -3,7 +3,7 @@ yazi_macro::mod_flat!(conn gate metadata read_dir sftp);
static CONN: yazi_shared::RoCell<
parking_lot::Mutex<
hashbrown::HashMap<
&'static yazi_config::vfs::ProviderSftp,
&'static yazi_config::vfs::ServiceSftp,
&'static deadpool::managed::Pool<Conn>,
>,
>,

View file

@ -1,7 +1,7 @@
use std::{io, sync::Arc};
use tokio::{io::{AsyncWriteExt, BufReader, BufWriter}, sync::mpsc::Receiver};
use yazi_config::vfs::{ProviderSftp, Vfs};
use yazi_config::vfs::{ServiceSftp, Vfs};
use yazi_fs::{CWD, provider::{DirReader, FileHolder, Provider}};
use yazi_sftp::fs::{Attrs, Flags};
use yazi_shared::{loc::LocBuf, path::{AsPath, PathBufDyn}, pool::InternStr, scheme::SchemeKind, url::{Url, UrlBuf, UrlCow, UrlLike}};
@ -15,7 +15,7 @@ pub struct Sftp<'a> {
path: &'a typed_path::UnixPath,
name: &'static str,
config: &'static ProviderSftp,
config: &'static ServiceSftp,
}
impl<'a> Provider for Sftp<'a> {
@ -136,7 +136,7 @@ impl<'a> Provider for Sftp<'a> {
Err(io::Error::new(io::ErrorKind::InvalidInput, format!("Not a SFTP URL: {url:?}")))
}
Url::Sftp { loc, domain } => {
let (name, config) = Vfs::provider::<&ProviderSftp>(domain).await?;
let (name, config) = Vfs::service::<&ServiceSftp>(domain).await?;
Ok(Self::Me { url, path: loc.as_inner(), name, config })
}
}