Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: Introduce remote scheme #1152

Open
wants to merge 7 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
461 changes: 461 additions & 0 deletions Cargo.lock

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion cspell.json
Original file line number Diff line number Diff line change
@@ -1 +1 @@
{"language":"en","version":"0.2","flagWords":[],"words":["Punct","KEYMAP","splitn","crossterm","YAZI","unar","peekable","ratatui","syntect","pbpaste","pbcopy","ffmpegthumbnailer","oneshot","Posix","Lsar","XADDOS","zoxide","cands","Deque","precache","imageops","IFBLK","IFCHR","IFDIR","IFIFO","IFLNK","IFMT","IFSOCK","IRGRP","IROTH","IRUSR","ISGID","ISUID","ISVTX","IWGRP","IWOTH","IWUSR","IXGRP","IXOTH","IXUSR","libc","winsize","TIOCGWINSZ","xpixel","ypixel","ioerr","appender","Catppuccin","macchiato","gitmodules","Dotfiles","bashprofile","vimrc","flac","webp","exiftool","mediainfo","ripgrep","nvim","indexmap","indexmap","unwatch","canonicalize","serde","fsevent","Ueberzug","iterm","wezterm","sixel","chafa","ueberzugpp","️ Überzug","️ Überzug","Konsole","Alacritty","Überzug","pkgs","paru","unarchiver","pdftoppm","poppler","prebuild","singlefile","jpegopt","EXIF","rustfmt","mktemp","nanos","xclip","xsel","natord","Mintty","nixos","nixpkgs","SIGTSTP","SIGCONT","SIGCONT","mlua","nonstatic","userdata","metatable","natsort","backstack","luajit","Succ","Succ","cand","fileencoding","foldmethod","lightgreen","darkgray","lightred","lightyellow","lightcyan","nushell","msvc","aarch","linemode","sxyazi","rsplit","ZELLIJ","bitflags","bitflags","USERPROFILE","Neovim","vergen","gitcl","Renderable","preloaders","prec","imagesize","Upserting","prio","Ghostty","Catmull","Lanczos","cmds","unyank","scrolloff","headsup","unsub","uzers","scopeguard","SPDLOG","globset","filetime","magick","magick","prefetcher","Prework","prefetchers","PREWORKERS","conds","translit","rxvt","Urxvt","realpath"]}
{"words":["Punct","KEYMAP","splitn","crossterm","YAZI","unar","peekable","ratatui","syntect","pbpaste","pbcopy","ffmpegthumbnailer","oneshot","Posix","Lsar","XADDOS","zoxide","cands","Deque","precache","imageops","IFBLK","IFCHR","IFDIR","IFIFO","IFLNK","IFMT","IFSOCK","IRGRP","IROTH","IRUSR","ISGID","ISUID","ISVTX","IWGRP","IWOTH","IWUSR","IXGRP","IXOTH","IXUSR","libc","winsize","TIOCGWINSZ","xpixel","ypixel","ioerr","appender","Catppuccin","macchiato","gitmodules","Dotfiles","bashprofile","vimrc","flac","webp","exiftool","mediainfo","ripgrep","nvim","indexmap","indexmap","unwatch","canonicalize","serde","fsevent","Ueberzug","iterm","wezterm","sixel","chafa","ueberzugpp","️ Überzug","️ Überzug","Konsole","Alacritty","Überzug","pkgs","paru","unarchiver","pdftoppm","poppler","prebuild","singlefile","jpegopt","EXIF","rustfmt","mktemp","nanos","xclip","xsel","natord","Mintty","nixos","nixpkgs","SIGTSTP","SIGCONT","SIGCONT","mlua","nonstatic","userdata","metatable","natsort","backstack","luajit","Succ","Succ","cand","fileencoding","foldmethod","lightgreen","darkgray","lightred","lightyellow","lightcyan","nushell","msvc","aarch","linemode","sxyazi","rsplit","ZELLIJ","bitflags","bitflags","USERPROFILE","Neovim","vergen","gitcl","Renderable","preloaders","prec","imagesize","Upserting","prio","Ghostty","Catmull","Lanczos","cmds","unyank","scrolloff","headsup","unsub","uzers","scopeguard","SPDLOG","globset","filetime","magick","magick","prefetcher","Prework","prefetchers","PREWORKERS","conds","translit","rxvt","Urxvt","realpath","opendal"],"language":"en","flagWords":[],"version":"0.2"}
1 change: 1 addition & 0 deletions yazi-config/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ bitflags = "2.5.0"
crossterm = "0.27.0"
globset = "0.4.14"
indexmap = "2.2.6"
opendal = "0.47.0"
ratatui = "0.26.3"
serde = { version = "1.0.203", features = [ "derive" ] }
shell-words = "1.1.0"
Expand Down
1 change: 1 addition & 0 deletions yazi-fm/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ yazi-boot = { path = "../yazi-boot", version = "0.2.5" }
yazi-config = { path = "../yazi-config", version = "0.2.5" }
yazi-core = { path = "../yazi-core", version = "0.2.5" }
yazi-dds = { path = "../yazi-dds", version = "0.2.5" }
yazi-fs = { path = "../yazi-fs", version = "0.2.5" }
yazi-plugin = { path = "../yazi-plugin", version = "0.2.5" }
yazi-proxy = { path = "../yazi-proxy", version = "0.2.5" }
yazi-shared = { path = "../yazi-shared", version = "0.2.5" }
Expand Down
19 changes: 19 additions & 0 deletions yazi-fs/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
[package]
name = "yazi-fs"
version = "0.2.5"
edition = "2021"
license = "MIT"
authors = [ "sxyazi <[email protected]>" ]
description = "Yazi file system compatibility layer"
homepage = "https://yazi-rs.github.io"
repository = "https://github.com/sxyazi/yazi"

[dependencies]
yazi-shared = { path = "../yazi-shared", version = "0.2.5" }

# External dependencies
anyhow = "1.0.86"
opendal = "0.47.0"
serde = { version = "1.0.203", features = [ "derive" ] }
tokio = { version = "1.38.0", features = [ "full" ] }
toml = { version = "0.8.14", features = [ "preserve_order" ] }
3 changes: 3 additions & 0 deletions yazi-fs/src/lib.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
#![allow(clippy::option_map_unit_fn)]

pub mod providers;
7 changes: 7 additions & 0 deletions yazi-fs/src/providers/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
#![allow(clippy::module_inception)]

mod provider;
mod providers;

pub use provider::*;
pub use providers::*;
20 changes: 20 additions & 0 deletions yazi-fs/src/providers/provider.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
use std::{collections::HashMap, str::FromStr};

use serde::Deserialize;

#[derive(Debug, Deserialize)]
pub struct Provider {
pub name: String,
#[serde(rename = "type")]
pub type_: String,
pub config: HashMap<String, String>,
}

impl TryFrom<Provider> for opendal::Operator {
type Error = anyhow::Error;

fn try_from(value: Provider) -> Result<Self, Self::Error> {
let scheme = opendal::Scheme::from_str(&value.name)?;
Ok(opendal::Operator::via_map(scheme, value.config)?)
}
}
47 changes: 47 additions & 0 deletions yazi-fs/src/providers/providers.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
use std::{collections::HashMap, ops::Deref};

use anyhow::Result;
use serde::{Deserialize, Deserializer};
use yazi_shared::Xdg;

use crate::providers::Provider;

pub struct Providers(HashMap<String, opendal::Operator>);

impl Deref for Providers {
type Target = HashMap<String, opendal::Operator>;

fn deref(&self) -> &Self::Target { &self.0 }
}

impl Default for Providers {
fn default() -> Self {
let s = std::fs::read_to_string(Xdg::state_dir().join(".secret.toml")).unwrap_or_default();
toml::from_str(&s).unwrap()
}
}

impl<'de> Deserialize<'de> for Providers {
fn deserialize<D>(deserializer: D) -> std::result::Result<Self, D::Error>
where
D: Deserializer<'de>,
{
#[derive(Deserialize, Default)]
struct Outer {
providers: Shadow,
}
#[derive(Deserialize, Default)]
struct Shadow {
rules: Vec<Provider>,
}

let outer = Outer::deserialize(deserializer)?;
let result: Result<_> =
outer.providers.rules.into_iter().map(|p| Ok((p.name.clone(), p.try_into()?))).collect();

match result {
Ok(v) => Ok(Providers(v)),
Err(e) => Err(serde::de::Error::custom(e)),
}
}
}
1 change: 1 addition & 0 deletions yazi-shared/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ crossterm = "0.27.0"
dirs = "5.0.1"
filetime = "0.2.23"
futures = "0.3.30"
opendal = "0.47.0"
parking_lot = "0.12.3"
percent-encoding = "2.3.1"
ratatui = "0.26.3"
Expand Down
38 changes: 38 additions & 0 deletions yazi-shared/src/fs/file.rs
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,11 @@ impl Deref for File {
impl File {
#[inline]
pub async fn from(url: Url) -> Result<Self> {
// TODO: refactor this
// if url.is_remote().is_some() {
// return Self::from_remote(url).await;
// }

let meta = fs::symlink_metadata(&url).await?;
Ok(Self::from_meta(url, meta).await)
}
Expand Down Expand Up @@ -57,6 +62,39 @@ impl File {
Self { url, cha: Cha::from(meta).with_kind(ck), link_to, icon: Default::default() }
}

// TODO: refactor this
/// Build a new file from remote.
// pub async fn from_remote(url: Url) -> Result<Self> {
// let scheme = url.is_remote().ok_or(anyhow!("not a remote file"))?;
// let op = SCHEMES.get(scheme)?;

// let meta = op.stat(&url.as_path().to_string_lossy()).await?;
// let mut kind = ChaKind::default();
// if meta.is_dir() {
// kind |= ChaKind::DIR;
// }
// let cha = Cha {
// kind,
// len: meta.content_length(),
// accessed: None,
// created: None,
// modified: meta
// .last_modified()
// .map(|v| SystemTime::UNIX_EPOCH.add(Duration::from_micros(v.timestamp_micros() as u64))),
// // Always return 774 for remote files.
// #[cfg(unix)]
// permissions: 0774,
// // Always return current user for remote files.
// #[cfg(unix)]
// uid: unsafe { libc::getuid().into() },
// // Always return current group for remote files.
// #[cfg(unix)]
// gid: unsafe { libc::getgid().into() },
// };

// Ok(Self { url, cha, link_to: None, icon: Default::default() })
// }

#[inline]
pub fn from_dummy(url: &Url) -> Self { Self { url: url.to_owned(), ..Default::default() } }
}
Expand Down
31 changes: 22 additions & 9 deletions yazi-shared/src/fs/url.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,12 +12,13 @@ pub struct Url {
frag: String,
}

#[derive(Clone, Copy, Default, Eq, Hash, Ord, PartialEq, PartialOrd)]
#[derive(Clone, Default, Eq, Hash, Ord, PartialEq, PartialOrd)]
pub enum UrlScheme {
#[default]
Regular,
Search,
Archive,
Remote(String),
}

impl Deref for Url {
Expand Down Expand Up @@ -98,18 +99,16 @@ impl AsRef<OsStr> for Url {

impl Display for Url {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
if self.scheme == UrlScheme::Regular {
return f.write_str(&self.path.to_string_lossy());
match &self.scheme {
UrlScheme::Regular => return f.write_str(&self.path.to_string_lossy()),
UrlScheme::Search => write!(f, "search://")?,
UrlScheme::Archive => write!(f, "archive://")?,
UrlScheme::Remote(scheme) => write!(f, "{scheme}://")?,
}

let scheme = match self.scheme {
UrlScheme::Regular => unreachable!(),
UrlScheme::Search => "search://",
UrlScheme::Archive => "archive://",
};
let path = percent_encode(self.path.as_os_str().as_encoded_bytes(), ENCODE_SET);
write!(f, "{path}")?;

write!(f, "{scheme}{path}")?;
if !self.frag.is_empty() {
write!(f, "#{}", self.frag)?;
}
Expand All @@ -126,6 +125,7 @@ impl Url {
UrlScheme::Regular => url,
UrlScheme::Search => url,
UrlScheme::Archive => url.into_archive(),
UrlScheme::Remote(_) => url,
}
}

Expand All @@ -137,6 +137,7 @@ impl Url {
UrlScheme::Regular => url,
UrlScheme::Search => url,
UrlScheme::Archive => url,
UrlScheme::Remote(_) => url,
}
})
}
Expand Down Expand Up @@ -195,6 +196,18 @@ impl Url {
self
}

/// Check and fetch remote scheme.
///
/// Returns None if current url is not remote, otherwise returns
/// `Some(scheme)`
#[inline]
pub fn is_remote(&self) -> Option<&str> {
match &self.scheme {
UrlScheme::Remote(scheme) => Some(scheme),
_ => None,
}
}

// --- Path
#[inline]
pub fn set_path(&mut self, path: PathBuf) { self.path = path; }
Expand Down