mirror of
https://github.com/sxyazi/yazi.git
synced 2026-07-21 23:01:05 +00:00
feat: SFTP virtual filesystem provider (#3172)
This commit is contained in:
parent
7deeaa4356
commit
6cfa92f112
49 changed files with 1086 additions and 749 deletions
39
Cargo.lock
generated
39
Cargo.lock
generated
|
|
@ -874,6 +874,27 @@ version = "2.9.0"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2a2330da5de22e8a3cb63252ce2abb30116bf5265e89c0e01bc17015ce30a476"
|
||||
|
||||
[[package]]
|
||||
name = "deadpool"
|
||||
version = "0.12.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0be2b1d1d6ec8d846f05e137292d0b89133caf95ef33695424c09568bdd39b1b"
|
||||
dependencies = [
|
||||
"deadpool-runtime",
|
||||
"lazy_static",
|
||||
"num_cpus",
|
||||
"tokio",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "deadpool-runtime"
|
||||
version = "0.1.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "092966b41edc516079bdf31ec78a2e0588d1d0c08f78b91d8307215928642b2b"
|
||||
dependencies = [
|
||||
"tokio",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "delegate"
|
||||
version = "0.13.4"
|
||||
|
|
@ -1483,6 +1504,12 @@ version = "0.5.0"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea"
|
||||
|
||||
[[package]]
|
||||
name = "hermit-abi"
|
||||
version = "0.5.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "fc0fef456e4baa96da950455cd02c081ca953b141298e41db3fc7e36b1da849c"
|
||||
|
||||
[[package]]
|
||||
name = "hex"
|
||||
version = "0.4.3"
|
||||
|
|
@ -2185,6 +2212,16 @@ dependencies = [
|
|||
"libm",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "num_cpus"
|
||||
version = "1.17.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "91df4bbde75afed763b708b7eee1e8e7651e02d97f6d5dd763e89367e957b23b"
|
||||
dependencies = [
|
||||
"hermit-abi",
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "num_threads"
|
||||
version = "0.1.7"
|
||||
|
|
@ -4810,6 +4847,7 @@ dependencies = [
|
|||
"arc-swap",
|
||||
"bitflags 2.9.4",
|
||||
"core-foundation-sys",
|
||||
"deadpool",
|
||||
"dirs",
|
||||
"foldhash 0.2.0",
|
||||
"futures",
|
||||
|
|
@ -4827,6 +4865,7 @@ dependencies = [
|
|||
"windows-sys 0.61.0",
|
||||
"yazi-ffi",
|
||||
"yazi-macro",
|
||||
"yazi-sftp",
|
||||
"yazi-shared",
|
||||
]
|
||||
|
||||
|
|
|
|||
|
|
@ -47,7 +47,7 @@ impl Actor for Trigger {
|
|||
|
||||
while let Ok(Some(ent)) = dir.next_entry().await {
|
||||
if let Ok(ft) = ent.file_type().await {
|
||||
cache.push(CmpItem { name: ent.file_name(), is_dir: ft.is_dir() });
|
||||
cache.push(CmpItem { name: ent.name().into_owned(), is_dir: ft.is_dir() });
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@ use scopeguard::defer;
|
|||
use tokio::io::AsyncWriteExt;
|
||||
use yazi_config::{YAZI, opener::OpenerRule};
|
||||
use yazi_dds::Pubsub;
|
||||
use yazi_fs::{File, FilesOp, max_common_root, maybe_exists, path::skip_url, provider::{self, local::{Gate, Local}}};
|
||||
use yazi_fs::{File, FilesOp, max_common_root, maybe_exists, path::skip_url, provider::{self, FileBuilder, Provider, local::{Gate, Local}}};
|
||||
use yazi_macro::{err, succ};
|
||||
use yazi_parser::VoidOpt;
|
||||
use yazi_proxy::{AppProxy, HIDER, TasksProxy};
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@ use anyhow::Result;
|
|||
use image::{DynamicImage, ExtendedColorType, ImageDecoder, ImageEncoder, ImageError, ImageReader, ImageResult, Limits, codecs::{jpeg::JpegEncoder, png::PngEncoder}, imageops::FilterType, metadata::Orientation};
|
||||
use ratatui::layout::Rect;
|
||||
use yazi_config::YAZI;
|
||||
use yazi_fs::provider::local::Local;
|
||||
use yazi_fs::provider::{Provider, local::Local};
|
||||
|
||||
use crate::Dimension;
|
||||
|
||||
|
|
|
|||
|
|
@ -37,16 +37,11 @@ impl Cha {
|
|||
len: t.raw_get("len").unwrap_or_default(),
|
||||
atime: parse_time(t.raw_get("atime").ok())?,
|
||||
btime: parse_time(t.raw_get("btime").ok())?,
|
||||
#[cfg(unix)]
|
||||
ctime: parse_time(t.raw_get("ctime").ok())?,
|
||||
mtime: parse_time(t.raw_get("mtime").ok())?,
|
||||
#[cfg(unix)]
|
||||
dev: t.raw_get("dev").unwrap_or_default(),
|
||||
#[cfg(unix)]
|
||||
uid: t.raw_get("uid").unwrap_or_default(),
|
||||
#[cfg(unix)]
|
||||
gid: t.raw_get("gid").unwrap_or_default(),
|
||||
#[cfg(unix)]
|
||||
nlink: t.raw_get("nlink").unwrap_or_default(),
|
||||
})
|
||||
.into_lua(lua)
|
||||
|
|
@ -70,15 +65,6 @@ impl UserData for Cha {
|
|||
fields.add_field_method_get("is_exec", |_, me| Ok(me.is_exec()));
|
||||
fields.add_field_method_get("is_sticky", |_, me| Ok(me.is_sticky()));
|
||||
|
||||
#[cfg(unix)]
|
||||
{
|
||||
use std::ops::Not;
|
||||
fields.add_field_method_get("dev", |_, me| Ok(me.is_dummy().not().then_some(me.dev)));
|
||||
fields.add_field_method_get("uid", |_, me| Ok(me.is_dummy().not().then_some(me.uid)));
|
||||
fields.add_field_method_get("gid", |_, me| Ok(me.is_dummy().not().then_some(me.gid)));
|
||||
fields.add_field_method_get("nlink", |_, me| Ok(me.is_dummy().not().then_some(me.nlink)));
|
||||
}
|
||||
|
||||
fields.add_field_method_get("len", |_, me| Ok(me.len));
|
||||
fields.add_field_method_get("atime", |_, me| {
|
||||
Ok(me.atime.and_then(|t| t.duration_since(UNIX_EPOCH).map(|d| d.as_secs_f64()).ok()))
|
||||
|
|
@ -86,13 +72,16 @@ impl UserData for Cha {
|
|||
fields.add_field_method_get("btime", |_, me| {
|
||||
Ok(me.btime.and_then(|t| t.duration_since(UNIX_EPOCH).map(|d| d.as_secs_f64()).ok()))
|
||||
});
|
||||
#[cfg(unix)]
|
||||
fields.add_field_method_get("ctime", |_, me| {
|
||||
Ok(me.ctime.and_then(|t| t.duration_since(UNIX_EPOCH).map(|d| d.as_secs_f64()).ok()))
|
||||
});
|
||||
fields.add_field_method_get("mtime", |_, me| {
|
||||
Ok(me.mtime.and_then(|t| t.duration_since(UNIX_EPOCH).map(|d| d.as_secs_f64()).ok()))
|
||||
});
|
||||
fields.add_field_method_get("dev", |_, me| Ok(me.dev));
|
||||
fields.add_field_method_get("uid", |_, me| Ok(me.uid));
|
||||
fields.add_field_method_get("gid", |_, me| Ok(me.gid));
|
||||
fields.add_field_method_get("nlink", |_, me| Ok(me.nlink));
|
||||
}
|
||||
|
||||
fn add_methods<M: UserDataMethods<Self>>(methods: &mut M) {
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
use anyhow::{Context, Result};
|
||||
use yazi_fs::{ok_or_not_found, provider::local::Local, remove_dir_clean};
|
||||
use yazi_fs::{ok_or_not_found, provider::{DirReader, FileHolder, Provider, local::Local}, remove_dir_clean};
|
||||
use yazi_macro::outln;
|
||||
|
||||
use super::Dependency;
|
||||
|
|
@ -25,7 +25,7 @@ impl Dependency {
|
|||
let assets = self.target().join("assets");
|
||||
match Local::read_dir(&assets).await {
|
||||
Ok(mut it) => {
|
||||
while let Some(entry) = it.next_entry().await? {
|
||||
while let Some(entry) = it.next().await? {
|
||||
remove_sealed(&entry.path())
|
||||
.await
|
||||
.with_context(|| format!("failed to remove `{}`", entry.path().display()))?;
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@ use std::{io::BufWriter, path::{Path, PathBuf}, str::FromStr};
|
|||
use anyhow::{Result, bail};
|
||||
use serde::{Deserialize, Deserializer, Serialize, Serializer};
|
||||
use twox_hash::XxHash3_128;
|
||||
use yazi_fs::{Xdg, provider::local::Local};
|
||||
use yazi_fs::{Xdg, provider::{DirReader, FileHolder, Provider, local::Local}};
|
||||
use yazi_shared::BytesExt;
|
||||
|
||||
#[derive(Clone, Default)]
|
||||
|
|
@ -65,8 +65,8 @@ impl Dependency {
|
|||
let mut it = Local::read_dir(dir).await?;
|
||||
let mut files: Vec<String> =
|
||||
["LICENSE", "README.md", "main.lua"].into_iter().map(Into::into).collect();
|
||||
while let Some(entry) = it.next_entry().await? {
|
||||
if let Ok(name) = entry.file_name().into_string()
|
||||
while let Some(entry) = it.next().await? {
|
||||
if let Ok(name) = entry.name().into_owned().into_string()
|
||||
&& let Some(stripped) = name.strip_suffix(".lua")
|
||||
&& stripped != "main"
|
||||
&& stripped.as_bytes().kebab_cased()
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
use std::path::{Path, PathBuf};
|
||||
|
||||
use anyhow::{Context, Result};
|
||||
use yazi_fs::{provider::local::Local, remove_dir_clean};
|
||||
use yazi_fs::{provider::{DirReader, FileHolder, Provider, local::Local}, remove_dir_clean};
|
||||
use yazi_macro::outln;
|
||||
|
||||
use super::Dependency;
|
||||
|
|
@ -43,8 +43,8 @@ impl Dependency {
|
|||
match Local::read_dir(&from).await {
|
||||
Ok(mut it) => {
|
||||
Local::create_dir_all(&to).await?;
|
||||
while let Some(entry) = it.next_entry().await? {
|
||||
let (src, dist) = (entry.path(), to.join(entry.file_name()));
|
||||
while let Some(entry) = it.next().await? {
|
||||
let (src, dist) = (entry.path(), to.join(entry.name()));
|
||||
copy_and_seal(&src, &dist).await.with_context(|| {
|
||||
format!("failed to copy `{}` to `{}`", src.display(), dist.display())
|
||||
})?;
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
use anyhow::{Context, Result, bail};
|
||||
use twox_hash::XxHash3_128;
|
||||
use yazi_fs::{ok_or_not_found, provider::local::Local};
|
||||
use yazi_fs::{ok_or_not_found, provider::{DirReader, FileHolder, Provider, local::Local}};
|
||||
|
||||
use super::Dependency;
|
||||
|
||||
|
|
@ -20,8 +20,8 @@ impl Dependency {
|
|||
let mut assets = vec![];
|
||||
match Local::read_dir(dir.join("assets")).await {
|
||||
Ok(mut it) => {
|
||||
while let Some(entry) = it.next_entry().await? {
|
||||
let Ok(name) = entry.file_name().into_string() else {
|
||||
while let Some(entry) = it.next().await? {
|
||||
let Ok(name) = entry.name().into_owned().into_string() else {
|
||||
bail!("asset path is not valid UTF-8: {}", entry.path().display());
|
||||
};
|
||||
assets.push((name, Local::read(entry.path()).await?));
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@ use std::{path::PathBuf, str::FromStr};
|
|||
|
||||
use anyhow::{Context, Result, bail};
|
||||
use serde::{Deserialize, Deserializer, Serialize, Serializer};
|
||||
use yazi_fs::{Xdg, provider::local::Local};
|
||||
use yazi_fs::{Xdg, provider::{Provider, local::Local}};
|
||||
use yazi_macro::outln;
|
||||
|
||||
use super::Dependency;
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
use std::{io, path::Path};
|
||||
|
||||
use tokio::io::AsyncWriteExt;
|
||||
use yazi_fs::{ok_or_not_found, provider::local::{Gate, Local}};
|
||||
use yazi_fs::{ok_or_not_found, provider::{FileBuilder, Provider, local::{Gate, Local}}};
|
||||
|
||||
#[inline]
|
||||
pub async fn must_exists(path: impl AsRef<Path>) -> bool {
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@ use hashbrown::HashMap;
|
|||
use parking_lot::RwLock;
|
||||
use tokio::io::{AsyncBufReadExt, AsyncWriteExt, BufReader, BufWriter};
|
||||
use yazi_boot::BOOT;
|
||||
use yazi_fs::provider::local::{Gate, Local};
|
||||
use yazi_fs::provider::{FileBuilder, Provider, local::{Gate, Local}};
|
||||
use yazi_shared::{RoCell, timestamp_us};
|
||||
|
||||
use crate::CLIENTS;
|
||||
|
|
|
|||
|
|
@ -36,6 +36,8 @@ impl Stream {
|
|||
|
||||
#[cfg(unix)]
|
||||
pub(super) async fn bind() -> std::io::Result<ServerListener> {
|
||||
use yazi_fs::provider::Provider;
|
||||
|
||||
let p = Self::socket_file();
|
||||
|
||||
yazi_fs::provider::local::Local::remove_file(&p).await.ok();
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
use std::ffi::OsString;
|
||||
|
||||
use yazi_boot::ARGS;
|
||||
use yazi_fs::provider::local::Local;
|
||||
use yazi_fs::provider::{Provider, local::Local};
|
||||
use yazi_shared::event::EventQuit;
|
||||
|
||||
use crate::{Term, app::App};
|
||||
|
|
|
|||
|
|
@ -11,12 +11,14 @@ repository = "https://github.com/sxyazi/yazi"
|
|||
[dependencies]
|
||||
yazi-ffi = { path = "../yazi-ffi", version = "25.9.15" }
|
||||
yazi-macro = { path = "../yazi-macro", version = "25.9.15" }
|
||||
yazi-sftp = { path = "../yazi-sftp", version = "0.1.0" }
|
||||
yazi-shared = { path = "../yazi-shared", version = "25.9.15" }
|
||||
|
||||
# External dependencies
|
||||
anyhow = { workspace = true }
|
||||
arc-swap = "1.7.1"
|
||||
bitflags = { workspace = true }
|
||||
deadpool = { version = "0.12.3", default-features = false, features = [ "managed", "rt_tokio_1" ] }
|
||||
dirs = { workspace = true }
|
||||
foldhash = { workspace = true }
|
||||
futures = { workspace = true }
|
||||
|
|
|
|||
|
|
@ -13,16 +13,11 @@ pub struct Cha {
|
|||
pub len: u64,
|
||||
pub atime: Option<SystemTime>,
|
||||
pub btime: Option<SystemTime>,
|
||||
#[cfg(unix)]
|
||||
pub ctime: Option<SystemTime>,
|
||||
pub mtime: Option<SystemTime>,
|
||||
#[cfg(unix)]
|
||||
pub dev: libc::dev_t,
|
||||
#[cfg(unix)]
|
||||
pub dev: u64,
|
||||
pub uid: u32,
|
||||
#[cfg(unix)]
|
||||
pub gid: u32,
|
||||
#[cfg(unix)]
|
||||
pub nlink: u64,
|
||||
}
|
||||
|
||||
|
|
@ -35,22 +30,17 @@ impl Deref for Cha {
|
|||
impl Default for Cha {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
kind: ChaKind::DUMMY,
|
||||
mode: ChaMode::empty(),
|
||||
len: 0,
|
||||
atime: None,
|
||||
btime: None,
|
||||
#[cfg(unix)]
|
||||
ctime: None,
|
||||
mtime: None,
|
||||
#[cfg(unix)]
|
||||
dev: 0,
|
||||
#[cfg(unix)]
|
||||
uid: 0,
|
||||
#[cfg(unix)]
|
||||
gid: 0,
|
||||
#[cfg(unix)]
|
||||
nlink: 0,
|
||||
kind: ChaKind::DUMMY,
|
||||
mode: ChaMode::empty(),
|
||||
len: 0,
|
||||
atime: None,
|
||||
btime: None,
|
||||
ctime: None,
|
||||
mtime: None,
|
||||
dev: 0,
|
||||
uid: 0,
|
||||
gid: 0,
|
||||
nlink: 0,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -136,17 +126,15 @@ impl Cha {
|
|||
len: m.len(),
|
||||
atime: m.accessed().ok(),
|
||||
btime: m.created().ok(),
|
||||
#[cfg(unix)]
|
||||
ctime: UNIX_EPOCH.checked_add(Duration::new(m.ctime() as u64, m.ctime_nsec() as u32)),
|
||||
ctime: unix_either!(
|
||||
UNIX_EPOCH.checked_add(Duration::new(m.ctime() as u64, m.ctime_nsec() as u32)),
|
||||
None
|
||||
),
|
||||
mtime: m.modified().ok(),
|
||||
#[cfg(unix)]
|
||||
dev: m.dev() as _,
|
||||
#[cfg(unix)]
|
||||
uid: m.uid() as _,
|
||||
#[cfg(unix)]
|
||||
gid: m.gid() as _,
|
||||
#[cfg(unix)]
|
||||
nlink: m.nlink() as _,
|
||||
dev: unix_either!(m.dev(), 0) as _,
|
||||
uid: unix_either!(m.uid(), 0) as _,
|
||||
gid: unix_either!(m.gid(), 0) as _,
|
||||
nlink: unix_either!(m.nlink(), 0) as _,
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
#![allow(clippy::if_same_then_else, clippy::option_map_unit_fn)]
|
||||
#![allow(clippy::if_same_then_else, clippy::option_map_unit_fn, clippy::unit_arg)]
|
||||
|
||||
yazi_macro::mod_pub!(cha mounts provider path);
|
||||
|
||||
|
|
@ -8,4 +8,6 @@ pub fn init() {
|
|||
CWD.init(<_>::default());
|
||||
|
||||
mounts::init();
|
||||
|
||||
provider::init();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -9,22 +9,22 @@ use yazi_shared::{natsort, replace_cow, replace_vec_cow};
|
|||
use super::{Locked, Partition, Partitions};
|
||||
|
||||
impl Partitions {
|
||||
pub fn monitor<F>(me: Locked, cb: F)
|
||||
pub fn monitor<F>(me: &'static Locked, cb: F)
|
||||
where
|
||||
F: Fn() + Copy + Send + 'static,
|
||||
{
|
||||
async fn wait_mounts(me: Locked, cb: impl Fn()) -> Result<()> {
|
||||
async fn wait_mounts(me: &'static Locked, cb: impl Fn()) -> Result<()> {
|
||||
let f = std::fs::File::open("/proc/mounts")?;
|
||||
let fd = AsyncFd::with_interest(f.as_fd(), Interest::READABLE)?;
|
||||
loop {
|
||||
let mut guard = fd.readable().await?;
|
||||
guard.clear_ready();
|
||||
Partitions::update(me.clone()).await;
|
||||
Partitions::update(me).await;
|
||||
cb();
|
||||
}
|
||||
}
|
||||
|
||||
async fn wait_partitions(me: Locked, cb: impl Fn()) -> Result<()> {
|
||||
async fn wait_partitions(me: &'static Locked, cb: impl Fn()) -> Result<()> {
|
||||
loop {
|
||||
let partitions = Partitions::partitions()?;
|
||||
if me.read().linux_cache == partitions {
|
||||
|
|
@ -33,17 +33,16 @@ impl Partitions {
|
|||
}
|
||||
|
||||
me.write().linux_cache = partitions;
|
||||
Partitions::update(me.clone()).await;
|
||||
Partitions::update(me).await;
|
||||
|
||||
cb();
|
||||
sleep(Duration::from_secs(3)).await;
|
||||
}
|
||||
}
|
||||
|
||||
let me_ = me.clone();
|
||||
tokio::spawn(async move {
|
||||
loop {
|
||||
if let Err(e) = wait_mounts(me_.clone(), cb).await {
|
||||
if let Err(e) = wait_mounts(me, cb).await {
|
||||
error!("Error encountered while monitoring /proc/mounts: {e:?}");
|
||||
}
|
||||
sleep(Duration::from_secs(5)).await;
|
||||
|
|
@ -52,7 +51,7 @@ impl Partitions {
|
|||
|
||||
tokio::spawn(async move {
|
||||
loop {
|
||||
if let Err(e) = wait_partitions(me.clone(), cb).await {
|
||||
if let Err(e) = wait_partitions(me, cb).await {
|
||||
error!("Error encountered while monitoring /proc/partitions: {e:?}");
|
||||
}
|
||||
sleep(Duration::from_secs(5)).await;
|
||||
|
|
@ -75,7 +74,7 @@ impl Partitions {
|
|||
Ok(set)
|
||||
}
|
||||
|
||||
async fn update(me: Locked) {
|
||||
async fn update(me: &'static Locked) {
|
||||
_ = tokio::task::spawn_blocking(move || {
|
||||
let mut guard = me.write();
|
||||
match Self::all(&guard) {
|
||||
|
|
|
|||
|
|
@ -12,7 +12,7 @@ use yazi_shared::natsort;
|
|||
use super::{Locked, Partition, Partitions};
|
||||
|
||||
impl Partitions {
|
||||
pub fn monitor<F>(me: Locked, cb: F)
|
||||
pub fn monitor<F>(me: &'static Locked, cb: F)
|
||||
where
|
||||
F: Fn() + Copy + Send + 'static,
|
||||
{
|
||||
|
|
@ -39,12 +39,11 @@ impl Partitions {
|
|||
}
|
||||
|
||||
let create_context = || {
|
||||
let me = me.clone();
|
||||
let boxed: Box<dyn Fn()> = Box::new(move || {
|
||||
if mem::replace(&mut me.write().need_update, true) {
|
||||
return;
|
||||
}
|
||||
Self::update(me.clone(), cb);
|
||||
Self::update(me, cb);
|
||||
});
|
||||
Box::into_raw(Box::new(boxed)) as *mut c_void
|
||||
};
|
||||
|
|
@ -70,7 +69,7 @@ impl Partitions {
|
|||
});
|
||||
}
|
||||
|
||||
fn update(me: Locked, cb: impl Fn() + Send + 'static) {
|
||||
fn update(me: &'static Locked, cb: impl Fn() + Send + 'static) {
|
||||
_ = tokio::task::spawn_blocking(move || {
|
||||
let result = Self::all_names().and_then(Self::all_partitions);
|
||||
if let Err(ref e) = result {
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@ pub struct Partition {
|
|||
pub src: OsString,
|
||||
pub dist: Option<PathBuf>,
|
||||
#[cfg(unix)]
|
||||
pub rdev: Option<libc::dev_t>,
|
||||
pub rdev: Option<u64>,
|
||||
pub label: Option<OsString>,
|
||||
pub fstype: Option<OsString>,
|
||||
pub capacity: u64,
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
use std::{ops::Deref, sync::Arc};
|
||||
use std::ops::Deref;
|
||||
|
||||
use parking_lot::RwLock;
|
||||
use yazi_shared::RoCell;
|
||||
|
|
@ -6,7 +6,7 @@ use yazi_shared::RoCell;
|
|||
use super::Partition;
|
||||
use crate::cha::Cha;
|
||||
|
||||
pub(super) type Locked = Arc<RwLock<Partitions>>;
|
||||
pub(super) type Locked = RwLock<Partitions>;
|
||||
|
||||
pub static PARTITIONS: RoCell<Locked> = RoCell::new();
|
||||
|
||||
|
|
@ -27,7 +27,7 @@ impl Deref for Partitions {
|
|||
|
||||
impl Partitions {
|
||||
#[cfg(unix)]
|
||||
pub fn by_dev(&self, dev: libc::dev_t) -> Option<&Partition> {
|
||||
pub fn by_dev(&self, dev: u64) -> Option<&Partition> {
|
||||
self.inner.iter().find(|p| p.rdev == Some(dev))
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,23 +1,29 @@
|
|||
use std::{ffi::OsString, io};
|
||||
use std::{borrow::Cow, ffi::OsStr, io};
|
||||
|
||||
use yazi_shared::url::UrlBuf;
|
||||
|
||||
use crate::provider::FileHolder;
|
||||
|
||||
pub enum DirEntry {
|
||||
Local(super::local::DirEntry),
|
||||
}
|
||||
|
||||
impl From<super::local::DirEntry> for DirEntry {
|
||||
fn from(value: super::local::DirEntry) -> Self { Self::Local(value) }
|
||||
}
|
||||
|
||||
impl DirEntry {
|
||||
#[must_use]
|
||||
pub fn url(&self) -> UrlBuf {
|
||||
match self {
|
||||
Self::Local(local) => local.url(),
|
||||
Self::Local(local) => local.path().into(),
|
||||
}
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub fn file_name(&self) -> OsString {
|
||||
pub fn name(&self) -> Cow<'_, OsStr> {
|
||||
match self {
|
||||
Self::Local(local) => local.file_name(),
|
||||
Self::Local(local) => local.name(),
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,24 +1,15 @@
|
|||
use std::ops::Deref;
|
||||
use std::{borrow::Cow, ffi::OsStr, io, path::PathBuf};
|
||||
|
||||
use yazi_shared::url::UrlBuf;
|
||||
use crate::provider::FileHolder;
|
||||
|
||||
pub struct DirEntry(tokio::fs::DirEntry);
|
||||
pub struct DirEntry(pub(super) tokio::fs::DirEntry);
|
||||
|
||||
impl Deref for DirEntry {
|
||||
type Target = tokio::fs::DirEntry;
|
||||
impl FileHolder for DirEntry {
|
||||
fn path(&self) -> PathBuf { self.0.path() }
|
||||
|
||||
fn deref(&self) -> &Self::Target { &self.0 }
|
||||
}
|
||||
|
||||
impl From<tokio::fs::DirEntry> for DirEntry {
|
||||
fn from(value: tokio::fs::DirEntry) -> Self { Self(value) }
|
||||
}
|
||||
|
||||
impl From<DirEntry> for crate::provider::DirEntry {
|
||||
fn from(value: DirEntry) -> Self { Self::Local(value) }
|
||||
}
|
||||
|
||||
impl DirEntry {
|
||||
#[must_use]
|
||||
pub fn url(&self) -> UrlBuf { self.0.path().into() }
|
||||
fn name(&self) -> Cow<'_, OsStr> { self.0.file_name().into() }
|
||||
|
||||
async fn metadata(&self) -> io::Result<std::fs::Metadata> { self.0.metadata().await }
|
||||
|
||||
async fn file_type(&self) -> io::Result<std::fs::FileType> { self.0.file_type().await }
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,14 +1,47 @@
|
|||
use std::ops::{Deref, DerefMut};
|
||||
use std::{io, path::Path};
|
||||
|
||||
use crate::provider::FileBuilder;
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct Gate(tokio::fs::OpenOptions);
|
||||
|
||||
impl Deref for Gate {
|
||||
type Target = tokio::fs::OpenOptions;
|
||||
impl FileBuilder for Gate {
|
||||
type File = tokio::fs::File;
|
||||
|
||||
fn deref(&self) -> &Self::Target { &self.0 }
|
||||
}
|
||||
fn append(&mut self, append: bool) -> &mut Self {
|
||||
self.0.append(append);
|
||||
self
|
||||
}
|
||||
|
||||
impl DerefMut for Gate {
|
||||
fn deref_mut(&mut self) -> &mut Self::Target { &mut self.0 }
|
||||
fn create(&mut self, create: bool) -> &mut Self {
|
||||
self.0.create(create);
|
||||
self
|
||||
}
|
||||
|
||||
fn create_new(&mut self, create_new: bool) -> &mut Self {
|
||||
self.0.create_new(create_new);
|
||||
self
|
||||
}
|
||||
|
||||
async fn open<P>(&self, path: P) -> io::Result<Self::File>
|
||||
where
|
||||
P: AsRef<Path>,
|
||||
{
|
||||
self.0.open(path).await
|
||||
}
|
||||
|
||||
fn read(&mut self, read: bool) -> &mut Self {
|
||||
self.0.read(read);
|
||||
self
|
||||
}
|
||||
|
||||
fn truncate(&mut self, truncate: bool) -> &mut Self {
|
||||
self.0.truncate(truncate);
|
||||
self
|
||||
}
|
||||
|
||||
fn write(&mut self, write: bool) -> &mut Self {
|
||||
self.0.write(write);
|
||||
self
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,12 +1,16 @@
|
|||
use std::{io, path::{Path, PathBuf}};
|
||||
|
||||
use crate::{cha::Cha, provider::local::{Gate, ReadDir, RwFile}};
|
||||
use crate::{cha::Cha, provider::Provider};
|
||||
|
||||
pub struct Local;
|
||||
|
||||
impl Local {
|
||||
impl Provider for Local {
|
||||
type File = tokio::fs::File;
|
||||
type Gate = super::Gate;
|
||||
type ReadDir = super::ReadDir;
|
||||
|
||||
#[inline]
|
||||
pub fn cache<P>(_: P) -> Option<PathBuf>
|
||||
fn cache<P>(_: P) -> Option<PathBuf>
|
||||
where
|
||||
P: AsRef<Path>,
|
||||
{
|
||||
|
|
@ -14,7 +18,7 @@ impl Local {
|
|||
}
|
||||
|
||||
#[inline]
|
||||
pub async fn canonicalize<P>(path: P) -> io::Result<PathBuf>
|
||||
async fn canonicalize<P>(path: P) -> io::Result<PathBuf>
|
||||
where
|
||||
P: AsRef<Path>,
|
||||
{
|
||||
|
|
@ -22,7 +26,7 @@ impl Local {
|
|||
}
|
||||
|
||||
#[inline]
|
||||
pub async fn copy<P, Q>(from: P, to: Q, cha: Cha) -> io::Result<u64>
|
||||
async fn copy<P, Q>(from: P, to: Q, cha: Cha) -> io::Result<u64>
|
||||
where
|
||||
P: AsRef<Path>,
|
||||
Q: AsRef<Path>,
|
||||
|
|
@ -32,6 +36,183 @@ impl Local {
|
|||
Self::copy_impl(from, to, cha).await
|
||||
}
|
||||
|
||||
#[inline]
|
||||
async fn create_dir<P>(path: P) -> io::Result<()>
|
||||
where
|
||||
P: AsRef<Path>,
|
||||
{
|
||||
tokio::fs::create_dir(path).await
|
||||
}
|
||||
|
||||
#[inline]
|
||||
async fn create_dir_all<P>(path: P) -> io::Result<()>
|
||||
where
|
||||
P: AsRef<Path>,
|
||||
{
|
||||
tokio::fs::create_dir_all(path).await
|
||||
}
|
||||
|
||||
#[inline]
|
||||
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]
|
||||
async fn metadata<P>(path: P) -> io::Result<std::fs::Metadata>
|
||||
where
|
||||
P: AsRef<Path>,
|
||||
{
|
||||
tokio::fs::metadata(path).await
|
||||
}
|
||||
|
||||
#[inline]
|
||||
async fn read_dir<P>(path: P) -> io::Result<Self::ReadDir>
|
||||
where
|
||||
P: AsRef<Path>,
|
||||
{
|
||||
tokio::fs::read_dir(path).await.map(super::ReadDir)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
async fn read_link<P>(path: P) -> io::Result<PathBuf>
|
||||
where
|
||||
P: AsRef<Path>,
|
||||
{
|
||||
tokio::fs::read_link(path).await
|
||||
}
|
||||
|
||||
#[inline]
|
||||
async fn remove_dir<P>(path: P) -> io::Result<()>
|
||||
where
|
||||
P: AsRef<Path>,
|
||||
{
|
||||
tokio::fs::remove_dir(path).await
|
||||
}
|
||||
|
||||
#[inline]
|
||||
async fn remove_dir_all<P>(path: P) -> io::Result<()>
|
||||
where
|
||||
P: AsRef<Path>,
|
||||
{
|
||||
tokio::fs::remove_dir_all(path).await
|
||||
}
|
||||
|
||||
#[inline]
|
||||
async fn remove_file<P>(path: P) -> io::Result<()>
|
||||
where
|
||||
P: AsRef<Path>,
|
||||
{
|
||||
tokio::fs::remove_file(path).await
|
||||
}
|
||||
|
||||
#[inline]
|
||||
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]
|
||||
async fn symlink<P, Q, F>(original: P, link: Q, _is_dir: F) -> io::Result<()>
|
||||
where
|
||||
P: AsRef<Path>,
|
||||
Q: AsRef<Path>,
|
||||
F: AsyncFnOnce() -> io::Result<bool>,
|
||||
{
|
||||
#[cfg(unix)]
|
||||
{
|
||||
tokio::fs::symlink(original, link).await
|
||||
}
|
||||
#[cfg(windows)]
|
||||
if _is_dir().await? {
|
||||
Self::symlink_dir(original, link).await
|
||||
} else {
|
||||
Self::symlink_file(original, link).await
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
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
|
||||
}
|
||||
#[cfg(windows)]
|
||||
{
|
||||
tokio::fs::symlink_dir(original, link).await
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
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
|
||||
}
|
||||
#[cfg(windows)]
|
||||
{
|
||||
tokio::fs::symlink_file(original, link).await
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
async fn symlink_metadata<P>(path: P) -> io::Result<std::fs::Metadata>
|
||||
where
|
||||
P: AsRef<Path>,
|
||||
{
|
||||
tokio::fs::symlink_metadata(path).await
|
||||
}
|
||||
|
||||
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]
|
||||
async fn write<P, C>(path: P, contents: C) -> io::Result<()>
|
||||
where
|
||||
P: AsRef<Path>,
|
||||
C: AsRef<[u8]>,
|
||||
{
|
||||
tokio::fs::write(path, contents).await
|
||||
}
|
||||
}
|
||||
|
||||
impl Local {
|
||||
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));
|
||||
|
|
@ -80,55 +261,6 @@ impl Local {
|
|||
}
|
||||
}
|
||||
|
||||
#[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<P>(path: P) -> io::Result<()>
|
||||
where
|
||||
P: AsRef<Path>,
|
||||
{
|
||||
tokio::fs::create_dir(path).await
|
||||
}
|
||||
|
||||
#[inline]
|
||||
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<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<P>(path: P) -> io::Result<std::fs::Metadata>
|
||||
where
|
||||
P: AsRef<Path>,
|
||||
{
|
||||
tokio::fs::metadata(path).await
|
||||
}
|
||||
|
||||
#[inline]
|
||||
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<P>(path: P) -> io::Result<Vec<u8>>
|
||||
where
|
||||
|
|
@ -137,22 +269,6 @@ impl Local {
|
|||
tokio::fs::read(path).await
|
||||
}
|
||||
|
||||
#[inline]
|
||||
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 async fn read_link<P>(path: P) -> io::Result<PathBuf>
|
||||
where
|
||||
P: AsRef<Path>,
|
||||
{
|
||||
tokio::fs::read_link(path).await
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub async fn read_to_string<P>(path: P) -> io::Result<String>
|
||||
where
|
||||
|
|
@ -160,130 +276,4 @@ impl Local {
|
|||
{
|
||||
tokio::fs::read_to_string(path).await
|
||||
}
|
||||
|
||||
#[inline]
|
||||
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<P>(path: P) -> io::Result<()>
|
||||
where
|
||||
P: AsRef<Path>,
|
||||
{
|
||||
tokio::fs::remove_dir_all(path).await
|
||||
}
|
||||
|
||||
#[inline]
|
||||
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<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<P, Q, F>(original: P, link: Q, _is_dir: F) -> io::Result<()>
|
||||
where
|
||||
P: AsRef<Path>,
|
||||
Q: AsRef<Path>,
|
||||
F: AsyncFnOnce() -> io::Result<bool>,
|
||||
{
|
||||
#[cfg(unix)]
|
||||
{
|
||||
tokio::fs::symlink(original, link).await
|
||||
}
|
||||
#[cfg(windows)]
|
||||
if _is_dir().await? {
|
||||
Self::symlink_dir(original, link).await
|
||||
} else {
|
||||
Self::symlink_file(original, link).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
|
||||
}
|
||||
#[cfg(windows)]
|
||||
{
|
||||
tokio::fs::symlink_dir(original, link).await
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
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
|
||||
}
|
||||
#[cfg(windows)]
|
||||
{
|
||||
tokio::fs::symlink_file(original, link).await
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub async fn symlink_metadata<P>(path: P) -> io::Result<std::fs::Metadata>
|
||||
where
|
||||
P: AsRef<Path>,
|
||||
{
|
||||
tokio::fs::symlink_metadata(path).await
|
||||
}
|
||||
|
||||
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<P, C>(path: P, contents: C) -> io::Result<()>
|
||||
where
|
||||
P: AsRef<Path>,
|
||||
C: AsRef<[u8]>,
|
||||
{
|
||||
tokio::fs::write(path, contents).await
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1 +1 @@
|
|||
yazi_macro::mod_flat!(calculator casefold dir_entry gate identical local read_dir rw_file);
|
||||
yazi_macro::mod_flat!(calculator casefold dir_entry gate identical local read_dir);
|
||||
|
|
|
|||
|
|
@ -1,19 +1,13 @@
|
|||
use std::io;
|
||||
|
||||
use super::DirEntry;
|
||||
use crate::provider::DirReader;
|
||||
|
||||
pub struct ReadDir(tokio::fs::ReadDir);
|
||||
pub struct ReadDir(pub(super) tokio::fs::ReadDir);
|
||||
|
||||
impl From<tokio::fs::ReadDir> for ReadDir {
|
||||
fn from(value: tokio::fs::ReadDir) -> Self { Self(value) }
|
||||
}
|
||||
impl DirReader for ReadDir {
|
||||
type Entry<'a> = super::DirEntry;
|
||||
|
||||
impl From<ReadDir> for crate::provider::ReadDir {
|
||||
fn from(value: ReadDir) -> Self { Self::Local(value) }
|
||||
}
|
||||
|
||||
impl ReadDir {
|
||||
pub async fn next_entry(&mut self) -> io::Result<Option<DirEntry>> {
|
||||
self.0.next_entry().await.map(|entry| entry.map(Into::into))
|
||||
async fn next(&mut self) -> io::Result<Option<Self::Entry<'_>>> {
|
||||
self.0.next_entry().await.map(|entry| entry.map(super::DirEntry))
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,78 +0,0 @@
|
|||
use std::{pin::Pin, task::Poll};
|
||||
|
||||
use tokio::io::{AsyncRead, AsyncSeek, AsyncWrite};
|
||||
|
||||
pub struct RwFile(tokio::fs::File);
|
||||
|
||||
impl From<tokio::fs::File> for RwFile {
|
||||
fn from(value: tokio::fs::File) -> Self { Self(value) }
|
||||
}
|
||||
|
||||
impl From<RwFile> for crate::provider::RwFile {
|
||||
fn from(value: RwFile) -> Self { Self::Local(value) }
|
||||
}
|
||||
|
||||
impl AsyncRead for RwFile {
|
||||
#[inline]
|
||||
fn poll_read(
|
||||
mut self: Pin<&mut Self>,
|
||||
cx: &mut std::task::Context<'_>,
|
||||
buf: &mut tokio::io::ReadBuf<'_>,
|
||||
) -> Poll<std::io::Result<()>> {
|
||||
Pin::new(&mut self.0).poll_read(cx, buf)
|
||||
}
|
||||
}
|
||||
|
||||
impl AsyncSeek for RwFile {
|
||||
#[inline]
|
||||
fn start_seek(mut self: Pin<&mut Self>, position: std::io::SeekFrom) -> std::io::Result<()> {
|
||||
Pin::new(&mut self.0).start_seek(position)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn poll_complete(
|
||||
mut self: Pin<&mut Self>,
|
||||
cx: &mut std::task::Context<'_>,
|
||||
) -> Poll<std::io::Result<u64>> {
|
||||
Pin::new(&mut self.0).poll_complete(cx)
|
||||
}
|
||||
}
|
||||
|
||||
impl AsyncWrite for RwFile {
|
||||
#[inline]
|
||||
fn poll_write(
|
||||
mut self: Pin<&mut Self>,
|
||||
cx: &mut std::task::Context<'_>,
|
||||
buf: &[u8],
|
||||
) -> Poll<Result<usize, std::io::Error>> {
|
||||
Pin::new(&mut self.0).poll_write(cx, buf)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn poll_flush(
|
||||
mut self: Pin<&mut Self>,
|
||||
cx: &mut std::task::Context<'_>,
|
||||
) -> Poll<Result<(), std::io::Error>> {
|
||||
Pin::new(&mut self.0).poll_flush(cx)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn poll_shutdown(
|
||||
mut self: Pin<&mut Self>,
|
||||
cx: &mut std::task::Context<'_>,
|
||||
) -> Poll<Result<(), std::io::Error>> {
|
||||
Pin::new(&mut self.0).poll_shutdown(cx)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn poll_write_vectored(
|
||||
mut self: Pin<&mut Self>,
|
||||
cx: &mut std::task::Context<'_>,
|
||||
bufs: &[std::io::IoSlice<'_>],
|
||||
) -> Poll<Result<usize, std::io::Error>> {
|
||||
Pin::new(&mut self.0).poll_write_vectored(cx, bufs)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn is_write_vectored(&self) -> bool { self.0.is_write_vectored() }
|
||||
}
|
||||
|
|
@ -1,3 +1,5 @@
|
|||
yazi_macro::mod_pub!(local sftp);
|
||||
|
||||
yazi_macro::mod_flat!(calculator dir_entry provider read_dir rw_file);
|
||||
yazi_macro::mod_flat!(calculator dir_entry provider read_dir rw_file traits);
|
||||
|
||||
pub fn init() { sftp::init(); }
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@ use std::{io, path::{Path, PathBuf}};
|
|||
|
||||
use yazi_shared::url::{Url, UrlBuf};
|
||||
|
||||
use crate::{cha::Cha, provider::{ReadDir, RwFile, local::{self, Local}}};
|
||||
use crate::{cha::Cha, provider::{Provider, ReadDir, RwFile, local::{self, Local}}};
|
||||
|
||||
#[inline]
|
||||
pub fn cache<'a, U>(url: U) -> Option<PathBuf>
|
||||
|
|
@ -145,18 +145,6 @@ where
|
|||
identical(a, b).await.unwrap_or(false)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
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 {
|
||||
Err(io::Error::new(io::ErrorKind::Unsupported, "Unsupported filesystem"))
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub async fn read_dir<'a, U>(url: U) -> io::Result<ReadDir>
|
||||
where
|
||||
|
|
|
|||
|
|
@ -1,15 +1,20 @@
|
|||
use std::io;
|
||||
|
||||
use super::DirEntry;
|
||||
use crate::provider::DirReader;
|
||||
|
||||
pub enum ReadDir {
|
||||
Local(super::local::ReadDir),
|
||||
}
|
||||
|
||||
impl From<super::local::ReadDir> for ReadDir {
|
||||
fn from(value: super::local::ReadDir) -> Self { Self::Local(value) }
|
||||
}
|
||||
|
||||
impl ReadDir {
|
||||
pub async fn next_entry(&mut self) -> io::Result<Option<DirEntry>> {
|
||||
match self {
|
||||
Self::Local(local) => local.next_entry().await.map(|entry| entry.map(Into::into)),
|
||||
Self::Local(local) => local.next().await.map(|entry| entry.map(Into::into)),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,7 +3,16 @@ use std::pin::Pin;
|
|||
use tokio::io::{AsyncRead, AsyncWrite};
|
||||
|
||||
pub enum RwFile {
|
||||
Local(super::local::RwFile),
|
||||
Tokio(tokio::fs::File),
|
||||
SFTP(Box<yazi_sftp::fs::File>),
|
||||
}
|
||||
|
||||
impl From<tokio::fs::File> for RwFile {
|
||||
fn from(f: tokio::fs::File) -> Self { Self::Tokio(f) }
|
||||
}
|
||||
|
||||
impl From<yazi_sftp::fs::File> for RwFile {
|
||||
fn from(f: yazi_sftp::fs::File) -> Self { Self::SFTP(Box::new(f)) }
|
||||
}
|
||||
|
||||
impl AsyncRead for RwFile {
|
||||
|
|
@ -14,7 +23,8 @@ impl AsyncRead for RwFile {
|
|||
buf: &mut tokio::io::ReadBuf<'_>,
|
||||
) -> std::task::Poll<std::io::Result<()>> {
|
||||
match &mut *self {
|
||||
Self::Local(f) => Pin::new(f).poll_read(cx, buf),
|
||||
Self::Tokio(f) => Pin::new(f).poll_read(cx, buf),
|
||||
Self::SFTP(f) => Pin::new(f).poll_read(cx, buf),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -27,7 +37,8 @@ impl AsyncWrite for RwFile {
|
|||
buf: &[u8],
|
||||
) -> std::task::Poll<Result<usize, std::io::Error>> {
|
||||
match &mut *self {
|
||||
Self::Local(f) => Pin::new(f).poll_write(cx, buf),
|
||||
Self::Tokio(f) => Pin::new(f).poll_write(cx, buf),
|
||||
Self::SFTP(f) => Pin::new(f).poll_write(cx, buf),
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -37,7 +48,8 @@ impl AsyncWrite for RwFile {
|
|||
cx: &mut std::task::Context<'_>,
|
||||
) -> std::task::Poll<Result<(), std::io::Error>> {
|
||||
match &mut *self {
|
||||
Self::Local(f) => Pin::new(f).poll_flush(cx),
|
||||
Self::Tokio(f) => Pin::new(f).poll_flush(cx),
|
||||
Self::SFTP(f) => Pin::new(f).poll_flush(cx),
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -47,7 +59,8 @@ impl AsyncWrite for RwFile {
|
|||
cx: &mut std::task::Context<'_>,
|
||||
) -> std::task::Poll<Result<(), std::io::Error>> {
|
||||
match &mut *self {
|
||||
Self::Local(f) => Pin::new(f).poll_shutdown(cx),
|
||||
Self::Tokio(f) => Pin::new(f).poll_shutdown(cx),
|
||||
Self::SFTP(f) => Pin::new(f).poll_shutdown(cx),
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -58,14 +71,16 @@ impl AsyncWrite for RwFile {
|
|||
bufs: &[std::io::IoSlice<'_>],
|
||||
) -> std::task::Poll<Result<usize, std::io::Error>> {
|
||||
match &mut *self {
|
||||
Self::Local(f) => Pin::new(f).poll_write_vectored(cx, bufs),
|
||||
Self::Tokio(f) => Pin::new(f).poll_write_vectored(cx, bufs),
|
||||
Self::SFTP(f) => Pin::new(f).poll_write_vectored(cx, bufs),
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn is_write_vectored(&self) -> bool {
|
||||
match self {
|
||||
Self::Local(f) => f.is_write_vectored(),
|
||||
Self::Tokio(f) => f.is_write_vectored(),
|
||||
Self::SFTP(f) => f.is_write_vectored(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
75
yazi-fs/src/provider/sftp/gate.rs
Normal file
75
yazi-fs/src/provider/sftp/gate.rs
Normal file
|
|
@ -0,0 +1,75 @@
|
|||
use std::{io, path::Path};
|
||||
|
||||
use yazi_sftp::fs::{Attrs, Flags};
|
||||
|
||||
use crate::provider::FileBuilder;
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct Gate {
|
||||
append: bool,
|
||||
create: bool,
|
||||
create_new: bool,
|
||||
read: bool,
|
||||
truncate: bool,
|
||||
write: bool,
|
||||
}
|
||||
|
||||
impl FileBuilder for Gate {
|
||||
type File = yazi_sftp::fs::File;
|
||||
|
||||
fn append(&mut self, append: bool) -> &mut Self {
|
||||
self.append = append;
|
||||
self
|
||||
}
|
||||
|
||||
fn create(&mut self, create: bool) -> &mut Self {
|
||||
self.create = create;
|
||||
self
|
||||
}
|
||||
|
||||
fn create_new(&mut self, create_new: bool) -> &mut Self {
|
||||
self.create_new = create_new;
|
||||
self
|
||||
}
|
||||
|
||||
async fn open<P>(&self, path: P) -> io::Result<Self::File>
|
||||
where
|
||||
P: AsRef<Path>,
|
||||
{
|
||||
let mut flags = Flags::empty();
|
||||
if self.append {
|
||||
flags |= Flags::APPEND;
|
||||
}
|
||||
if self.create {
|
||||
flags |= Flags::CREATE;
|
||||
}
|
||||
if self.create_new {
|
||||
flags |= Flags::CREATE | Flags::EXCLUDE;
|
||||
}
|
||||
if self.read {
|
||||
flags |= Flags::READ;
|
||||
}
|
||||
if self.truncate {
|
||||
flags |= Flags::TRUNCATE;
|
||||
}
|
||||
if self.write {
|
||||
flags |= Flags::WRITE;
|
||||
}
|
||||
Ok(super::Sftp::op().await?.open(&path, flags, Attrs::default()).await?)
|
||||
}
|
||||
|
||||
fn read(&mut self, read: bool) -> &mut Self {
|
||||
self.read = read;
|
||||
self
|
||||
}
|
||||
|
||||
fn truncate(&mut self, truncate: bool) -> &mut Self {
|
||||
self.truncate = truncate;
|
||||
self
|
||||
}
|
||||
|
||||
fn write(&mut self, write: bool) -> &mut Self {
|
||||
self.write = write;
|
||||
self
|
||||
}
|
||||
}
|
||||
|
|
@ -1 +1,6 @@
|
|||
yazi_macro::mod_flat!(sftp);
|
||||
yazi_macro::mod_flat!(gate read_dir sftp);
|
||||
|
||||
pub(super) static CONN: yazi_shared::RoCell<deadpool::managed::Pool<Sftp>> =
|
||||
yazi_shared::RoCell::new();
|
||||
|
||||
pub(super) fn init() { CONN.init(deadpool::managed::Pool::builder(Sftp).build().unwrap()); }
|
||||
|
|
|
|||
26
yazi-fs/src/provider/sftp/read_dir.rs
Normal file
26
yazi-fs/src/provider/sftp/read_dir.rs
Normal file
|
|
@ -0,0 +1,26 @@
|
|||
use std::{borrow::Cow, ffi::OsStr, io, path::PathBuf};
|
||||
|
||||
use crate::provider::{DirReader, FileHolder};
|
||||
|
||||
pub struct ReadDir(pub(super) yazi_sftp::fs::ReadDir);
|
||||
|
||||
impl DirReader for ReadDir {
|
||||
type Entry<'a> = DirEntry<'a>;
|
||||
|
||||
async fn next(&mut self) -> io::Result<Option<Self::Entry<'_>>> {
|
||||
Ok(self.0.next().await?.map(DirEntry))
|
||||
}
|
||||
}
|
||||
|
||||
// --- Entry
|
||||
pub struct DirEntry<'a>(yazi_sftp::fs::DirEntry<'a>);
|
||||
|
||||
impl FileHolder for DirEntry<'_> {
|
||||
fn path(&self) -> PathBuf { self.0.path() }
|
||||
|
||||
fn name(&self) -> Cow<'_, OsStr> { self.0.name() }
|
||||
|
||||
async fn metadata(&self) -> io::Result<std::fs::Metadata> { todo!() }
|
||||
|
||||
async fn file_type(&self) -> io::Result<std::fs::FileType> { todo!() }
|
||||
}
|
||||
|
|
@ -1,176 +1,169 @@
|
|||
use std::{io, path::{Path, PathBuf}};
|
||||
use std::{io, path::{Path, PathBuf}, time::UNIX_EPOCH};
|
||||
|
||||
use crate::cha::Cha;
|
||||
use yazi_sftp::fs::{Attrs, Flags};
|
||||
|
||||
use crate::{cha::Cha, provider::Provider};
|
||||
|
||||
pub struct Sftp;
|
||||
|
||||
impl Sftp {
|
||||
pub fn cache<P>(_: P) -> Option<PathBuf>
|
||||
impl Provider for Sftp {
|
||||
type File = yazi_sftp::fs::File;
|
||||
type Gate = super::Gate;
|
||||
type ReadDir = super::ReadDir;
|
||||
|
||||
fn cache<P>(_: P) -> Option<PathBuf>
|
||||
where
|
||||
P: AsRef<Path>,
|
||||
{
|
||||
todo!()
|
||||
}
|
||||
|
||||
pub async fn canonicalize<P>(path: P) -> io::Result<PathBuf>
|
||||
async fn canonicalize<P>(path: P) -> io::Result<PathBuf>
|
||||
where
|
||||
P: AsRef<Path>,
|
||||
{
|
||||
todo!()
|
||||
Ok(Self::op().await?.realpath(&path).await?)
|
||||
}
|
||||
|
||||
pub async fn copy<P, Q>(from: P, to: Q, cha: Cha) -> io::Result<u64>
|
||||
async fn copy<P, Q>(from: P, to: Q, cha: Cha) -> io::Result<u64>
|
||||
where
|
||||
P: AsRef<Path>,
|
||||
Q: AsRef<Path>,
|
||||
{
|
||||
todo!()
|
||||
// FIXME: pull this out to a From<Cha> for Attrs impl
|
||||
let attrs = Attrs {
|
||||
size: Some(cha.len),
|
||||
uid: Some(cha.uid),
|
||||
gid: Some(cha.gid),
|
||||
perm: Some(cha.mode.bits() as _),
|
||||
atime: cha
|
||||
.atime
|
||||
.and_then(|t| t.duration_since(UNIX_EPOCH).ok())
|
||||
.map(|d| d.as_secs() as u32),
|
||||
mtime: cha
|
||||
.mtime
|
||||
.and_then(|t| t.duration_since(UNIX_EPOCH).ok())
|
||||
.map(|d| d.as_secs() as u32),
|
||||
extended: Default::default(),
|
||||
};
|
||||
|
||||
let op = Self::op().await?;
|
||||
|
||||
let mut from = op.open(&from, Flags::READ, Attrs::default()).await?;
|
||||
let mut to = op.open(&to, Flags::WRITE | Flags::CREATE | Flags::TRUNCATE, attrs).await?;
|
||||
|
||||
tokio::io::copy(&mut from, &mut to).await
|
||||
}
|
||||
|
||||
pub async fn create<P>(path: P) -> io::Result<()>
|
||||
async fn create_dir<P>(path: P) -> io::Result<()>
|
||||
where
|
||||
P: AsRef<Path>,
|
||||
{
|
||||
todo!()
|
||||
Ok(Self::op().await?.mkdir(&path, Attrs::default()).await?)
|
||||
}
|
||||
|
||||
pub async fn create_dir<P>(path: P) -> io::Result<()>
|
||||
where
|
||||
P: AsRef<Path>,
|
||||
{
|
||||
todo!()
|
||||
}
|
||||
|
||||
pub async fn create_dir_all<P>(path: P) -> io::Result<()>
|
||||
where
|
||||
P: AsRef<Path>,
|
||||
{
|
||||
todo!()
|
||||
}
|
||||
|
||||
pub async fn hard_link<P, Q>(original: P, link: Q) -> io::Result<()>
|
||||
async fn hard_link<P, Q>(original: P, link: Q) -> io::Result<()>
|
||||
where
|
||||
P: AsRef<Path>,
|
||||
Q: AsRef<Path>,
|
||||
{
|
||||
todo!()
|
||||
Ok(Self::op().await?.hardlink(&original, &link).await?)
|
||||
}
|
||||
|
||||
pub async fn metadata<P>(path: P) -> io::Result<std::fs::Metadata>
|
||||
async fn metadata<P>(path: P) -> io::Result<std::fs::Metadata>
|
||||
where
|
||||
P: AsRef<Path>,
|
||||
{
|
||||
todo!()
|
||||
}
|
||||
|
||||
pub async fn open<P>(path: P) -> io::Result<()>
|
||||
async fn read_dir<P>(path: P) -> io::Result<Self::ReadDir>
|
||||
where
|
||||
P: AsRef<Path>,
|
||||
{
|
||||
todo!()
|
||||
Ok(super::ReadDir(Self::op().await?.read_dir(&path).await?))
|
||||
}
|
||||
|
||||
pub async fn read<P>(path: P) -> io::Result<Vec<u8>>
|
||||
async fn read_link<P>(path: P) -> io::Result<PathBuf>
|
||||
where
|
||||
P: AsRef<Path>,
|
||||
{
|
||||
todo!()
|
||||
Ok(Self::op().await?.readlink(&path).await?)
|
||||
}
|
||||
|
||||
pub async fn read_dir<P>(path: P) -> io::Result<()>
|
||||
async fn remove_dir<P>(path: P) -> io::Result<()>
|
||||
where
|
||||
P: AsRef<Path>,
|
||||
{
|
||||
todo!()
|
||||
Ok(Self::op().await?.rmdir(&path).await?)
|
||||
}
|
||||
|
||||
pub async fn read_link<P>(path: P) -> io::Result<PathBuf>
|
||||
async fn remove_file<P>(path: P) -> io::Result<()>
|
||||
where
|
||||
P: AsRef<Path>,
|
||||
{
|
||||
todo!()
|
||||
Ok(Self::op().await?.remove(&path).await?)
|
||||
}
|
||||
|
||||
pub async fn read_to_string<P>(path: P) -> io::Result<String>
|
||||
where
|
||||
P: AsRef<Path>,
|
||||
{
|
||||
todo!()
|
||||
}
|
||||
|
||||
pub async fn remove_dir<P>(path: P) -> io::Result<()>
|
||||
where
|
||||
P: AsRef<Path>,
|
||||
{
|
||||
todo!()
|
||||
}
|
||||
|
||||
pub async fn remove_dir_all<P>(path: P) -> io::Result<()>
|
||||
where
|
||||
P: AsRef<Path>,
|
||||
{
|
||||
todo!()
|
||||
}
|
||||
|
||||
pub async fn remove_file<P>(path: P) -> io::Result<()>
|
||||
where
|
||||
P: AsRef<Path>,
|
||||
{
|
||||
todo!()
|
||||
}
|
||||
|
||||
pub async fn rename<P, Q>(from: P, to: Q) -> io::Result<()>
|
||||
async fn rename<P, Q>(from: P, to: Q) -> io::Result<()>
|
||||
where
|
||||
P: AsRef<Path>,
|
||||
Q: AsRef<Path>,
|
||||
{
|
||||
todo!()
|
||||
Ok(Self::op().await?.rename(&from, &to).await?)
|
||||
}
|
||||
|
||||
pub async fn symlink<P, Q, F>(original: P, link: Q, _is_dir: F) -> io::Result<()>
|
||||
async fn symlink<P, Q, F>(original: P, link: Q, _is_dir: F) -> io::Result<()>
|
||||
where
|
||||
P: AsRef<Path>,
|
||||
Q: AsRef<Path>,
|
||||
F: AsyncFnOnce() -> io::Result<bool>,
|
||||
{
|
||||
todo!()
|
||||
Ok(Self::op().await?.symlink(&original, &link).await?)
|
||||
}
|
||||
|
||||
pub async fn symlink_dir<P, Q>(original: P, link: Q) -> io::Result<()>
|
||||
where
|
||||
P: AsRef<Path>,
|
||||
Q: AsRef<Path>,
|
||||
{
|
||||
todo!()
|
||||
}
|
||||
|
||||
pub async fn symlink_file<P, Q>(original: P, link: Q) -> io::Result<()>
|
||||
where
|
||||
P: AsRef<Path>,
|
||||
Q: AsRef<Path>,
|
||||
{
|
||||
todo!()
|
||||
}
|
||||
|
||||
pub async fn symlink_metadata<P>(path: P) -> io::Result<std::fs::Metadata>
|
||||
async fn symlink_metadata<P>(path: P) -> io::Result<std::fs::Metadata>
|
||||
where
|
||||
P: AsRef<Path>,
|
||||
{
|
||||
todo!()
|
||||
}
|
||||
|
||||
pub async fn trash<P>(path: P) -> io::Result<()>
|
||||
async fn trash<P>(_path: P) -> io::Result<()>
|
||||
where
|
||||
P: AsRef<Path>,
|
||||
{
|
||||
todo!()
|
||||
}
|
||||
|
||||
pub async fn write<P, C>(path: P, contents: C) -> io::Result<()>
|
||||
where
|
||||
P: AsRef<Path>,
|
||||
C: AsRef<[u8]>,
|
||||
{
|
||||
todo!()
|
||||
Err(io::Error::new(io::ErrorKind::Unsupported, "Trash not supported"))
|
||||
}
|
||||
}
|
||||
|
||||
impl Sftp {
|
||||
pub(super) async fn op() -> io::Result<deadpool::managed::Object<Sftp>> {
|
||||
use deadpool::managed::PoolError;
|
||||
|
||||
super::CONN.get().await.map_err(|e| match e {
|
||||
PoolError::Timeout(_) => io::Error::new(io::ErrorKind::TimedOut, e.to_string()),
|
||||
PoolError::Backend(e) => e,
|
||||
PoolError::Closed | PoolError::NoRuntimeSpecified | PoolError::PostCreateHook(_) => {
|
||||
io::Error::other(e.to_string())
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl deadpool::managed::Manager for Sftp {
|
||||
type Error = io::Error;
|
||||
type Type = yazi_sftp::Operator;
|
||||
|
||||
async fn create(&self) -> Result<Self::Type, Self::Error> { todo!() }
|
||||
|
||||
async fn recycle(
|
||||
&self,
|
||||
obj: &mut Self::Type,
|
||||
metrics: &deadpool::managed::Metrics,
|
||||
) -> deadpool::managed::RecycleResult<Self::Error> {
|
||||
todo!()
|
||||
}
|
||||
|
||||
fn detach(&self, _obj: &mut Self::Type) { todo!() }
|
||||
}
|
||||
|
|
|
|||
214
yazi-fs/src/provider/traits.rs
Normal file
214
yazi-fs/src/provider/traits.rs
Normal file
|
|
@ -0,0 +1,214 @@
|
|||
use std::{borrow::Cow, ffi::OsStr, io, path::{Path, PathBuf}};
|
||||
|
||||
use tokio::io::{AsyncRead, AsyncWrite, AsyncWriteExt};
|
||||
use yazi_macro::ok_or_not_found;
|
||||
|
||||
use crate::cha::Cha;
|
||||
|
||||
pub trait Provider {
|
||||
type File: AsyncRead + AsyncWrite + Unpin;
|
||||
type Gate: FileBuilder<File = Self::File>;
|
||||
type ReadDir: DirReader;
|
||||
|
||||
fn cache<P>(_: P) -> Option<PathBuf>
|
||||
where
|
||||
P: AsRef<Path>;
|
||||
|
||||
fn canonicalize<P>(path: P) -> impl Future<Output = io::Result<PathBuf>>
|
||||
where
|
||||
P: AsRef<Path>;
|
||||
|
||||
fn copy<P, Q>(from: P, to: Q, cha: Cha) -> impl Future<Output = io::Result<u64>>
|
||||
where
|
||||
P: AsRef<Path>,
|
||||
Q: AsRef<Path>;
|
||||
|
||||
fn create<P>(path: P) -> impl Future<Output = io::Result<Self::File>>
|
||||
where
|
||||
P: AsRef<Path>,
|
||||
{
|
||||
async move { Self::Gate::default().write(true).create(true).truncate(true).open(path).await }
|
||||
}
|
||||
|
||||
fn create_dir<P>(path: P) -> impl Future<Output = io::Result<()>>
|
||||
where
|
||||
P: AsRef<Path>;
|
||||
|
||||
fn create_dir_all<P>(path: P) -> impl Future<Output = io::Result<()>>
|
||||
where
|
||||
P: AsRef<Path>,
|
||||
{
|
||||
async move {
|
||||
let path = path.as_ref();
|
||||
if path == Path::new("") {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
match Self::create_dir(path).await {
|
||||
Ok(()) => return Ok(()),
|
||||
Err(e) if e.kind() == io::ErrorKind::NotFound => {}
|
||||
Err(_) if Self::metadata(path).await.is_ok_and(|m| m.is_dir()) => return Ok(()),
|
||||
Err(e) => return Err(e),
|
||||
}
|
||||
match path.parent() {
|
||||
Some(p) => Self::create_dir_all(p).await?,
|
||||
None => return Err(io::Error::other("failed to create whole tree")),
|
||||
}
|
||||
match Self::create_dir(path).await {
|
||||
Ok(()) => Ok(()),
|
||||
Err(_) if Self::metadata(path).await.is_ok_and(|m| m.is_dir()) => Ok(()),
|
||||
Err(e) => Err(e),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn hard_link<P, Q>(original: P, link: Q) -> impl Future<Output = io::Result<()>>
|
||||
where
|
||||
P: AsRef<Path>,
|
||||
Q: AsRef<Path>;
|
||||
|
||||
fn metadata<P>(path: P) -> impl Future<Output = io::Result<std::fs::Metadata>>
|
||||
where
|
||||
P: AsRef<Path>;
|
||||
|
||||
fn open<P>(path: P) -> impl Future<Output = io::Result<Self::File>>
|
||||
where
|
||||
P: AsRef<Path>,
|
||||
{
|
||||
async move { Self::Gate::default().read(true).open(path).await }
|
||||
}
|
||||
|
||||
fn read_dir<P>(path: P) -> impl Future<Output = io::Result<Self::ReadDir>>
|
||||
where
|
||||
P: AsRef<Path>;
|
||||
|
||||
fn read_link<P>(path: P) -> impl Future<Output = io::Result<PathBuf>>
|
||||
where
|
||||
P: AsRef<Path>;
|
||||
|
||||
fn remove_dir<P>(path: P) -> impl Future<Output = io::Result<()>>
|
||||
where
|
||||
P: AsRef<Path>;
|
||||
|
||||
fn remove_dir_all<P>(path: P) -> impl Future<Output = io::Result<()>>
|
||||
where
|
||||
P: AsRef<Path>,
|
||||
{
|
||||
async fn remove_dir_all_impl<S>(path: &Path) -> io::Result<()>
|
||||
where
|
||||
S: Provider + ?Sized,
|
||||
{
|
||||
let mut it = ok_or_not_found!(S::read_dir(path).await, return Ok(()));
|
||||
while let Some(child) = it.next().await? {
|
||||
let ft = ok_or_not_found!(child.file_type().await, continue);
|
||||
let result = if ft.is_dir() {
|
||||
remove_dir_all_impl::<S>(&child.path()).await
|
||||
} else {
|
||||
S::remove_file(&child.path()).await
|
||||
};
|
||||
|
||||
() = ok_or_not_found!(result);
|
||||
}
|
||||
|
||||
Ok(ok_or_not_found!(S::remove_dir(path).await))
|
||||
}
|
||||
|
||||
async move {
|
||||
let path = path.as_ref();
|
||||
let ft = ok_or_not_found!(Self::symlink_metadata(path).await, return Ok(()));
|
||||
if ft.is_symlink() {
|
||||
Self::remove_file(path).await
|
||||
} else {
|
||||
remove_dir_all_impl::<Self>(path).await
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn remove_file<P>(path: P) -> impl Future<Output = io::Result<()>>
|
||||
where
|
||||
P: AsRef<Path>;
|
||||
|
||||
fn rename<P, Q>(from: P, to: Q) -> impl Future<Output = io::Result<()>>
|
||||
where
|
||||
P: AsRef<Path>,
|
||||
Q: AsRef<Path>;
|
||||
|
||||
fn symlink<P, Q, F>(original: P, link: Q, _is_dir: F) -> impl Future<Output = io::Result<()>>
|
||||
where
|
||||
P: AsRef<Path>,
|
||||
Q: AsRef<Path>,
|
||||
F: AsyncFnOnce() -> io::Result<bool>;
|
||||
|
||||
fn symlink_dir<P, Q>(original: P, link: Q) -> impl Future<Output = io::Result<()>>
|
||||
where
|
||||
P: AsRef<Path>,
|
||||
Q: AsRef<Path>,
|
||||
{
|
||||
Self::symlink(original, link, async || Ok(true))
|
||||
}
|
||||
|
||||
fn symlink_file<P, Q>(original: P, link: Q) -> impl Future<Output = io::Result<()>>
|
||||
where
|
||||
P: AsRef<Path>,
|
||||
Q: AsRef<Path>,
|
||||
{
|
||||
Self::symlink(original, link, async || Ok(false))
|
||||
}
|
||||
|
||||
fn symlink_metadata<P>(path: P) -> impl Future<Output = io::Result<std::fs::Metadata>>
|
||||
where
|
||||
P: AsRef<Path>;
|
||||
|
||||
fn trash<P>(path: P) -> impl Future<Output = io::Result<()>>
|
||||
where
|
||||
P: AsRef<Path>;
|
||||
|
||||
fn write<P, C>(path: P, contents: C) -> impl Future<Output = io::Result<()>>
|
||||
where
|
||||
P: AsRef<Path>,
|
||||
C: AsRef<[u8]>,
|
||||
{
|
||||
async move { Self::create(path).await?.write_all(contents.as_ref()).await }
|
||||
}
|
||||
}
|
||||
|
||||
// --- DirReader
|
||||
pub trait DirReader {
|
||||
type Entry<'a>: FileHolder
|
||||
where
|
||||
Self: 'a;
|
||||
|
||||
fn next(&mut self) -> impl Future<Output = io::Result<Option<Self::Entry<'_>>>>;
|
||||
}
|
||||
|
||||
// --- FileHolder
|
||||
pub trait FileHolder {
|
||||
fn path(&self) -> PathBuf;
|
||||
|
||||
fn name(&self) -> Cow<'_, OsStr>;
|
||||
|
||||
fn metadata(&self) -> impl Future<Output = io::Result<std::fs::Metadata>>;
|
||||
|
||||
fn file_type(&self) -> impl Future<Output = io::Result<std::fs::FileType>>;
|
||||
}
|
||||
|
||||
// --- FileOpener
|
||||
pub trait FileBuilder: Default {
|
||||
type File: AsyncRead + AsyncWrite + Unpin;
|
||||
|
||||
fn append(&mut self, append: bool) -> &mut Self;
|
||||
|
||||
fn create(&mut self, create: bool) -> &mut Self;
|
||||
|
||||
fn create_new(&mut self, create_new: bool) -> &mut Self;
|
||||
|
||||
fn open<P>(&self, path: P) -> impl Future<Output = io::Result<Self::File>>
|
||||
where
|
||||
P: AsRef<Path>;
|
||||
|
||||
fn read(&mut self, read: bool) -> &mut Self;
|
||||
|
||||
fn truncate(&mut self, truncate: bool) -> &mut Self;
|
||||
|
||||
fn write(&mut self, write: bool) -> &mut Self;
|
||||
}
|
||||
13
yazi-macro/src/fs.rs
Normal file
13
yazi-macro/src/fs.rs
Normal file
|
|
@ -0,0 +1,13 @@
|
|||
#[macro_export]
|
||||
macro_rules! ok_or_not_found {
|
||||
($result:expr, $not_found:expr) => {
|
||||
match $result {
|
||||
Ok(v) => v,
|
||||
Err(e) if e.kind() == std::io::ErrorKind::NotFound => $not_found,
|
||||
Err(e) => Err(e)?,
|
||||
}
|
||||
};
|
||||
($result:expr) => {
|
||||
ok_or_not_found!($result, ())
|
||||
};
|
||||
}
|
||||
|
|
@ -3,6 +3,7 @@ mod asset;
|
|||
mod context;
|
||||
mod event;
|
||||
mod fmt;
|
||||
mod fs;
|
||||
mod log;
|
||||
mod module;
|
||||
mod platform;
|
||||
|
|
|
|||
|
|
@ -57,9 +57,9 @@ end
|
|||
function Linemode:permissions() return self._file.cha:perm() or "" end
|
||||
|
||||
function Linemode:owner()
|
||||
local user = self._file.cha.uid and ya.user_name(self._file.cha.uid) or self._file.cha.uid
|
||||
local group = self._file.cha.gid and ya.group_name(self._file.cha.gid) or self._file.cha.gid
|
||||
return string.format("%s:%s", user or "-", group or "-")
|
||||
local user = ya.user_name and ya.user_name(self._file.cha.uid) or self._file.cha.uid
|
||||
local group = ya.group_name and ya.group_name(self._file.cha.gid) or self._file.cha.gid
|
||||
return string.format("%s:%s", user, group)
|
||||
end
|
||||
|
||||
function Linemode:redraw()
|
||||
|
|
|
|||
4
yazi-plugin/src/external/highlighter.rs
vendored
4
yazi-plugin/src/external/highlighter.rs
vendored
|
|
@ -5,7 +5,7 @@ use ratatui::{layout::Size, text::{Line, Span, Text}};
|
|||
use syntect::{LoadingError, dumps, easy::HighlightLines, highlighting::{self, Theme, ThemeSet}, parsing::{SyntaxReference, SyntaxSet}};
|
||||
use tokio::io::{AsyncBufReadExt, AsyncSeekExt, BufReader};
|
||||
use yazi_config::{THEME, YAZI, preview::PreviewWrap};
|
||||
use yazi_fs::provider::local::{self, Local};
|
||||
use yazi_fs::provider::{Provider, local::Local};
|
||||
use yazi_shared::{Ids, errors::PeekError, replace_to_printable};
|
||||
|
||||
static INCR: Ids = Ids::new();
|
||||
|
|
@ -132,7 +132,7 @@ impl Highlighter {
|
|||
|
||||
async fn find_syntax(
|
||||
path: &Path,
|
||||
reader: &mut BufReader<local::RwFile>,
|
||||
reader: &mut BufReader<tokio::fs::File>,
|
||||
) -> Result<&'static SyntaxReference> {
|
||||
let (_, syntaxes) = Self::init();
|
||||
let name = path.file_name().map(|n| n.to_string_lossy()).unwrap_or_default();
|
||||
|
|
|
|||
|
|
@ -38,6 +38,13 @@ impl<'a> From<&'a Path> for ByteStr<'a> {
|
|||
fn from(value: &'a Path) -> Self { ByteStr::from(value.as_os_str()) }
|
||||
}
|
||||
|
||||
impl<'a, T> From<&'a T> for ByteStr<'a>
|
||||
where
|
||||
T: AsRef<Path>,
|
||||
{
|
||||
fn from(value: &'a T) -> Self { Self::from(value.as_ref()) }
|
||||
}
|
||||
|
||||
impl PartialEq<&str> for ByteStr<'_> {
|
||||
fn eq(&self, other: &&str) -> bool { self.0 == other.as_bytes() }
|
||||
}
|
||||
|
|
@ -94,6 +101,8 @@ impl<'a> ByteStr<'a> {
|
|||
}
|
||||
}
|
||||
|
||||
pub fn into_owned(self) -> ByteStr<'static> { ByteStr(Cow::Owned(self.0.into_owned())) }
|
||||
|
||||
pub(super) unsafe fn from_str_bytes_unchecked(bytes: &'a [u8]) -> Self {
|
||||
Self(Cow::Borrowed(bytes))
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,11 +1,11 @@
|
|||
use std::{io, pin::Pin, task::{Context, Poll, ready}, time::Duration};
|
||||
use std::{io, pin::Pin, sync::Arc, task::{Context, Poll, ready}, time::Duration};
|
||||
|
||||
use tokio::{io::{AsyncRead, AsyncWrite, ReadBuf}, sync::oneshot, time::{Timeout, timeout}};
|
||||
|
||||
use crate::{Error, Packet, Session, fs::Attrs};
|
||||
use crate::{Error, Operator, Packet, Session, fs::Attrs};
|
||||
|
||||
pub struct File<'a> {
|
||||
session: &'a Session,
|
||||
pub struct File {
|
||||
session: Arc<Session>,
|
||||
handle: String,
|
||||
|
||||
closed: bool,
|
||||
|
|
@ -16,39 +16,41 @@ pub struct File<'a> {
|
|||
flush_rx: Option<Timeout<oneshot::Receiver<Packet<'static>>>>,
|
||||
}
|
||||
|
||||
impl Unpin for File<'_> {}
|
||||
impl Unpin for File {}
|
||||
|
||||
impl Drop for File<'_> {
|
||||
impl Drop for File {
|
||||
fn drop(&mut self) {
|
||||
if !self.closed {
|
||||
self.session.close(&self.handle).ok();
|
||||
Operator::from(&self.session).close(&self.handle).ok();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> File<'a> {
|
||||
pub(crate) fn new(session: &'a Session, handle: impl Into<String>) -> Self {
|
||||
impl File {
|
||||
pub(crate) fn new(session: &Arc<Session>, handle: impl Into<String>) -> Self {
|
||||
Self {
|
||||
session,
|
||||
handle: handle.into(),
|
||||
session: session.clone(),
|
||||
handle: handle.into(),
|
||||
|
||||
closed: false,
|
||||
cursor: 0,
|
||||
closed: false,
|
||||
cursor: 0,
|
||||
close_rx: None,
|
||||
read_rx: None,
|
||||
read_rx: None,
|
||||
write_rx: None,
|
||||
flush_rx: None,
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn fstat(&self) -> Result<Attrs, Error> { self.session.fstat(&self.handle).await }
|
||||
pub async fn fstat(&self) -> Result<Attrs, Error> {
|
||||
Operator::from(&self.session).fstat(&self.handle).await
|
||||
}
|
||||
|
||||
pub async fn fsetstat(&self, attrs: Attrs) -> Result<(), Error> {
|
||||
self.session.fsetstat(&self.handle, attrs).await
|
||||
Operator::from(&self.session).fsetstat(&self.handle, attrs).await
|
||||
}
|
||||
}
|
||||
|
||||
impl AsyncRead for File<'_> {
|
||||
impl AsyncRead for File {
|
||||
fn poll_read(
|
||||
self: Pin<&mut Self>,
|
||||
cx: &mut Context<'_>,
|
||||
|
|
@ -58,7 +60,7 @@ impl AsyncRead for File<'_> {
|
|||
|
||||
if me.read_rx.is_none() {
|
||||
let max = buf.remaining().min(261120) as u32;
|
||||
me.read_rx = Some(me.session.read(&me.handle, me.cursor, max)?);
|
||||
me.read_rx = Some(Operator::from(&me.session).read(&me.handle, me.cursor, max)?);
|
||||
}
|
||||
|
||||
let result = ready!(Pin::new(me.read_rx.as_mut().unwrap()).poll(cx));
|
||||
|
|
@ -79,7 +81,7 @@ impl AsyncRead for File<'_> {
|
|||
}
|
||||
}
|
||||
|
||||
impl AsyncWrite for File<'_> {
|
||||
impl AsyncWrite for File {
|
||||
fn poll_write(
|
||||
self: Pin<&mut Self>,
|
||||
cx: &mut Context<'_>,
|
||||
|
|
@ -91,7 +93,7 @@ impl AsyncWrite for File<'_> {
|
|||
Some((rx, len)) => (rx, *len),
|
||||
None => {
|
||||
let max = buf.len().min(261120);
|
||||
let rx = me.session.write(&me.handle, me.cursor, &buf[..max])?;
|
||||
let rx = Operator::from(&me.session).write(&me.handle, me.cursor, &buf[..max])?;
|
||||
(&mut me.write_rx.get_or_insert((rx, max)).0, max)
|
||||
}
|
||||
};
|
||||
|
|
@ -114,7 +116,7 @@ impl AsyncWrite for File<'_> {
|
|||
let me = unsafe { self.get_unchecked_mut() };
|
||||
|
||||
if me.flush_rx.is_none() {
|
||||
match me.session.fsync(&me.handle) {
|
||||
match Operator::from(&me.session).fsync(&me.handle) {
|
||||
Ok(rx) => me.flush_rx = Some(timeout(Duration::from_secs(10), rx)),
|
||||
Err(Error::Unsupported) => return Poll::Ready(Ok(())),
|
||||
Err(e) => Err(e)?,
|
||||
|
|
@ -141,7 +143,8 @@ impl AsyncWrite for File<'_> {
|
|||
let me = unsafe { self.get_unchecked_mut() };
|
||||
|
||||
if me.close_rx.is_none() {
|
||||
me.close_rx = Some(timeout(Duration::from_secs(10), me.session.close(&me.handle)?));
|
||||
me.close_rx =
|
||||
Some(timeout(Duration::from_secs(10), Operator::from(&me.session).close(&me.handle)?));
|
||||
}
|
||||
|
||||
let rx = unsafe { Pin::new_unchecked(me.close_rx.as_mut().unwrap()) };
|
||||
|
|
|
|||
|
|
@ -1,20 +1,28 @@
|
|||
use std::mem;
|
||||
use std::{mem, sync::Arc};
|
||||
|
||||
use crate::{ByteStr, Error, Session, fs::DirEntry, requests, responses};
|
||||
|
||||
pub struct ReadDir<'a> {
|
||||
dir: ByteStr<'a>,
|
||||
pub struct ReadDir {
|
||||
session: Arc<Session>,
|
||||
dir: ByteStr<'static>,
|
||||
handle: String,
|
||||
session: &'a Session,
|
||||
|
||||
name: responses::Name<'a>,
|
||||
name: responses::Name<'static>,
|
||||
cursor: usize,
|
||||
done: bool,
|
||||
}
|
||||
|
||||
impl<'a> ReadDir<'a> {
|
||||
pub(crate) fn new(session: &'a Session, dir: ByteStr<'a>, handle: String) -> Self {
|
||||
Self { dir, handle, session, name: Default::default(), cursor: 0, done: false }
|
||||
impl ReadDir {
|
||||
pub(crate) fn new(session: &Arc<Session>, dir: ByteStr, handle: String) -> Self {
|
||||
Self {
|
||||
session: session.clone(),
|
||||
dir: dir.into_owned(),
|
||||
handle,
|
||||
|
||||
name: Default::default(),
|
||||
cursor: 0,
|
||||
done: false,
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn next(&mut self) -> Result<Option<DirEntry<'_>>, Error> {
|
||||
|
|
|
|||
|
|
@ -9,6 +9,7 @@ mod de;
|
|||
mod error;
|
||||
mod id;
|
||||
mod macros;
|
||||
mod operator;
|
||||
mod packet;
|
||||
mod ser;
|
||||
mod session;
|
||||
|
|
@ -17,6 +18,7 @@ pub use byte_str::*;
|
|||
pub(crate) use de::*;
|
||||
pub use error::*;
|
||||
pub(crate) use id::*;
|
||||
pub use operator::*;
|
||||
pub use packet::*;
|
||||
pub(crate) use ser::*;
|
||||
pub use session::*;
|
||||
|
|
|
|||
197
yazi-sftp/src/operator.rs
Normal file
197
yazi-sftp/src/operator.rs
Normal file
|
|
@ -0,0 +1,197 @@
|
|||
use std::{ops::Deref, path::PathBuf, sync::Arc};
|
||||
|
||||
use tokio::sync::oneshot;
|
||||
|
||||
use crate::{ByteStr, Error, Packet, Session, fs::{Attrs, File, Flags, ReadDir}, requests, responses};
|
||||
|
||||
pub struct Operator(Arc<Session>);
|
||||
|
||||
impl Deref for Operator {
|
||||
type Target = Session;
|
||||
|
||||
fn deref(&self) -> &Self::Target { &self.0 }
|
||||
}
|
||||
|
||||
impl From<&Arc<Session>> for Operator {
|
||||
fn from(session: &Arc<Session>) -> Self { Self(session.clone()) }
|
||||
}
|
||||
|
||||
impl Operator {
|
||||
pub async fn init(&mut self) -> Result<(), Error> {
|
||||
let version: responses::Version = self.send(requests::Init::default()).await?;
|
||||
*self.extensions.lock() = version.extensions;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn open<'a, P>(&self, path: P, flags: Flags, attrs: Attrs) -> Result<File, Error>
|
||||
where
|
||||
P: Into<ByteStr<'a>>,
|
||||
{
|
||||
let handle: responses::Handle = self.send(requests::Open::new(path, flags, attrs)).await?;
|
||||
|
||||
Ok(File::new(&self.0, handle.handle))
|
||||
}
|
||||
|
||||
pub fn close(&self, handle: &str) -> Result<oneshot::Receiver<Packet<'static>>, Error> {
|
||||
self.send_sync(requests::Close::new(handle))
|
||||
}
|
||||
|
||||
pub fn read(
|
||||
&self,
|
||||
handle: &str,
|
||||
offset: u64,
|
||||
len: u32,
|
||||
) -> Result<oneshot::Receiver<Packet<'static>>, Error> {
|
||||
self.send_sync(requests::Read::new(handle, offset, len))
|
||||
}
|
||||
|
||||
pub fn write(
|
||||
&self,
|
||||
handle: &str,
|
||||
offset: u64,
|
||||
data: &[u8],
|
||||
) -> Result<oneshot::Receiver<Packet<'static>>, Error> {
|
||||
self.send_sync(requests::Write::new(handle, offset, data))
|
||||
}
|
||||
|
||||
pub async fn lstat<'a, P>(&self, path: P) -> Result<Attrs, Error>
|
||||
where
|
||||
P: Into<ByteStr<'a>>,
|
||||
{
|
||||
let attrs: responses::Attrs = self.send(requests::Lstat::new(path)).await?;
|
||||
Ok(attrs.attrs)
|
||||
}
|
||||
|
||||
pub async fn fstat(&self, handle: &str) -> Result<Attrs, Error> {
|
||||
let attrs: responses::Attrs = self.send(requests::Fstat::new(handle)).await?;
|
||||
Ok(attrs.attrs)
|
||||
}
|
||||
|
||||
pub async fn setstat<'a, P>(&self, path: P, attrs: Attrs) -> Result<(), Error>
|
||||
where
|
||||
P: Into<ByteStr<'a>>,
|
||||
{
|
||||
let status: responses::Status = self.send(requests::SetStat::new(path, attrs)).await?;
|
||||
status.into()
|
||||
}
|
||||
|
||||
pub async fn fsetstat(&self, handle: &str, attrs: Attrs) -> Result<(), Error> {
|
||||
let status: responses::Status = self.send(requests::FSetStat::new(handle, attrs)).await?;
|
||||
status.into()
|
||||
}
|
||||
|
||||
pub async fn read_dir<'a>(&'a self, dir: impl Into<ByteStr<'a>>) -> Result<ReadDir, Error> {
|
||||
let dir: ByteStr = dir.into();
|
||||
let handle: responses::Handle = self.send(requests::OpenDir::new(&dir)).await?;
|
||||
|
||||
Ok(ReadDir::new(&self.0, dir, handle.handle))
|
||||
}
|
||||
|
||||
pub async fn remove<'a, P>(&self, path: P) -> Result<(), Error>
|
||||
where
|
||||
P: Into<ByteStr<'a>>,
|
||||
{
|
||||
let status: responses::Status = self.send(requests::Remove::new(path)).await?;
|
||||
status.into()
|
||||
}
|
||||
|
||||
pub async fn mkdir<'a, P>(&self, path: P, attrs: Attrs) -> Result<(), Error>
|
||||
where
|
||||
P: Into<ByteStr<'a>>,
|
||||
{
|
||||
let status: responses::Status = self.send(requests::Mkdir::new(path, attrs)).await?;
|
||||
status.into()
|
||||
}
|
||||
|
||||
pub async fn rmdir<'a, P>(&self, path: P) -> Result<(), Error>
|
||||
where
|
||||
P: Into<ByteStr<'a>>,
|
||||
{
|
||||
let status: responses::Status = self.send(requests::Rmdir::new(path)).await?;
|
||||
status.into()
|
||||
}
|
||||
|
||||
pub async fn realpath<'a, P>(&self, path: P) -> Result<PathBuf, Error>
|
||||
where
|
||||
P: Into<ByteStr<'a>>,
|
||||
{
|
||||
let mut name: responses::Name = self.send(requests::Realpath::new(path)).await?;
|
||||
if name.items.is_empty() {
|
||||
Err(Error::custom("realpath returned no names"))
|
||||
} else {
|
||||
Ok(name.items.swap_remove(0).name.into_path())
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn stat<'a, P>(&self, path: P) -> Result<Attrs, Error>
|
||||
where
|
||||
P: Into<ByteStr<'a>>,
|
||||
{
|
||||
let attrs: responses::Attrs = self.send(requests::Stat::new(path)).await?;
|
||||
Ok(attrs.attrs)
|
||||
}
|
||||
|
||||
pub async fn rename<'a, F, T>(&self, from: F, to: T) -> Result<(), Error>
|
||||
where
|
||||
F: Into<ByteStr<'a>>,
|
||||
T: Into<ByteStr<'a>>,
|
||||
{
|
||||
let status: responses::Status = self.send(requests::Rename::new(from, to)).await?;
|
||||
status.into()
|
||||
}
|
||||
|
||||
pub async fn readlink<'a, P>(&self, path: P) -> Result<PathBuf, Error>
|
||||
where
|
||||
P: Into<ByteStr<'a>>,
|
||||
{
|
||||
let mut name: responses::Name = self.send(requests::Readlink::new(path)).await?;
|
||||
if name.items.is_empty() {
|
||||
Err(Error::custom("readlink returned no names"))
|
||||
} else {
|
||||
Ok(name.items.swap_remove(0).name.into_path())
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn symlink<'a, L, O>(&self, original: O, link: L) -> Result<(), Error>
|
||||
where
|
||||
O: Into<ByteStr<'a>>,
|
||||
L: Into<ByteStr<'a>>,
|
||||
{
|
||||
let status: responses::Status = self.send(requests::Symlink::new(original, link)).await?;
|
||||
status.into()
|
||||
}
|
||||
|
||||
pub fn fsync(&self, handle: &str) -> Result<oneshot::Receiver<Packet<'static>>, Error> {
|
||||
if self.extensions.lock().get("fsync@openssh.com").is_none_or(|s| s != "1") {
|
||||
return Err(Error::Unsupported);
|
||||
}
|
||||
|
||||
let data = requests::ExtendedFsync::new(handle);
|
||||
self.send_sync(requests::Extended::new("fsync@openssh.com", data))
|
||||
}
|
||||
|
||||
pub async fn hardlink<'a, O, L>(&self, original: O, link: L) -> Result<(), Error>
|
||||
where
|
||||
O: Into<ByteStr<'a>>,
|
||||
L: Into<ByteStr<'a>>,
|
||||
{
|
||||
if self.extensions.lock().get("hardlink@openssh.com").is_none_or(|s| s != "1") {
|
||||
return Err(Error::Unsupported);
|
||||
}
|
||||
|
||||
let data = requests::ExtendedHardlink::new(original, link);
|
||||
let status: responses::Status =
|
||||
self.send(requests::Extended::new("hardlink@openssh.com", data)).await?;
|
||||
status.into()
|
||||
}
|
||||
|
||||
pub async fn limits(&self) -> Result<responses::ExtendedLimits, Error> {
|
||||
if self.extensions.lock().get("limits@openssh.com").is_none_or(|s| s != "1") {
|
||||
return Err(Error::Unsupported);
|
||||
}
|
||||
|
||||
let extended: responses::Extended =
|
||||
self.send(requests::Extended::new("limits@openssh.com", requests::ExtendedLimits)).await?;
|
||||
extended.try_into()
|
||||
}
|
||||
}
|
||||
|
|
@ -1,25 +1,30 @@
|
|||
use std::{any::TypeId, collections::HashMap, io::ErrorKind, path::PathBuf, sync::Arc};
|
||||
use std::{any::TypeId, collections::HashMap, io::ErrorKind, sync::Arc};
|
||||
|
||||
use parking_lot::Mutex;
|
||||
use russh::{ChannelStream, client::Msg};
|
||||
use serde::Serialize;
|
||||
use tokio::{io::{AsyncReadExt, AsyncWriteExt}, select, sync::{mpsc, oneshot}};
|
||||
|
||||
use crate::{ByteStr, Error, Id, Packet, fs::{Attrs, File, Flags, ReadDir}, requests, responses};
|
||||
use crate::{Error, Id, Packet, responses};
|
||||
|
||||
pub struct Session {
|
||||
tx: mpsc::UnboundedSender<Vec<u8>>,
|
||||
pub(crate) id: Id,
|
||||
callback: Arc<Mutex<HashMap<u32, oneshot::Sender<Packet<'static>>>>>,
|
||||
extensions: HashMap<String, String>,
|
||||
tx: mpsc::UnboundedSender<Vec<u8>>,
|
||||
id: Id,
|
||||
callback: Mutex<HashMap<u32, oneshot::Sender<Packet<'static>>>>,
|
||||
pub(super) extensions: Mutex<HashMap<String, String>>,
|
||||
}
|
||||
|
||||
impl Session {
|
||||
pub fn make(stream: ChannelStream<Msg>) -> Self {
|
||||
let (tx, mut rx) = mpsc::unbounded_channel::<Vec<u8>>();
|
||||
let (mut reader, mut writer) = tokio::io::split(stream);
|
||||
let callback = Arc::new(Mutex::new(HashMap::<_, oneshot::Sender<_>>::new()));
|
||||
pub fn make(stream: ChannelStream<Msg>) -> Arc<Self> {
|
||||
let (tx, mut rx) = mpsc::unbounded_channel();
|
||||
let me = Arc::new(Self {
|
||||
tx,
|
||||
id: Id::default(),
|
||||
callback: Default::default(),
|
||||
extensions: Default::default(),
|
||||
});
|
||||
|
||||
let (mut reader, mut writer) = tokio::io::split(stream);
|
||||
tokio::spawn(async move {
|
||||
while let Some(data) = rx.recv().await {
|
||||
if data.is_empty() {
|
||||
|
|
@ -32,206 +37,28 @@ impl Session {
|
|||
}
|
||||
});
|
||||
|
||||
let (tx_, callback_) = (tx.clone(), callback.clone());
|
||||
let me_ = me.clone();
|
||||
tokio::spawn(async move {
|
||||
loop {
|
||||
select! {
|
||||
Ok(len) = reader.read_u32() => {
|
||||
let mut buf = vec![0; len as usize];
|
||||
if let Err(e) = reader.read_exact(&mut buf).await && e.kind() == ErrorKind::UnexpectedEof {
|
||||
tx_.send(vec![]).ok();
|
||||
me_.tx.send(vec![]).ok();
|
||||
break;
|
||||
}
|
||||
if let Ok(packet) = crate::from_bytes(&buf)
|
||||
&& let Some(cb) = callback_.lock().remove(&packet.id())
|
||||
&& let Some(cb) = me_.callback.lock().remove(&packet.id())
|
||||
{
|
||||
cb.send(packet).ok();
|
||||
}
|
||||
}
|
||||
_ = tx_.closed() => break,
|
||||
_ = me_.tx.closed() => break,
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
Self { tx, id: Id::default(), callback, extensions: HashMap::new() }
|
||||
}
|
||||
|
||||
pub async fn init(&mut self) -> Result<(), Error> {
|
||||
let version: responses::Version = self.send(requests::Init::default()).await?;
|
||||
self.extensions = version.extensions;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn open<'a, P>(&self, path: P, flags: Flags, attrs: Attrs) -> Result<File<'_>, Error>
|
||||
where
|
||||
P: Into<ByteStr<'a>>,
|
||||
{
|
||||
let handle: responses::Handle = self.send(requests::Open::new(path, flags, attrs)).await?;
|
||||
|
||||
Ok(File::new(self, handle.handle))
|
||||
}
|
||||
|
||||
pub fn close(&self, handle: &str) -> Result<oneshot::Receiver<Packet<'static>>, Error> {
|
||||
self.send_sync(requests::Close::new(handle))
|
||||
}
|
||||
|
||||
pub fn read(
|
||||
&self,
|
||||
handle: &str,
|
||||
offset: u64,
|
||||
len: u32,
|
||||
) -> Result<oneshot::Receiver<Packet<'static>>, Error> {
|
||||
self.send_sync(requests::Read::new(handle, offset, len))
|
||||
}
|
||||
|
||||
pub fn write(
|
||||
&self,
|
||||
handle: &str,
|
||||
offset: u64,
|
||||
data: &[u8],
|
||||
) -> Result<oneshot::Receiver<Packet<'static>>, Error> {
|
||||
self.send_sync(requests::Write::new(handle, offset, data))
|
||||
}
|
||||
|
||||
pub async fn lstat<'a, P>(&self, path: P) -> Result<Attrs, Error>
|
||||
where
|
||||
P: Into<ByteStr<'a>>,
|
||||
{
|
||||
let attrs: responses::Attrs = self.send(requests::Lstat::new(path)).await?;
|
||||
Ok(attrs.attrs)
|
||||
}
|
||||
|
||||
pub async fn fstat(&self, handle: &str) -> Result<Attrs, Error> {
|
||||
let attrs: responses::Attrs = self.send(requests::Fstat::new(handle)).await?;
|
||||
Ok(attrs.attrs)
|
||||
}
|
||||
|
||||
pub async fn setstat<'a, P>(&self, path: P, attrs: Attrs) -> Result<(), Error>
|
||||
where
|
||||
P: Into<ByteStr<'a>>,
|
||||
{
|
||||
let status: responses::Status = self.send(requests::SetStat::new(path, attrs)).await?;
|
||||
status.into()
|
||||
}
|
||||
|
||||
pub async fn fsetstat(&self, handle: &str, attrs: Attrs) -> Result<(), Error> {
|
||||
let status: responses::Status = self.send(requests::FSetStat::new(handle, attrs)).await?;
|
||||
status.into()
|
||||
}
|
||||
|
||||
pub async fn read_dir<'a>(&'a self, dir: impl Into<ByteStr<'a>>) -> Result<ReadDir<'a>, Error> {
|
||||
let dir: ByteStr = dir.into();
|
||||
let handle: responses::Handle = self.send(requests::OpenDir::new(&dir)).await?;
|
||||
|
||||
Ok(ReadDir::new(self, dir, handle.handle))
|
||||
}
|
||||
|
||||
pub async fn remove<'a, P>(&self, path: P) -> Result<(), Error>
|
||||
where
|
||||
P: Into<ByteStr<'a>>,
|
||||
{
|
||||
let status: responses::Status = self.send(requests::Remove::new(path)).await?;
|
||||
status.into()
|
||||
}
|
||||
|
||||
pub async fn mkdir<'a, P>(&self, path: P, attrs: Attrs) -> Result<(), Error>
|
||||
where
|
||||
P: Into<ByteStr<'a>>,
|
||||
{
|
||||
let status: responses::Status = self.send(requests::Mkdir::new(path, attrs)).await?;
|
||||
status.into()
|
||||
}
|
||||
|
||||
pub async fn rmdir<'a, P>(&self, path: P) -> Result<(), Error>
|
||||
where
|
||||
P: Into<ByteStr<'a>>,
|
||||
{
|
||||
let status: responses::Status = self.send(requests::Rmdir::new(path)).await?;
|
||||
status.into()
|
||||
}
|
||||
|
||||
pub async fn realpath<'a, P>(&self, path: P) -> Result<PathBuf, Error>
|
||||
where
|
||||
P: Into<ByteStr<'a>>,
|
||||
{
|
||||
let mut name: responses::Name = self.send(requests::Realpath::new(path)).await?;
|
||||
if name.items.is_empty() {
|
||||
Err(Error::custom("realpath returned no names"))
|
||||
} else {
|
||||
Ok(name.items.swap_remove(0).name.into_path())
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn stat<'a, P>(&self, path: P) -> Result<Attrs, Error>
|
||||
where
|
||||
P: Into<ByteStr<'a>>,
|
||||
{
|
||||
let attrs: responses::Attrs = self.send(requests::Stat::new(path)).await?;
|
||||
Ok(attrs.attrs)
|
||||
}
|
||||
|
||||
pub async fn rename<'a, F, T>(&self, from: F, to: T) -> Result<(), Error>
|
||||
where
|
||||
F: Into<ByteStr<'a>>,
|
||||
T: Into<ByteStr<'a>>,
|
||||
{
|
||||
let status: responses::Status = self.send(requests::Rename::new(from, to)).await?;
|
||||
status.into()
|
||||
}
|
||||
|
||||
pub async fn readlink<'a, P>(&self, path: P) -> Result<PathBuf, Error>
|
||||
where
|
||||
P: Into<ByteStr<'a>>,
|
||||
{
|
||||
let mut name: responses::Name = self.send(requests::Readlink::new(path)).await?;
|
||||
if name.items.is_empty() {
|
||||
Err(Error::custom("readlink returned no names"))
|
||||
} else {
|
||||
Ok(name.items.swap_remove(0).name.into_path())
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn symlink<'a, L, O>(&self, link: L, original: O) -> Result<(), Error>
|
||||
where
|
||||
L: Into<ByteStr<'a>>,
|
||||
O: Into<ByteStr<'a>>,
|
||||
{
|
||||
let status: responses::Status = self.send(requests::Symlink::new(link, original)).await?;
|
||||
status.into()
|
||||
}
|
||||
|
||||
pub fn fsync(&self, handle: &str) -> Result<oneshot::Receiver<Packet<'static>>, Error> {
|
||||
if self.extensions.get("fsync@openssh.com").is_none_or(|s| s != "1") {
|
||||
return Err(Error::Unsupported);
|
||||
}
|
||||
|
||||
let data = requests::ExtendedFsync::new(handle);
|
||||
self.send_sync(requests::Extended::new("fsync@openssh.com", data))
|
||||
}
|
||||
|
||||
pub async fn hardlink<'a, O, L>(&self, original: O, link: L) -> Result<(), Error>
|
||||
where
|
||||
O: Into<ByteStr<'a>>,
|
||||
L: Into<ByteStr<'a>>,
|
||||
{
|
||||
if self.extensions.get("hardlink@openssh.com").is_none_or(|s| s != "1") {
|
||||
return Err(Error::Unsupported);
|
||||
}
|
||||
|
||||
let data = requests::ExtendedHardlink::new(original, link);
|
||||
let status: responses::Status =
|
||||
self.send(requests::Extended::new("hardlink@openssh.com", data)).await?;
|
||||
status.into()
|
||||
}
|
||||
|
||||
pub async fn limits(&self) -> Result<responses::ExtendedLimits, Error> {
|
||||
if self.extensions.get("limits@openssh.com").is_none_or(|s| s != "1") {
|
||||
return Err(Error::Unsupported);
|
||||
}
|
||||
|
||||
let extended: responses::Extended =
|
||||
self.send(requests::Extended::new("limits@openssh.com", requests::ExtendedLimits)).await?;
|
||||
extended.try_into()
|
||||
me
|
||||
}
|
||||
|
||||
pub async fn send<'a, I, O>(&self, input: I) -> Result<O, Error>
|
||||
|
|
|
|||
|
|
@ -53,9 +53,7 @@ impl<'a> TryFrom<&'a [u8]> for UrlCow<'a> {
|
|||
Self::Borrowed { loc: Loc::with(p, uri, urn)?, scheme }
|
||||
}
|
||||
(Cow::Owned(p), None) => Self::Owned { loc: LocBuf::from(p), scheme },
|
||||
(Cow::Owned(p), Some((uri, urn))) => {
|
||||
Self::Owned { loc: LocBuf::with(p, uri, urn)?, scheme }
|
||||
}
|
||||
(Cow::Owned(p), Some((uri, urn))) => Self::Owned { loc: LocBuf::with(p, uri, urn)?, scheme },
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -13,7 +13,7 @@ pub(crate) struct Backend {
|
|||
impl Backend {
|
||||
pub(crate) fn serve(out_tx: mpsc::UnboundedSender<UrlBuf>) -> Self {
|
||||
#[cfg(any(target_os = "linux", target_os = "macos"))]
|
||||
yazi_fs::mounts::Partitions::monitor(yazi_fs::mounts::PARTITIONS.clone(), || {
|
||||
yazi_fs::mounts::Partitions::monitor(&yazi_fs::mounts::PARTITIONS, || {
|
||||
yazi_macro::err!(yazi_dds::Pubsub::pub_after_mount())
|
||||
});
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue