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). For breaking changes, see [Migrating to Yazi v0.4.0](https://github.com/sxyazi/yazi/issues/1772).
<br><br>
<div align="center"> <div align="center">
<img src="assets/logo.png" alt="Yazi logo" width="20%"> <img src="assets/logo.png" alt="Yazi logo" width="20%">
</div> </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 | | [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 | | [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 | | [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 | | 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 | | 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 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); || realname_unchecked(u, &mut cached).await.is_ok_and(|s| urn.as_urn() == s);
if !eq { if !eq {

View file

@ -111,7 +111,7 @@ pub fn install(lua: &Lua) -> mlua::Result<()> {
( (
"unique_name", "unique_name",
lua.create_async_function(|lua, url: UrlRef| async move { 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), Ok(u) => (Url::cast(lua, u)?, Value::Nil).into_lua_multi(lua),
Err(e) => (Value::Nil, e.raw_os_error()).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; let id = task.id;
self.prog.send(TaskProg::New(id, cha.len))?; 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?; self.queue(FileOp::Link(task.into()), NORMAL).await?;
} else { } else {
self.queue(FileOp::Paste(task), LOW).await?; self.queue(FileOp::Paste(task), LOW).await?;
@ -208,7 +208,7 @@ impl File {
let to = dest.join(from.file_name().unwrap()); let to = dest.join(from.file_name().unwrap());
self.prog.send(TaskProg::New(task.id, cha.len))?; 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?; self.queue(FileOp::Link(task.spawn(from, to, cha).into()), NORMAL).await?;
} else { } else {
self.queue(FileOp::Paste(task.spawn(from, to, cha)), LOW).await?; 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_config::{TASKS, open::Opener, plugin::{Fetcher, Preloader}};
use yazi_dds::Pump; use yazi_dds::Pump;
use yazi_proxy::ManagerProxy; 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 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}}; 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(); let file = self.file.clone();
self.send_micro(id, LOW, async move { self.send_micro(id, LOW, async move {
if !force { 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 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(); let file = self.file.clone();
self.send_micro(id, LOW, async move { self.send_micro(id, LOW, async move {
if !force { 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 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(); let file = self.file.clone();
self.send_micro(id, LOW, async move { self.send_micro(id, LOW, async move {
if !force { if !force {
to = unique_name(to).await?; to = unique_name(to, must_be_dir(&from)).await?;
} }
file file
.link(FileOpLink { id, from, to, cha: None, resolve: false, relative, delete: false }) .link(FileOpLink { id, from, to, cha: None, resolve: false, relative, delete: false })
@ -145,7 +145,7 @@ impl Scheduler {
let file = self.file.clone(); let file = self.file.clone();
self.send_micro(id, LOW, async move { self.send_micro(id, LOW, async move {
if !force { 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 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(); let mut kind = ChaKind::empty();
if m.is_dir() { if m.is_dir() {
kind |= ChaKind::DIR; kind |= ChaKind::DIR;
} else if m.is_symlink() {
kind |= ChaKind::LINK;
} }
Self { Self {
@ -91,7 +93,7 @@ impl From<FileType> for Cha {
kind |= ChaKind::DIR; kind |= ChaKind::DIR;
libc::S_IFDIR libc::S_IFDIR
} else if t.is_symlink() { } else if t.is_symlink() {
kind |= ChaKind::ORPHAN; kind |= ChaKind::LINK;
libc::S_IFLNK libc::S_IFLNK
} else if t.is_block_device() { } else if t.is_block_device() {
libc::S_IFBLK libc::S_IFBLK
@ -130,8 +132,11 @@ impl Cha {
let mut attached = ChaKind::empty(); let mut attached = ChaKind::empty();
if meta.is_symlink() { if meta.is_symlink() {
attached |= ChaKind::LINK;
meta = tokio::fs::metadata(path).await.unwrap_or(meta); 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); let mut cha = Self::new_nofollow(path, meta);
@ -187,9 +192,6 @@ impl Cha {
#[inline] #[inline]
pub const fn is_orphan(&self) -> bool { self.kind.contains(ChaKind::ORPHAN) } 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] #[inline]
pub const fn is_dummy(&self) -> bool { self.kind.contains(ChaKind::DUMMY) } 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] #[inline]
pub fn ok_or_not_found(result: io::Result<()>) -> io::Result<()> { pub fn ok_or_not_found(result: io::Result<()>) -> io::Result<()> {
match 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; 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 { let Some(stem) = u.file_stem().map(|s| s.to_owned()) else {
return Err(io::Error::new(io::ErrorKind::InvalidInput, "empty file stem")); return Err(io::Error::new(io::ErrorKind::InvalidInput, "empty file stem"));
}; };
let ext = u let dot_ext = u.extension().map_or_else(OsString::new, |e| {
.extension() let mut s = OsString::with_capacity(e.len() + 1);
.map(|s| { s.push(".");
let mut n = OsString::with_capacity(s.len() + 1); s.push(e);
n.push("."); s
n.push(s); });
n
})
.unwrap_or_default();
let mut i = 1u64; let mut i = 1u64;
let mut p = u.to_path(); let mut p = u.to_path();
loop { 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 { match fs::symlink_metadata(&p).await {
Ok(_) => {} Ok(_) => i += 1,
Err(e) if e.kind() == io::ErrorKind::NotFound => break, Err(e) if e.kind() == io::ErrorKind::NotFound => break,
Err(e) => return Err(e), 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)); u.set_loc(Loc::from(u.base(), p));