feat: support VFS for join(), starts_with(), and ends_with() of Url (#3094)

This commit is contained in:
三咲雅 misaki masa 2025-08-23 10:26:20 +08:00 committed by sxyazi
parent 0054cf0b87
commit c27ef58116
No known key found for this signature in database
36 changed files with 699 additions and 449 deletions

View file

@ -38,3 +38,6 @@ windows-sys = { version = "0.60.2", features = [ "Win32_Storage_FileSystem" ] }
[target.'cfg(target_os = "macos")'.dependencies]
core-foundation-sys = { workspace = true }
objc = { workspace = true }
[target.'cfg(not(target_os = "android"))'.dependencies]
trash = "5.2.3"

View file

@ -1,4 +1,4 @@
use std::{ffi::OsStr, fs::{FileType, Metadata}, hash::{BuildHasher, Hash, Hasher}, ops::Deref};
use std::{ffi::OsStr, fs::{FileType, Metadata}, hash::{BuildHasher, Hash, Hasher}, ops::Deref, path::PathBuf};
use anyhow::Result;
use yazi_shared::url::{Uri, UrlBuf, UrlCow, Urn, UrnBuf};
@ -9,7 +9,7 @@ use crate::{cha::Cha, provider};
pub struct File {
pub url: UrlBuf,
pub cha: Cha,
pub link_to: Option<UrlBuf>,
pub link_to: Option<PathBuf>,
}
impl Deref for File {

View file

@ -31,59 +31,6 @@ pub fn ok_or_not_found<T: Default>(result: io::Result<T>) -> io::Result<T> {
}
}
#[inline]
pub async fn paths_to_same_file(a: impl AsRef<Path>, b: impl AsRef<Path>) -> bool {
_paths_to_same_file(a.as_ref(), b.as_ref()).await.unwrap_or(false)
}
#[cfg(unix)]
async fn _paths_to_same_file(a: &Path, b: &Path) -> io::Result<bool> {
use std::os::unix::fs::MetadataExt;
let (a_, b_) = (fs::symlink_metadata(a).await?, fs::symlink_metadata(b).await?);
Ok(
a_.ino() == b_.ino()
&& a_.dev() == b_.dev()
&& fs::canonicalize(a).await? == fs::canonicalize(b).await?,
)
}
#[cfg(windows)]
async fn _paths_to_same_file(a: &Path, b: &Path) -> std::io::Result<bool> {
use std::os::windows::{ffi::OsStringExt, io::AsRawHandle};
use windows_sys::Win32::{Foundation::{HANDLE, MAX_PATH}, Storage::FileSystem::{FILE_FLAG_BACKUP_SEMANTICS, FILE_FLAG_OPEN_REPARSE_POINT, GetFinalPathNameByHandleW, VOLUME_NAME_DOS}};
async fn final_name(p: &Path) -> std::io::Result<PathBuf> {
let file = fs::OpenOptions::new()
.access_mode(0)
.custom_flags(FILE_FLAG_BACKUP_SEMANTICS | FILE_FLAG_OPEN_REPARSE_POINT)
.open(p)
.await?;
tokio::task::spawn_blocking(move || {
let mut buf = [0u16; MAX_PATH as usize];
let len = unsafe {
GetFinalPathNameByHandleW(
file.as_raw_handle() as HANDLE,
buf.as_mut_ptr(),
buf.len() as u32,
VOLUME_NAME_DOS,
)
};
if len == 0 {
Err(std::io::Error::last_os_error())
} else {
Ok(PathBuf::from(OsString::from_wide(&buf[0..len as usize])))
}
})
.await?
}
Ok(final_name(a).await? == final_name(b).await?)
}
pub async fn realname(u: &UrlBuf) -> Option<OsString> {
let name = u.file_name()?;
if *u == provider::canonicalize(u).await.ok()? {
@ -170,7 +117,7 @@ pub fn copy_with_progress(
tokio::spawn({
let (from, to) = (from.clone(), to.clone());
async move {
tick_tx.send(_copy_with_progress(from, to, cha).await).ok();
tick_tx.send(provider::copy(&from, &to, cha).await).ok();
}
});
@ -213,54 +160,6 @@ pub fn copy_with_progress(
rx
}
async fn _copy_with_progress(from: UrlBuf, to: UrlBuf, cha: Cha) -> io::Result<u64> {
let mut ft = std::fs::FileTimes::new();
cha.atime.map(|t| ft = ft.set_accessed(t));
cha.mtime.map(|t| ft = ft.set_modified(t));
#[cfg(target_os = "macos")]
{
use std::os::macos::fs::FileTimesExt;
cha.btime.map(|t| ft = ft.set_created(t));
}
#[cfg(windows)]
{
use std::os::windows::fs::FileTimesExt;
cha.btime.map(|t| ft = ft.set_created(t));
}
#[cfg(any(target_os = "linux", target_os = "android"))]
{
use std::os::{fd::AsRawFd, unix::fs::OpenOptionsExt};
tokio::task::spawn_blocking(move || {
let mut reader = std::fs::File::open(from)?;
let mut writer = std::fs::OpenOptions::new()
.mode(cha.mode as u32) // Do not remove `as u32`, https://github.com/termux/termux-packages/pull/22481
.write(true)
.create(true)
.truncate(true)
.open(to)?;
let written = std::io::copy(&mut reader, &mut writer)?;
unsafe { libc::fchmod(writer.as_raw_fd(), cha.mode) };
writer.set_times(ft).ok();
Ok(written)
})
.await?
}
#[cfg(not(any(target_os = "linux", target_os = "android")))]
{
tokio::task::spawn_blocking(move || {
let written = std::fs::copy(from, &to)?;
std::fs::File::options().write(true).open(to).and_then(|f| f.set_times(ft)).ok();
Ok(written)
})
.await?
}
}
pub async fn remove_dir_clean(dir: &UrlBuf) {
let Ok(mut it) = provider::read_dir(dir).await else { return };

View file

@ -1 +1 @@
yazi_macro::mod_flat!(clean expand path);
yazi_macro::mod_flat!(clean expand path relative);

View file

@ -1,7 +1,6 @@
use std::{borrow::Cow, ffi::{OsStr, OsString}, future::Future, io, path::PathBuf};
use std::{borrow::Cow, ffi::{OsStr, OsString}, future::Future, io};
use anyhow::{Result, bail};
use yazi_shared::{loc::LocBuf, url::{UrlBuf, UrlCow}};
use yazi_shared::url::UrlBuf;
use crate::provider;
@ -63,50 +62,6 @@ async fn _unique_name(mut url: UrlBuf, append: bool) -> io::Result<UrlBuf> {
Ok(url)
}
pub fn url_relative_to<'a>(
from: impl Into<UrlCow<'a>>,
to: impl Into<UrlCow<'a>>,
) -> Result<UrlCow<'a>> {
url_relative_to_impl(from.into(), to.into())
}
pub fn url_relative_to_impl<'a>(from: UrlCow<'a>, to: UrlCow<'a>) -> Result<UrlCow<'a>> {
use yazi_shared::url::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:?}");
};
}
if from.covariant(&to) {
return Ok(UrlBuf { loc: LocBuf::zeroed("."), scheme: to.scheme().clone() }.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)) => {
return Ok(to);
}
(None, None) => break (None, None),
(a, b) if a != b => break (a, b),
_ => (),
}
};
let dots = f_head.into_iter().chain(f_it).map(|_| ParentDir);
let rest = t_head.into_iter().chain(t_it);
let buf: PathBuf = dots.chain(rest).collect();
Ok(UrlBuf { loc: LocBuf::zeroed(buf), scheme: to.scheme().clone() }.into())
}
#[cfg(windows)]
pub fn backslash_to_slash(p: &std::path::Path) -> Cow<'_, std::path::Path> {
let bytes = p.as_os_str().as_encoded_bytes();
@ -125,63 +80,61 @@ pub fn backslash_to_slash(p: &std::path::Path) -> Cow<'_, std::path::Path> {
for &b in rest {
out.push(if b == b'\\' { b'/' } else { b });
}
Cow::Owned(PathBuf::from(unsafe { OsString::from_encoded_bytes_unchecked(out) }))
Cow::Owned(std::path::PathBuf::from(unsafe { OsString::from_encoded_bytes_unchecked(out) }))
}
#[cfg(test)]
mod tests {
use super::url_relative_to;
use yazi_shared::url::UrlCow;
use crate::path::url_relative_to;
#[test]
fn test_path_relative_to() {
fn test_url_relative_to() {
yazi_shared::init_tests();
fn assert(from: &str, to: &str, ret: &str) {
assert_eq!(
url_relative_to(&from.parse().unwrap(), &to.parse().unwrap()).unwrap(),
ret.parse().unwrap()
);
}
#[cfg(unix)]
{
let cases = [
// Same urls
assert("", "", ".");
assert(".", ".", ".");
assert("/a", "/a", ".");
assert("regular:///", "/", ".");
assert("regular://", "regular://", ".");
assert("regular://", "search://kw/", "search://kw/.");
assert("regular:///b", "search://kw//b", "search://kw/.");
("", "", "."),
(".", ".", "."),
("/a", "/a", "."),
("regular:///", "/", "."),
("regular://", "regular://", "."),
("regular://", "search://kw/", "search://kw/."),
("regular:///b", "search://kw//b", "search://kw/."),
// Relative urls
assert("foo", "bar", "../bar");
("foo", "bar", "../bar"),
// Absolute urls
assert("/a/b/c", "/a/b", "../");
assert("/a/b", "/a/b/c", "c");
assert("/a/b/d", "/a/b/c", "../c");
assert("/a/b/c", "/a", "../../");
assert("/a/b/b", "/a/a/b", "../../a/b");
assert("regular:///a/b", "regular:///a/b/c", "c");
assert("/a/b/c/", "search://kw//a/d/", "search://kw/../../d");
assert("search://kw//a/b/c", "search://kw//a/b", "search://kw/../");
("/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
assert("", "sftp://test/", "sftp://test/");
assert("a", "sftp://test/", "sftp://test/");
assert("a", "sftp://test/b", "sftp://test/b");
assert("/a", "sftp://test//b", "sftp://test//b");
assert("sftp://test//a/b", "sftp://test//a/d", "sftp://test/../d");
}
("", "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)]
{
assert(r"C:\a\b\c", r"C:\a\b", r"..\");
assert(r"C:\a\b", r"C:\a\b\c", "c");
assert(r"C:\a\b\d", r"C:\a\b\c", r"..\c");
assert(r"C:\a\b\c", r"C:\a", r"..\..\");
assert(r"C:\a\b\b", r"C:\a\a\b", r"..\..\a\b");
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

@ -0,0 +1,51 @@
use std::{borrow::Cow, path::{Path, PathBuf}};
use anyhow::{Result, bail};
use yazi_shared::{loc::LocBuf, url::{Url, UrlBuf, UrlCow}};
pub fn path_relative_to<'a>(
from: impl AsRef<Path>,
to: &'a impl AsRef<Path>,
) -> Result<Cow<'a, Path>> {
Ok(match url_relative_to(Url::regular(&from).into(), Url::regular(to).into())? {
UrlCow::Borrowed(url) => Cow::Borrowed(url.loc.as_path()),
UrlCow::Owned(url) => Cow::Owned(url.loc.into_path()),
})
}
pub(super) fn url_relative_to<'a>(from: UrlCow<'_>, to: UrlCow<'a>) -> Result<UrlCow<'a>> {
use yazi_shared::url::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:?}");
};
}
if from.covariant(&to) {
return Ok(UrlBuf { loc: LocBuf::zeroed("."), scheme: to.scheme().clone() }.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)) => {
return Ok(to);
}
(None, None) => break (None, None),
(a, b) if a != b => break (a, b),
_ => (),
}
};
let dots = f_head.into_iter().chain(f_it).map(|_| ParentDir);
let rest = t_head.into_iter().chain(t_it);
let buf: PathBuf = dots.chain(rest).collect();
Ok(UrlBuf { loc: LocBuf::zeroed(buf), scheme: to.scheme().clone() }.into())
}

View file

@ -1,90 +1,270 @@
use std::{io, path::{Path, PathBuf}};
use crate::provider::local::{Gate, ReadDir, ReadDirSync, RwFile};
use crate::{cha::Cha, provider::local::{Gate, ReadDir, ReadDirSync, RwFile}};
pub struct Local;
impl Local {
#[inline]
pub async fn canonicalize(path: impl AsRef<Path>) -> io::Result<PathBuf> {
pub fn cache<P>(_: P) -> Option<PathBuf>
where
P: AsRef<Path>,
{
None
}
#[inline]
pub async fn canonicalize<P>(path: P) -> io::Result<PathBuf>
where
P: AsRef<Path>,
{
tokio::fs::canonicalize(path).await
}
#[inline]
pub async fn create(path: impl AsRef<Path>) -> io::Result<RwFile> {
pub async fn copy<P, Q>(from: P, to: Q, cha: Cha) -> io::Result<u64>
where
P: AsRef<Path>,
Q: AsRef<Path>,
{
let from = from.as_ref().to_owned();
let to = to.as_ref().to_owned();
Self::copy_impl(from, to, cha).await
}
async fn copy_impl(from: PathBuf, to: PathBuf, cha: Cha) -> io::Result<u64> {
let mut ft = std::fs::FileTimes::new();
cha.atime.map(|t| ft = ft.set_accessed(t));
cha.mtime.map(|t| ft = ft.set_modified(t));
#[cfg(target_os = "macos")]
{
use std::os::macos::fs::FileTimesExt;
cha.btime.map(|t| ft = ft.set_created(t));
}
#[cfg(windows)]
{
use std::os::windows::fs::FileTimesExt;
cha.btime.map(|t| ft = ft.set_created(t));
}
#[cfg(any(target_os = "linux", target_os = "android"))]
{
use std::os::{fd::AsRawFd, unix::fs::OpenOptionsExt};
tokio::task::spawn_blocking(move || {
let mut reader = std::fs::File::open(from)?;
let mut writer = std::fs::OpenOptions::new()
.mode(cha.mode as u32) // Do not remove `as u32`, https://github.com/termux/termux-packages/pull/22481
.write(true)
.create(true)
.truncate(true)
.open(to)?;
let written = std::io::copy(&mut reader, &mut writer)?;
unsafe { libc::fchmod(writer.as_raw_fd(), cha.mode) };
writer.set_times(ft).ok();
Ok(written)
})
.await?
}
#[cfg(not(any(target_os = "linux", target_os = "android")))]
{
tokio::task::spawn_blocking(move || {
let written = std::fs::copy(from, &to)?;
std::fs::File::options().write(true).open(to).and_then(|f| f.set_times(ft)).ok();
Ok(written)
})
.await?
}
}
#[inline]
pub async fn create<P>(path: P) -> io::Result<RwFile>
where
P: AsRef<Path>,
{
Gate::default().write(true).create(true).truncate(true).open(path).await.map(Into::into)
}
#[inline]
pub async fn create_dir(path: impl AsRef<Path>) -> io::Result<()> {
pub async fn create_dir<P>(path: P) -> io::Result<()>
where
P: AsRef<Path>,
{
tokio::fs::create_dir(path).await
}
#[inline]
pub async fn create_dir_all(path: impl AsRef<Path>) -> io::Result<()> {
pub async fn create_dir_all<P>(path: P) -> io::Result<()>
where
P: AsRef<Path>,
{
tokio::fs::create_dir_all(path).await
}
#[inline]
pub async fn hard_link(original: impl AsRef<Path>, link: impl AsRef<Path>) -> io::Result<()> {
pub async fn hard_link<P, Q>(original: P, link: Q) -> io::Result<()>
where
P: AsRef<Path>,
Q: AsRef<Path>,
{
tokio::fs::hard_link(original, link).await
}
#[inline]
pub async fn metadata(url: impl AsRef<Path>) -> io::Result<std::fs::Metadata> {
pub async fn metadata<P>(url: P) -> io::Result<std::fs::Metadata>
where
P: AsRef<Path>,
{
tokio::fs::metadata(url).await
}
#[inline]
pub async fn open(path: impl AsRef<Path>) -> io::Result<RwFile> {
pub async fn open<P>(path: P) -> io::Result<RwFile>
where
P: AsRef<Path>,
{
Gate::default().read(true).open(path).await.map(Into::into)
}
#[inline]
pub async fn read(path: impl AsRef<Path>) -> io::Result<Vec<u8>> { tokio::fs::read(path).await }
pub async fn read<P>(path: P) -> io::Result<Vec<u8>>
where
P: AsRef<Path>,
{
tokio::fs::read(path).await
}
#[inline]
pub async fn read_dir(path: impl AsRef<Path>) -> io::Result<ReadDir> {
pub async fn read_dir<P>(path: P) -> io::Result<ReadDir>
where
P: AsRef<Path>,
{
tokio::fs::read_dir(path).await.map(Into::into)
}
#[inline]
pub fn read_dir_sync(path: impl AsRef<Path>) -> io::Result<ReadDirSync> {
pub fn read_dir_sync<P>(path: P) -> io::Result<ReadDirSync>
where
P: AsRef<Path>,
{
std::fs::read_dir(path).map(Into::into)
}
#[inline]
pub async fn read_link(url: impl AsRef<Path>) -> io::Result<PathBuf> {
pub async fn read_link<P>(url: P) -> io::Result<PathBuf>
where
P: AsRef<Path>,
{
tokio::fs::read_link(url).await
}
#[inline]
pub async fn read_to_string(path: impl AsRef<Path>) -> io::Result<String> {
pub async fn read_to_string<P>(path: P) -> io::Result<String>
where
P: AsRef<Path>,
{
tokio::fs::read_to_string(path).await
}
#[inline]
pub async fn remove_dir(path: impl AsRef<Path>) -> io::Result<()> {
pub async fn remove_dir<P>(path: P) -> io::Result<()>
where
P: AsRef<Path>,
{
tokio::fs::remove_dir(path).await
}
#[inline]
pub async fn remove_dir_all(path: impl AsRef<Path>) -> io::Result<()> {
pub async fn remove_dir_all<P>(path: P) -> io::Result<()>
where
P: AsRef<Path>,
{
tokio::fs::remove_dir_all(path).await
}
#[inline]
pub async fn remove_file(path: impl AsRef<Path>) -> io::Result<()> {
pub async fn remove_file<P>(path: P) -> io::Result<()>
where
P: AsRef<Path>,
{
tokio::fs::remove_file(path).await
}
#[inline]
pub async fn rename(from: impl AsRef<Path>, to: impl AsRef<Path>) -> io::Result<()> {
pub async fn rename<P, Q>(from: P, to: Q) -> io::Result<()>
where
P: AsRef<Path>,
Q: AsRef<Path>,
{
tokio::fs::rename(from, to).await
}
#[inline]
pub async fn symlink_dir(original: impl AsRef<Path>, link: impl AsRef<Path>) -> io::Result<()> {
pub async fn same<P, Q>(a: P, b: Q) -> io::Result<bool>
where
P: AsRef<Path>,
Q: AsRef<Path>,
{
Self::same_impl(a.as_ref(), b.as_ref()).await
}
#[cfg(unix)]
async fn same_impl(a: &Path, b: &Path) -> io::Result<bool> {
use std::os::unix::fs::MetadataExt;
let (a_, b_) = (tokio::fs::symlink_metadata(a).await?, tokio::fs::symlink_metadata(b).await?);
Ok(
a_.ino() == b_.ino()
&& a_.dev() == b_.dev()
&& tokio::fs::canonicalize(a).await? == tokio::fs::canonicalize(b).await?,
)
}
#[cfg(windows)]
async fn same_impl(a: &Path, b: &Path) -> io::Result<bool> {
use std::{ffi::OsString, os::windows::{ffi::OsStringExt, fs::OpenOptionsExt, io::AsRawHandle}};
use windows_sys::Win32::{Foundation::{HANDLE, MAX_PATH}, Storage::FileSystem::{FILE_FLAG_BACKUP_SEMANTICS, FILE_FLAG_OPEN_REPARSE_POINT, GetFinalPathNameByHandleW, VOLUME_NAME_DOS}};
async fn final_name(path: &Path) -> io::Result<PathBuf> {
let path = path.to_owned();
tokio::task::spawn_blocking(move || {
let file = std::fs::OpenOptions::new()
.access_mode(0)
.custom_flags(FILE_FLAG_BACKUP_SEMANTICS | FILE_FLAG_OPEN_REPARSE_POINT)
.open(path)?;
let mut buf = [0u16; MAX_PATH as usize];
let len = unsafe {
GetFinalPathNameByHandleW(
file.as_raw_handle() as HANDLE,
buf.as_mut_ptr(),
buf.len() as u32,
VOLUME_NAME_DOS,
)
};
if len == 0 {
Err(io::Error::last_os_error())
} else {
Ok(PathBuf::from(OsString::from_wide(&buf[0..len as usize])))
}
})
.await?
}
Ok(final_name(a).await? == final_name(b).await?)
}
#[inline]
pub async fn symlink_dir<P, Q>(original: P, link: Q) -> io::Result<()>
where
P: AsRef<Path>,
Q: AsRef<Path>,
{
#[cfg(unix)]
{
tokio::fs::symlink(original, link).await
@ -96,7 +276,11 @@ impl Local {
}
#[inline]
pub async fn symlink_file(original: impl AsRef<Path>, link: impl AsRef<Path>) -> io::Result<()> {
pub async fn symlink_file<P, Q>(original: P, link: Q) -> io::Result<()>
where
P: AsRef<Path>,
Q: AsRef<Path>,
{
#[cfg(unix)]
{
tokio::fs::symlink(original, link).await
@ -108,17 +292,52 @@ impl Local {
}
#[inline]
pub async fn symlink_metadata(path: impl AsRef<Path>) -> io::Result<std::fs::Metadata> {
pub async fn symlink_metadata<P>(path: P) -> io::Result<std::fs::Metadata>
where
P: AsRef<Path>,
{
tokio::fs::symlink_metadata(path).await
}
#[inline]
pub fn symlink_metadata_sync(path: impl AsRef<Path>) -> io::Result<std::fs::Metadata> {
pub fn symlink_metadata_sync<P>(path: P) -> io::Result<std::fs::Metadata>
where
P: AsRef<Path>,
{
std::fs::symlink_metadata(path)
}
pub async fn trash<P>(path: P) -> io::Result<()>
where
P: AsRef<Path>,
{
let path = path.as_ref().to_owned();
tokio::task::spawn_blocking(move || {
#[cfg(target_os = "android")]
{
Err(io::Error::new(io::ErrorKind::Unsupported, "Unsupported OS for trash operation"))
}
#[cfg(target_os = "macos")]
{
use trash::{TrashContext, macos::{DeleteMethod, TrashContextExtMacos}};
let mut ctx = TrashContext::default();
ctx.set_delete_method(DeleteMethod::NsFileManager);
ctx.delete(path).map_err(io::Error::other)
}
#[cfg(all(not(target_os = "macos"), not(target_os = "android")))]
{
trash::delete(path).map_err(io::Error::other)
}
})
.await?
}
#[inline]
pub async fn write(path: impl AsRef<Path>, contents: impl AsRef<[u8]>) -> io::Result<()> {
pub async fn write<P, C>(path: P, contents: C) -> io::Result<()>
where
P: AsRef<Path>,
C: AsRef<[u8]>,
{
tokio::fs::write(path, contents).await
}
}

View file

@ -15,9 +15,4 @@ impl From<tokio::fs::File> for crate::provider::RwFile {
impl RwFile {
#[inline]
pub fn reader(self) -> tokio::io::BufReader<tokio::fs::File> { tokio::io::BufReader::new(self.0) }
#[inline]
pub async fn reader_sync(self) -> std::io::BufReader<std::fs::File> {
std::io::BufReader::new(self.0.into_std().await)
}
}

View file

@ -1,11 +1,22 @@
use std::io;
use std::{io, path::{Path, PathBuf}};
use yazi_shared::url::{Url, UrlBuf};
use crate::provider::{ReadDir, ReadDirSync, RwFile, local::Local};
use crate::{cha::Cha, provider::{ReadDir, ReadDirSync, RwFile, local::Local}};
#[inline]
pub async fn canonicalize<'a>(url: impl Into<Url<'a>>) -> io::Result<UrlBuf> {
pub fn cache<'a, U>(url: U) -> Option<PathBuf>
where
U: Into<Url<'a>>,
{
if let Some(path) = url.into().as_path() { Local::cache(path) } else { None }
}
#[inline]
pub async fn canonicalize<'a, U>(url: U) -> io::Result<UrlBuf>
where
U: Into<Url<'a>>,
{
if let Some(path) = url.into().as_path() {
Local::canonicalize(path).await.map(Into::into)
} else {
@ -14,7 +25,23 @@ pub async fn canonicalize<'a>(url: impl Into<Url<'a>>) -> io::Result<UrlBuf> {
}
#[inline]
pub async fn create<'a>(url: impl Into<Url<'a>>) -> io::Result<RwFile> {
pub async fn copy<'a, U, V>(from: U, to: V, cha: Cha) -> io::Result<u64>
where
U: Into<Url<'a>>,
V: Into<Url<'a>>,
{
if let (Some(from), Some(to)) = (from.into().as_path(), to.into().as_path()) {
Local::copy(from, to, cha).await
} else {
Err(io::Error::new(io::ErrorKind::Unsupported, "Unsupported filesystem"))
}
}
#[inline]
pub async fn create<'a, U>(url: U) -> io::Result<RwFile>
where
U: Into<Url<'a>>,
{
if let Some(path) = url.into().as_path() {
Local::create(path).await.map(Into::into)
} else {
@ -23,7 +50,10 @@ pub async fn create<'a>(url: impl Into<Url<'a>>) -> io::Result<RwFile> {
}
#[inline]
pub async fn create_dir<'a>(url: impl Into<Url<'a>>) -> io::Result<()> {
pub async fn create_dir<'a, U>(url: U) -> io::Result<()>
where
U: Into<Url<'a>>,
{
if let Some(path) = url.into().as_path() {
Local::create_dir(path).await
} else {
@ -32,7 +62,10 @@ pub async fn create_dir<'a>(url: impl Into<Url<'a>>) -> io::Result<()> {
}
#[inline]
pub async fn create_dir_all<'a>(url: impl Into<Url<'a>>) -> io::Result<()> {
pub async fn create_dir_all<'a, U>(url: U) -> io::Result<()>
where
U: Into<Url<'a>>,
{
if let Some(path) = url.into().as_path() {
Local::create_dir_all(path).await
} else {
@ -41,10 +74,11 @@ pub async fn create_dir_all<'a>(url: impl Into<Url<'a>>) -> io::Result<()> {
}
#[inline]
pub async fn hard_link<'a>(
original: impl Into<Url<'a>>,
link: impl Into<Url<'a>>,
) -> io::Result<()> {
pub async fn hard_link<'a, U, V>(original: U, link: V) -> io::Result<()>
where
U: Into<Url<'a>>,
V: Into<Url<'a>>,
{
if let (Some(original), Some(link)) = (original.into().as_path(), link.into().as_path()) {
Local::hard_link(original, link).await
} else {
@ -53,7 +87,10 @@ pub async fn hard_link<'a>(
}
#[inline]
pub async fn metadata<'a>(url: impl Into<Url<'a>>) -> io::Result<std::fs::Metadata> {
pub async fn metadata<'a, U>(url: U) -> io::Result<std::fs::Metadata>
where
U: Into<Url<'a>>,
{
if let Some(path) = url.into().as_path() {
Local::metadata(path).await
} else {
@ -62,7 +99,10 @@ pub async fn metadata<'a>(url: impl Into<Url<'a>>) -> io::Result<std::fs::Metada
}
#[inline]
pub async fn open<'a>(url: impl Into<Url<'a>>) -> io::Result<RwFile> {
pub async fn open<'a, U>(url: U) -> io::Result<RwFile>
where
U: Into<Url<'a>>,
{
if let Some(path) = url.into().as_path() {
Local::open(path).await.map(Into::into)
} else {
@ -71,7 +111,10 @@ pub async fn open<'a>(url: impl Into<Url<'a>>) -> io::Result<RwFile> {
}
#[inline]
pub async fn read_dir<'a>(url: impl Into<Url<'a>>) -> io::Result<ReadDir> {
pub async fn read_dir<'a, U>(url: U) -> io::Result<ReadDir>
where
U: Into<Url<'a>>,
{
if let Some(path) = url.into().as_path() {
Local::read_dir(path).await.map(Into::into)
} else {
@ -80,7 +123,10 @@ pub async fn read_dir<'a>(url: impl Into<Url<'a>>) -> io::Result<ReadDir> {
}
#[inline]
pub fn read_dir_sync<'a>(url: impl Into<Url<'a>>) -> io::Result<ReadDirSync> {
pub fn read_dir_sync<'a, U>(url: U) -> io::Result<ReadDirSync>
where
U: Into<Url<'a>>,
{
if let Some(path) = url.into().as_path() {
Local::read_dir_sync(path).map(Into::into)
} else {
@ -89,16 +135,22 @@ pub fn read_dir_sync<'a>(url: impl Into<Url<'a>>) -> io::Result<ReadDirSync> {
}
#[inline]
pub async fn read_link<'a>(url: impl Into<Url<'a>>) -> io::Result<UrlBuf> {
pub async fn read_link<'a, U>(url: U) -> io::Result<PathBuf>
where
U: Into<Url<'a>>,
{
if let Some(path) = url.into().as_path() {
Local::read_link(path).await.map(Into::into)
Local::read_link(path).await
} else {
Err(io::Error::new(io::ErrorKind::Unsupported, "Unsupported filesystem"))
}
}
#[inline]
pub async fn remove_dir<'a>(url: impl Into<Url<'a>>) -> io::Result<()> {
pub async fn remove_dir<'a, U>(url: U) -> io::Result<()>
where
U: Into<Url<'a>>,
{
if let Some(path) = url.into().as_path() {
Local::remove_dir(path).await
} else {
@ -107,7 +159,10 @@ pub async fn remove_dir<'a>(url: impl Into<Url<'a>>) -> io::Result<()> {
}
#[inline]
pub async fn remove_dir_all<'a>(url: impl Into<Url<'a>>) -> io::Result<()> {
pub async fn remove_dir_all<'a, U>(url: U) -> io::Result<()>
where
U: Into<Url<'a>>,
{
if let Some(path) = url.into().as_path() {
Local::remove_dir_all(path).await
} else {
@ -116,7 +171,10 @@ pub async fn remove_dir_all<'a>(url: impl Into<Url<'a>>) -> io::Result<()> {
}
#[inline]
pub async fn remove_file<'a>(url: impl Into<Url<'a>>) -> io::Result<()> {
pub async fn remove_file<'a, U>(url: U) -> io::Result<()>
where
U: Into<Url<'a>>,
{
if let Some(path) = url.into().as_path() {
Local::remove_file(path).await
} else {
@ -125,7 +183,11 @@ pub async fn remove_file<'a>(url: impl Into<Url<'a>>) -> io::Result<()> {
}
#[inline]
pub async fn rename<'a>(from: impl Into<Url<'a>>, to: impl Into<Url<'a>>) -> io::Result<()> {
pub async fn rename<'a, U, V>(from: U, to: V) -> io::Result<()>
where
U: Into<Url<'a>>,
V: Into<Url<'a>>,
{
if let (Some(from), Some(to)) = (from.into().as_path(), to.into().as_path()) {
Local::rename(from, to).await
} else {
@ -134,11 +196,24 @@ pub async fn rename<'a>(from: impl Into<Url<'a>>, to: impl Into<Url<'a>>) -> io:
}
#[inline]
pub async fn symlink_dir<'a>(
original: impl Into<Url<'a>>,
link: impl Into<Url<'a>>,
) -> io::Result<()> {
if let (Some(original), Some(link)) = (original.into().as_path(), link.into().as_path()) {
pub async fn same<'a, U, V>(a: U, b: V) -> io::Result<bool>
where
U: Into<Url<'a>>,
V: Into<Url<'a>>,
{
if let (Some(a), Some(b)) = (a.into().as_path(), b.into().as_path()) {
Local::same(a, b).await
} else {
Err(io::Error::new(io::ErrorKind::Unsupported, "Unsupported filesystem"))
}
}
#[inline]
pub async fn symlink_dir<'a, U>(original: &Path, link: U) -> io::Result<()>
where
U: Into<Url<'a>>,
{
if let Some(link) = link.into().as_path() {
Local::symlink_dir(original, link).await
} else {
Err(io::Error::new(io::ErrorKind::Unsupported, "Unsupported filesystem"))
@ -146,11 +221,11 @@ pub async fn symlink_dir<'a>(
}
#[inline]
pub async fn symlink_file<'a>(
original: impl Into<Url<'a>>,
link: impl Into<Url<'a>>,
) -> io::Result<()> {
if let (Some(original), Some(link)) = (original.into().as_path(), link.into().as_path()) {
pub async fn symlink_file<'a, U>(original: &Path, link: U) -> io::Result<()>
where
U: Into<Url<'a>>,
{
if let Some(link) = link.into().as_path() {
Local::symlink_file(original, link).await
} else {
Err(io::Error::new(io::ErrorKind::Unsupported, "Unsupported filesystem"))
@ -158,7 +233,10 @@ pub async fn symlink_file<'a>(
}
#[inline]
pub async fn symlink_metadata<'a>(url: impl Into<Url<'a>>) -> io::Result<std::fs::Metadata> {
pub async fn symlink_metadata<'a, U>(url: U) -> io::Result<std::fs::Metadata>
where
U: Into<Url<'a>>,
{
if let Some(path) = url.into().as_path() {
Local::symlink_metadata(path).await
} else {
@ -166,7 +244,11 @@ pub async fn symlink_metadata<'a>(url: impl Into<Url<'a>>) -> io::Result<std::fs
}
}
pub fn symlink_metadata_sync<'a>(url: impl Into<Url<'a>>) -> io::Result<std::fs::Metadata> {
#[inline]
pub fn symlink_metadata_sync<'a, U>(url: U) -> io::Result<std::fs::Metadata>
where
U: Into<Url<'a>>,
{
if let Some(path) = url.into().as_path() {
Local::symlink_metadata_sync(path)
} else {
@ -175,7 +257,23 @@ pub fn symlink_metadata_sync<'a>(url: impl Into<Url<'a>>) -> io::Result<std::fs:
}
#[inline]
pub async fn write<'a>(url: impl Into<Url<'a>>, contents: impl AsRef<[u8]>) -> io::Result<()> {
pub async fn trash<'a, U>(url: U) -> io::Result<()>
where
U: Into<Url<'a>>,
{
if let Some(path) = url.into().as_path() {
Local::trash(path).await
} else {
Err(io::Error::new(io::ErrorKind::Unsupported, "Unsupported filesystem"))
}
}
#[inline]
pub async fn write<'a, U, C>(url: U, contents: C) -> io::Result<()>
where
U: Into<Url<'a>>,
C: AsRef<[u8]>,
{
if let Some(path) = url.into().as_path() {
Local::write(path, contents).await
} else {

View file

@ -1,4 +1,4 @@
use crate::provider::{BufRead, BufReadSync};
use crate::provider::BufRead;
pub enum RwFile {
Local(super::local::RwFile),
@ -11,11 +11,4 @@ impl RwFile {
RwFile::Local(local) => Box::new(local.reader()),
}
}
#[inline]
pub async fn reader_sync(self) -> Box<dyn BufReadSync> {
match self {
RwFile::Local(local) => Box::new(local.reader_sync().await),
}
}
}