mirror of
https://github.com/sxyazi/yazi.git
synced 2026-07-21 23:01:05 +00:00
feat: new fs.copy() and fs.rename() APIs (#3467)
This commit is contained in:
parent
c5a3e42986
commit
77d36ff5a5
14 changed files with 711 additions and 179 deletions
|
|
@ -21,6 +21,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/):
|
|||
- Zoom in or out of the preview image ([#2864])
|
||||
- Improve the UX of the pick and input components ([#2906], [#2935])
|
||||
- Show progress of each task in task manager ([#3121], [#3131], [#3134])
|
||||
- New `fs.copy()` and `fs.rename()` APIs ([#3467])
|
||||
- New experimental `ya.async()` API ([#3422])
|
||||
- New `overall` option to set the overall background color ([#3317])
|
||||
- Rounded corners for indicator bar ([#3419])
|
||||
|
|
@ -1560,3 +1561,4 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/):
|
|||
[#3422]: https://github.com/sxyazi/yazi/pull/3422
|
||||
[#3429]: https://github.com/sxyazi/yazi/pull/3429
|
||||
[#3456]: https://github.com/sxyazi/yazi/pull/3456
|
||||
[#3467]: https://github.com/sxyazi/yazi/pull/3467
|
||||
|
|
|
|||
745
Cargo.lock
generated
745
Cargo.lock
generated
File diff suppressed because it is too large
Load diff
|
|
@ -26,7 +26,7 @@ lto = false
|
|||
debug = false
|
||||
|
||||
[workspace.dependencies]
|
||||
ansi-to-tui = "7.0.0"
|
||||
ansi-to-tui = "8.0.0"
|
||||
anyhow = "1.0.100"
|
||||
base64 = "0.22.1"
|
||||
bitflags = { version = "2.10.0", features = [ "serde" ] }
|
||||
|
|
@ -48,12 +48,12 @@ parking_lot = "0.12.5"
|
|||
paste = "1.0.15"
|
||||
percent-encoding = "2.3.2"
|
||||
rand = { version = "0.9.2", default-features = false, features = [ "os_rng", "small_rng", "std" ] }
|
||||
ratatui = { version = "0.29.0", features = [ "serde", "unstable-rendered-line-info", "unstable-widget-ref" ] }
|
||||
ratatui = { version = "0.30.0", features = [ "serde", "unstable-rendered-line-info", "unstable-widget-ref" ] }
|
||||
regex = "1.12.2"
|
||||
russh = { version = "0.56.0", default-features = false, features = [ "ring", "rsa" ] }
|
||||
scopeguard = "1.2.0"
|
||||
serde = { version = "1.0.228", features = [ "derive" ] }
|
||||
serde_json = "1.0.146"
|
||||
serde_json = "1.0.147"
|
||||
syntect = { version = "5.3.0", default-features = false, features = [ "parsing", "plist-load", "regex-onig" ] }
|
||||
thiserror = "2.0.17"
|
||||
tokio = { version = "1.48.0", features = [ "full" ] }
|
||||
|
|
@ -63,7 +63,7 @@ toml = { version = "0.9.10" }
|
|||
tracing = { version = "0.1.44", features = [ "max_level_debug", "release_max_level_debug" ] }
|
||||
twox-hash = { version = "2.1.2", default-features = false, features = [ "std", "random", "xxhash3_128" ] }
|
||||
typed-path = "0.12.0"
|
||||
unicode-width = { version = "0.2.0", default-features = false }
|
||||
unicode-width = { version = "0.2.2", default-features = false }
|
||||
uzers = "0.12.2"
|
||||
|
||||
[workspace.lints.clippy]
|
||||
|
|
|
|||
|
|
@ -20,7 +20,7 @@ pub struct Border {
|
|||
pub r#type: ratatui::widgets::BorderType,
|
||||
pub style: ratatui::style::Style,
|
||||
|
||||
pub titles: Vec<(ratatui::widgets::block::Position, ratatui::text::Line<'static>)>,
|
||||
pub titles: Vec<(ratatui::widgets::TitlePosition, ratatui::text::Line<'static>)>,
|
||||
}
|
||||
|
||||
impl Border {
|
||||
|
|
@ -51,8 +51,8 @@ impl Border {
|
|||
|
||||
for title in self.titles {
|
||||
block = match title {
|
||||
(ratatui::widgets::block::Position::Top, line) => block.title(line),
|
||||
(ratatui::widgets::block::Position::Bottom, line) => block.title(line),
|
||||
(ratatui::widgets::TitlePosition::Top, line) => block.title(line),
|
||||
(ratatui::widgets::TitlePosition::Bottom, line) => block.title(line),
|
||||
};
|
||||
}
|
||||
|
||||
|
|
@ -80,9 +80,9 @@ impl UserData for Border {
|
|||
"title",
|
||||
|_, (ud, line, position): (AnyUserData, Value, Option<u8>)| {
|
||||
let position = if position == Some(Borders::BOTTOM.bits()) {
|
||||
ratatui::widgets::block::Position::Bottom
|
||||
ratatui::widgets::TitlePosition::Bottom
|
||||
} else {
|
||||
ratatui::widgets::block::Position::Top
|
||||
ratatui::widgets::TitlePosition::Top
|
||||
};
|
||||
|
||||
ud.borrow_mut::<Self>()?.titles.push((position, Line::try_from(line)?.inner));
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
use ratatui::{buffer::Buffer, layout::{self, Alignment, Constraint, Rect}, text::Line, widgets::{Block, BorderType, Widget, WidgetRef}};
|
||||
use ratatui::{buffer::Buffer, layout::{self, Alignment, Constraint, Rect}, text::Line, widgets::{Block, BorderType, Widget}};
|
||||
use yazi_config::THEME;
|
||||
use yazi_core::{Core, tasks::TASKS_PERCENT};
|
||||
|
||||
|
|
@ -39,7 +39,7 @@ impl Widget for Tasks<'_> {
|
|||
.title_alignment(Alignment::Center)
|
||||
.border_type(BorderType::Rounded)
|
||||
.border_style(THEME.tasks.border);
|
||||
block.render_ref(area, buf);
|
||||
(&block).render(area, buf);
|
||||
|
||||
List::new(self.core).render(block.inner(area), buf);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@ use std::time::{Duration, SystemTime, UNIX_EPOCH};
|
|||
|
||||
use crate::cha::{Cha, ChaMode};
|
||||
|
||||
#[derive(Clone, Copy, Debug, Default)]
|
||||
#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
|
||||
pub struct Attrs {
|
||||
pub mode: Option<ChaMode>,
|
||||
pub atime: Option<SystemTime>,
|
||||
|
|
@ -16,35 +16,57 @@ impl From<Cha> for Attrs {
|
|||
}
|
||||
}
|
||||
|
||||
impl From<Attrs> for std::fs::FileTimes {
|
||||
fn from(attrs: Attrs) -> Self {
|
||||
let mut t = Self::new();
|
||||
impl TryFrom<Attrs> for std::fs::FileTimes {
|
||||
type Error = ();
|
||||
|
||||
if let Some(atime) = attrs.atime {
|
||||
fn try_from(value: Attrs) -> Result<Self, Self::Error> {
|
||||
if !value.has_times() {
|
||||
return Err(());
|
||||
}
|
||||
|
||||
let mut t = Self::new();
|
||||
if let Some(atime) = value.atime {
|
||||
t = t.set_accessed(atime);
|
||||
}
|
||||
|
||||
#[cfg(target_os = "macos")]
|
||||
if let Some(btime) = attrs.btime {
|
||||
if let Some(btime) = value.btime {
|
||||
use std::os::macos::fs::FileTimesExt;
|
||||
t = t.set_created(btime);
|
||||
}
|
||||
|
||||
#[cfg(windows)]
|
||||
if let Some(btime) = attrs.btime {
|
||||
if let Some(btime) = value.btime {
|
||||
use std::os::windows::fs::FileTimesExt;
|
||||
t = t.set_created(btime);
|
||||
}
|
||||
|
||||
if let Some(mtime) = attrs.mtime {
|
||||
if let Some(mtime) = value.mtime {
|
||||
t = t.set_modified(mtime);
|
||||
}
|
||||
|
||||
t
|
||||
Ok(t)
|
||||
}
|
||||
}
|
||||
|
||||
impl TryFrom<Attrs> for std::fs::Permissions {
|
||||
type Error = ();
|
||||
|
||||
fn try_from(value: Attrs) -> Result<Self, Self::Error> {
|
||||
#[cfg(unix)]
|
||||
if let Some(mode) = value.mode {
|
||||
return Ok(mode.into());
|
||||
}
|
||||
|
||||
Err(())
|
||||
}
|
||||
}
|
||||
|
||||
impl Attrs {
|
||||
pub fn has_times(self) -> bool {
|
||||
self.atime.is_some() || self.btime.is_some() || self.mtime.is_some()
|
||||
}
|
||||
|
||||
pub fn atime_dur(self) -> Option<Duration> { self.atime?.duration_since(UNIX_EPOCH).ok() }
|
||||
|
||||
pub fn btime_dur(self) -> Option<Duration> { self.btime?.duration_since(UNIX_EPOCH).ok() }
|
||||
|
|
|
|||
|
|
@ -22,7 +22,9 @@ pub(super) async fn copy_impl(from: PathBuf, to: PathBuf, attrs: Attrs) -> io::R
|
|||
if let Some(mode) = attrs.mode {
|
||||
writer.set_permissions(mode.into()).ok();
|
||||
}
|
||||
writer.set_times(attrs.into()).ok();
|
||||
if let Ok(times) = attrs.try_into() {
|
||||
writer.set_times(times).ok();
|
||||
}
|
||||
|
||||
Ok(written)
|
||||
})
|
||||
|
|
@ -34,8 +36,10 @@ pub(super) async fn copy_impl(from: PathBuf, to: PathBuf, attrs: Attrs) -> io::R
|
|||
tokio::task::spawn_blocking(move || {
|
||||
let written = std::fs::copy(from, &to)?;
|
||||
|
||||
if let Ok(file) = std::fs::File::options().write(true).open(to) {
|
||||
file.set_times(attrs.into()).ok();
|
||||
if let Ok(times) = attrs.try_into()
|
||||
&& let Ok(file) = std::fs::File::options().write(true).open(to)
|
||||
{
|
||||
file.set_times(times).ok();
|
||||
}
|
||||
|
||||
Ok(written)
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@ use std::str::FromStr;
|
|||
use mlua::{ExternalError, Function, IntoLua, IntoLuaMulti, Lua, Table, Value};
|
||||
use yazi_binding::{Cha, Composer, ComposerGet, ComposerSet, Error, File, Url, UrlRef};
|
||||
use yazi_config::Pattern;
|
||||
use yazi_fs::{mounts::PARTITIONS, provider::{DirReader, FileHolder}};
|
||||
use yazi_fs::{mounts::PARTITIONS, provider::{Attrs, DirReader, FileHolder}};
|
||||
use yazi_shared::url::{UrlCow, UrlLike};
|
||||
use yazi_vfs::{VfsFile, provider};
|
||||
|
||||
|
|
@ -15,9 +15,11 @@ pub fn compose() -> Composer<ComposerGet, ComposerSet> {
|
|||
b"op" => op(lua)?,
|
||||
b"cwd" => cwd(lua)?,
|
||||
b"cha" => cha(lua)?,
|
||||
b"copy" => copy(lua)?,
|
||||
b"write" => write(lua)?,
|
||||
b"create" => create(lua)?,
|
||||
b"remove" => remove(lua)?,
|
||||
b"rename" => rename(lua)?,
|
||||
b"read_dir" => read_dir(lua)?,
|
||||
b"calc_size" => calc_size(lua)?,
|
||||
b"expand_url" => expand_url(lua)?,
|
||||
|
|
@ -64,6 +66,15 @@ fn cha(lua: &Lua) -> mlua::Result<Function> {
|
|||
})
|
||||
}
|
||||
|
||||
fn copy(lua: &Lua) -> mlua::Result<Function> {
|
||||
lua.create_async_function(|lua, (from, to): (UrlRef, UrlRef)| async move {
|
||||
match provider::copy(&*from, &*to, Attrs::default()).await {
|
||||
Ok(len) => len.into_lua_multi(&lua),
|
||||
Err(e) => (Value::Nil, Error::Io(e)).into_lua_multi(&lua),
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
fn write(lua: &Lua) -> mlua::Result<Function> {
|
||||
lua.create_async_function(|lua, (url, data): (UrlRef, mlua::String)| async move {
|
||||
match provider::write(&*url, data.as_bytes()).await {
|
||||
|
|
@ -105,6 +116,15 @@ fn remove(lua: &Lua) -> mlua::Result<Function> {
|
|||
})
|
||||
}
|
||||
|
||||
fn rename(lua: &Lua) -> mlua::Result<Function> {
|
||||
lua.create_async_function(|lua, (from, to): (UrlRef, UrlRef)| async move {
|
||||
match provider::rename(&*from, &*to).await {
|
||||
Ok(()) => true.into_lua_multi(&lua),
|
||||
Err(e) => (false, Error::Io(e)).into_lua_multi(&lua),
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
fn read_dir(lua: &Lua) -> mlua::Result<Function> {
|
||||
lua.create_async_function(|lua, (dir, options): (UrlRef, Table)| async move {
|
||||
let pat = if let Ok(s) = options.raw_get::<mlua::String>("glob") {
|
||||
|
|
|
|||
|
|
@ -23,7 +23,7 @@ impl Utils {
|
|||
r#type: ratatui::widgets::BorderType::Rounded,
|
||||
style: THEME.spot.border.into(),
|
||||
titles: vec![(
|
||||
ratatui::widgets::block::Position::Top,
|
||||
ratatui::widgets::TitlePosition::Top,
|
||||
ratatui::text::Line::raw("Spot").centered().style(THEME.spot.title),
|
||||
)],
|
||||
}),
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@ use std::{collections::HashMap, fmt};
|
|||
|
||||
use serde::{Deserialize, Deserializer, Serialize, de::Visitor, ser::SerializeStruct};
|
||||
|
||||
#[derive(Clone, Debug, Default)]
|
||||
#[derive(Clone, Debug, Default, Eq, PartialEq)]
|
||||
pub struct Attrs {
|
||||
pub size: Option<u64>,
|
||||
pub uid: Option<u32>,
|
||||
|
|
@ -14,6 +14,8 @@ pub struct Attrs {
|
|||
}
|
||||
|
||||
impl Attrs {
|
||||
pub fn is_empty(&self) -> bool { *self == Self::default() }
|
||||
|
||||
pub fn len(&self) -> usize {
|
||||
let mut len = 4;
|
||||
if let Some(size) = self.size {
|
||||
|
|
|
|||
|
|
@ -28,18 +28,22 @@ impl RwFile {
|
|||
pub async fn set_attrs(&self, attrs: Attrs) -> io::Result<()> {
|
||||
match self {
|
||||
Self::Tokio(f) => {
|
||||
let (perm, times) = (attrs.try_into(), attrs.try_into());
|
||||
if perm.is_err() && times.is_err() {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
let std = f.try_clone().await?.into_std().await;
|
||||
tokio::task::spawn_blocking(move || {
|
||||
#[cfg(unix)]
|
||||
if let Some(mode) = attrs.mode {
|
||||
std.set_permissions(mode.into()).ok();
|
||||
}
|
||||
std.set_times(attrs.into()).ok();
|
||||
perm.map(|p| std.set_permissions(p)).ok();
|
||||
times.map(|t| std.set_times(t)).ok();
|
||||
})
|
||||
.await?;
|
||||
}
|
||||
Self::Sftp(f) => {
|
||||
f.fsetstat(&super::sftp::Attrs(attrs).into()).await?;
|
||||
if let Ok(attrs) = super::sftp::Attrs(attrs).try_into() {
|
||||
f.fsetstat(&attrs).await?;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -69,7 +69,7 @@ impl FileBuilder for Gate {
|
|||
};
|
||||
|
||||
let flags = Flags::from(*self);
|
||||
let attrs = super::Attrs(self.0.attrs).into();
|
||||
let attrs = super::Attrs(self.0.attrs).try_into().unwrap_or_default();
|
||||
Ok(Conn { name, config }.roll().await?.open(path, flags, &attrs).await?)
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -5,17 +5,21 @@ use yazi_fs::cha::ChaKind;
|
|||
// --- Attrs
|
||||
pub(crate) struct Attrs(pub(crate) yazi_fs::provider::Attrs);
|
||||
|
||||
impl From<Attrs> for yazi_sftp::fs::Attrs {
|
||||
fn from(attrs: Attrs) -> Self {
|
||||
Self {
|
||||
impl TryFrom<Attrs> for yazi_sftp::fs::Attrs {
|
||||
type Error = ();
|
||||
|
||||
fn try_from(value: Attrs) -> Result<Self, Self::Error> {
|
||||
let attrs = Self {
|
||||
size: None,
|
||||
uid: None,
|
||||
gid: None,
|
||||
perm: attrs.0.mode.map(|m| m.bits() as u32),
|
||||
atime: attrs.0.atime_dur().map(|d| d.as_secs() as u32),
|
||||
mtime: attrs.0.mtime_dur().map(|d| d.as_secs() as u32),
|
||||
perm: value.0.mode.map(|m| m.bits() as u32),
|
||||
atime: value.0.atime_dur().map(|d| d.as_secs() as u32),
|
||||
mtime: value.0.mtime_dur().map(|d| d.as_secs() as u32),
|
||||
extended: Default::default(),
|
||||
}
|
||||
};
|
||||
|
||||
if attrs.is_empty() { Err(()) } else { Ok(attrs) }
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -78,7 +78,7 @@ impl<'a> Provider for Sftp<'a> {
|
|||
P: AsPath,
|
||||
{
|
||||
let to = to.as_path().as_unix()?;
|
||||
let attrs = Attrs::from(super::Attrs(attrs));
|
||||
let attrs = super::Attrs(attrs).try_into().unwrap_or_default();
|
||||
|
||||
let op = self.op().await?;
|
||||
let from = op.open(self.path, Flags::READ, &Attrs::default()).await?;
|
||||
|
|
@ -89,7 +89,10 @@ impl<'a> Provider for Sftp<'a> {
|
|||
let written = tokio::io::copy(&mut reader, &mut writer).await?;
|
||||
|
||||
writer.flush().await?;
|
||||
writer.get_ref().fsetstat(&attrs).await.ok();
|
||||
if !attrs.is_empty() {
|
||||
writer.get_ref().fsetstat(&attrs).await.ok();
|
||||
}
|
||||
|
||||
writer.shutdown().await.ok();
|
||||
Ok(written)
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue