feat: append the suffix to the end when generating unique filenames for directories, i.e., after not before the extension (#1784)

Co-authored-by: sxyazi <sxyazi@gmail.com>
This commit is contained in:
Sarveshwaar SS 2024-10-15 22:14:27 +05:30 committed by GitHub
parent 097e5abb82
commit 17a1169ea2
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
8 changed files with 58 additions and 36 deletions

View file

@ -2,6 +2,8 @@
For breaking changes, see [Migrating to Yazi v0.4.0](https://github.com/sxyazi/yazi/issues/1772).
<br><br>
<div align="center">
<img src="assets/logo.png" alt="Yazi logo" width="20%">
</div>
@ -56,7 +58,7 @@ https://github.com/sxyazi/yazi/assets/17523360/92ff23fa-0cd5-4f04-b387-894c12265
| [VSCode](https://github.com/microsoft/vscode) | [Inline images protocol](https://iterm2.com/documentation-images.html) | ✅ Built-in |
| [Tabby](https://github.com/Eugeny/tabby) | [Inline images protocol](https://iterm2.com/documentation-images.html) | ✅ Built-in |
| [Hyper](https://github.com/vercel/hyper) | [Inline images protocol](https://iterm2.com/documentation-images.html) | ✅ Built-in |
| [Rio](https://github.com/raphamorim/rio) | [Inline images protocol](https://iterm2.com/documentation-images.html) | ✅ Built-in |
| [Rio](https://github.com/raphamorim/rio) | [Inline images protocol](https://iterm2.com/documentation-images.html) | ❌ Rio doesn't correctly clear images (#1786) |
| X11 / Wayland | Window system protocol | ☑️ [Überzug++](https://github.com/jstkdng/ueberzugpp) required |
| Fallback | [ASCII art (Unicode block)](https://en.wikipedia.org/wiki/ASCII_art) | ☑️ [Chafa](https://hpjansson.org/chafa/) required |

View file

@ -136,7 +136,7 @@ impl Watcher {
};
let u = &file.url;
let eq = (!file.is_linkable() && fs::canonicalize(u).await.is_ok_and(|p| p == ***u))
let eq = (!file.is_link() && fs::canonicalize(u).await.is_ok_and(|p| p == ***u))
|| realname_unchecked(u, &mut cached).await.is_ok_and(|s| urn.as_urn() == s);
if !eq {

View file

@ -111,7 +111,7 @@ pub fn install(lua: &Lua) -> mlua::Result<()> {
(
"unique_name",
lua.create_async_function(|lua, url: UrlRef| async move {
match yazi_shared::fs::unique_name(url.clone()).await {
match yazi_shared::fs::unique_name(url.clone(), async { false }).await {
Ok(u) => (Url::cast(lua, u)?, Value::Nil).into_lua_multi(lua),
Err(e) => (Value::Nil, e.raw_os_error()).into_lua_multi(lua),
}

View file

@ -163,7 +163,7 @@ impl File {
let id = task.id;
self.prog.send(TaskProg::New(id, cha.len))?;
if cha.is_linkable() {
if cha.is_orphan() || (cha.is_link() && !task.follow) {
self.queue(FileOp::Link(task.into()), NORMAL).await?;
} else {
self.queue(FileOp::Paste(task), LOW).await?;
@ -208,7 +208,7 @@ impl File {
let to = dest.join(from.file_name().unwrap());
self.prog.send(TaskProg::New(task.id, cha.len))?;
if cha.is_linkable() {
if cha.is_orphan() || (cha.is_link() && !task.follow) {
self.queue(FileOp::Link(task.spawn(from, to, cha).into()), NORMAL).await?;
} else {
self.queue(FileOp::Paste(task.spawn(from, to, cha)), LOW).await?;

View file

@ -7,7 +7,7 @@ use tokio::{fs, select, sync::{mpsc::{self, UnboundedReceiver}, oneshot}, task::
use yazi_config::{TASKS, open::Opener, plugin::{Fetcher, Preloader}};
use yazi_dds::Pump;
use yazi_proxy::ManagerProxy;
use yazi_shared::{Throttle, event::Data, fs::{Url, remove_dir_clean, unique_name}};
use yazi_shared::{Throttle, event::Data, fs::{Url, must_be_dir, remove_dir_clean, unique_name}};
use super::{Ongoing, TaskProg, TaskStage};
use crate::{HIGH, LOW, NORMAL, TaskKind, TaskOp, file::{File, FileOpDelete, FileOpHardlink, FileOpLink, FileOpPaste, FileOpTrash}, plugin::{Plugin, PluginOpEntry}, prework::{Prework, PreworkOpFetch, PreworkOpLoad, PreworkOpSize}, process::{Process, ProcessOpBg, ProcessOpBlock, ProcessOpOrphan}};
@ -97,7 +97,7 @@ impl Scheduler {
let file = self.file.clone();
self.send_micro(id, LOW, async move {
if !force {
to = unique_name(to).await?;
to = unique_name(to, must_be_dir(&from)).await?;
}
file.paste(FileOpPaste { id, from, to, cha: None, cut: true, follow: false, retry: 0 }).await
});
@ -114,7 +114,7 @@ impl Scheduler {
let file = self.file.clone();
self.send_micro(id, LOW, async move {
if !force {
to = unique_name(to).await?;
to = unique_name(to, must_be_dir(&from)).await?;
}
file.paste(FileOpPaste { id, from, to, cha: None, cut: false, follow, retry: 0 }).await
});
@ -126,7 +126,7 @@ impl Scheduler {
let file = self.file.clone();
self.send_micro(id, LOW, async move {
if !force {
to = unique_name(to).await?;
to = unique_name(to, must_be_dir(&from)).await?;
}
file
.link(FileOpLink { id, from, to, cha: None, resolve: false, relative, delete: false })
@ -145,7 +145,7 @@ impl Scheduler {
let file = self.file.clone();
self.send_micro(id, LOW, async move {
if !force {
to = unique_name(to).await?;
to = unique_name(to, must_be_dir(&from)).await?;
}
file.hardlink(FileOpHardlink { id, from, to, cha: None, follow }).await
});

View file

@ -42,6 +42,8 @@ impl From<Metadata> for Cha {
let mut kind = ChaKind::empty();
if m.is_dir() {
kind |= ChaKind::DIR;
} else if m.is_symlink() {
kind |= ChaKind::LINK;
}
Self {
@ -91,7 +93,7 @@ impl From<FileType> for Cha {
kind |= ChaKind::DIR;
libc::S_IFDIR
} else if t.is_symlink() {
kind |= ChaKind::ORPHAN;
kind |= ChaKind::LINK;
libc::S_IFLNK
} else if t.is_block_device() {
libc::S_IFBLK
@ -130,8 +132,11 @@ impl Cha {
let mut attached = ChaKind::empty();
if meta.is_symlink() {
attached |= ChaKind::LINK;
meta = tokio::fs::metadata(path).await.unwrap_or(meta);
attached |= if meta.is_symlink() { ChaKind::ORPHAN } else { ChaKind::LINK };
}
if meta.is_symlink() {
attached |= ChaKind::ORPHAN;
}
let mut cha = Self::new_nofollow(path, meta);
@ -187,9 +192,6 @@ impl Cha {
#[inline]
pub const fn is_orphan(&self) -> bool { self.kind.contains(ChaKind::ORPHAN) }
#[inline]
pub const fn is_linkable(&self) -> bool { self.is_link() || self.is_orphan() }
#[inline]
pub const fn is_dummy(&self) -> bool { self.kind.contains(ChaKind::DUMMY) }

View file

@ -16,6 +16,11 @@ pub async fn maybe_exists(p: impl AsRef<Path>) -> bool {
}
}
#[inline]
pub async fn must_be_dir(p: impl AsRef<Path>) -> bool {
fs::metadata(p).await.map_or(false, |m| m.is_dir())
}
#[inline]
pub fn ok_or_not_found(result: io::Result<()>) -> io::Result<()> {
match result {

View file

@ -1,4 +1,4 @@
use std::{borrow::Cow, env, ffi::OsString, io, path::{Component, Path, PathBuf}};
use std::{borrow::Cow, env, ffi::OsString, future::Future, io, path::{Component, Path, PathBuf}};
use tokio::fs;
@ -74,38 +74,51 @@ fn _expand_path(p: &Path) -> PathBuf {
}
}
pub async fn unique_name(mut u: Url) -> io::Result<Url> {
pub async fn unique_name<F>(u: Url, append: F) -> io::Result<Url>
where
F: Future<Output = bool>,
{
match fs::symlink_metadata(&u).await {
Ok(_) => _unique_name(u, append.await).await,
Err(e) if e.kind() == io::ErrorKind::NotFound => Ok(u),
Err(e) => Err(e),
}
}
async fn _unique_name(mut u: Url, append: bool) -> io::Result<Url> {
let Some(stem) = u.file_stem().map(|s| s.to_owned()) else {
return Err(io::Error::new(io::ErrorKind::InvalidInput, "empty file stem"));
};
let ext = u
.extension()
.map(|s| {
let mut n = OsString::with_capacity(s.len() + 1);
n.push(".");
n.push(s);
n
})
.unwrap_or_default();
let dot_ext = u.extension().map_or_else(OsString::new, |e| {
let mut s = OsString::with_capacity(e.len() + 1);
s.push(".");
s.push(e);
s
});
let mut i = 1u64;
let mut p = u.to_path();
loop {
let mut name = OsString::with_capacity(stem.len() + dot_ext.len() + 5);
name.push(&stem);
if append {
name.push(&dot_ext);
name.push("_");
name.push(i.to_string());
} else {
name.push("_");
name.push(i.to_string());
name.push(&dot_ext);
}
p.set_file_name(name);
match fs::symlink_metadata(&p).await {
Ok(_) => {}
Ok(_) => i += 1,
Err(e) if e.kind() == io::ErrorKind::NotFound => break,
Err(e) => return Err(e),
}
let mut name = OsString::with_capacity(stem.len() + ext.len() + 5);
name.push(&stem);
name.push("_");
name.push(i.to_string());
name.push(&ext);
p.set_file_name(name);
i += 1;
}
u.set_loc(Loc::from(u.base(), p));