mirror of
https://github.com/sxyazi/yazi.git
synced 2024-09-11 10:26:35 +03:00
fix: different filenames should be treated as the same file on case-insensitive file systems (#1151)
This commit is contained in:
parent
189cb81db3
commit
794694e2d6
28
Cargo.lock
generated
28
Cargo.lock
generated
@ -293,9 +293,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "clap"
|
||||
version = "4.5.4"
|
||||
version = "4.5.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "90bc066a67923782aa8515dbaea16946c5bcc5addbd668bb80af688e53e548a0"
|
||||
checksum = "5db83dced34638ad474f39f250d7fea9598bdd239eaced1bdf45d597da0f433f"
|
||||
dependencies = [
|
||||
"clap_builder",
|
||||
"clap_derive",
|
||||
@ -303,9 +303,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "clap_builder"
|
||||
version = "4.5.2"
|
||||
version = "4.5.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ae129e2e766ae0ec03484e609954119f123cc1fe650337e155d03b022f24f7b4"
|
||||
checksum = "f7e204572485eb3fbf28f871612191521df159bc3e15a9f5064c66dba3a8c05f"
|
||||
dependencies = [
|
||||
"anstream",
|
||||
"anstyle",
|
||||
@ -315,18 +315,18 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "clap_complete"
|
||||
version = "4.5.2"
|
||||
version = "4.5.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "dd79504325bf38b10165b02e89b4347300f855f273c4cb30c4a3209e6583275e"
|
||||
checksum = "d2020fa13af48afc65a9a87335bda648309ab3d154cd03c7ff95b378c7ed39c4"
|
||||
dependencies = [
|
||||
"clap",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "clap_complete_fig"
|
||||
version = "4.5.0"
|
||||
version = "4.5.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "54b3e65f91fabdd23cac3d57d39d5d938b4daabd070c335c006dccb866a61110"
|
||||
checksum = "fb4bc503cddc1cd320736fb555d6598309ad07c2ddeaa23891a10ffb759ee612"
|
||||
dependencies = [
|
||||
"clap",
|
||||
"clap_complete",
|
||||
@ -334,9 +334,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "clap_complete_nushell"
|
||||
version = "4.5.1"
|
||||
version = "4.5.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "80d0e48e026ce7df2040239117d25e4e79714907420c70294a5ce4b6bbe6a7b6"
|
||||
checksum = "1accf1b463dee0d3ab2be72591dccdab8bef314958340447c882c4c72acfe2a3"
|
||||
dependencies = [
|
||||
"clap",
|
||||
"clap_complete",
|
||||
@ -344,9 +344,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "clap_derive"
|
||||
version = "4.5.4"
|
||||
version = "4.5.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "528131438037fd55894f62d6e9f068b8f45ac57ffa77517819645d10aed04f64"
|
||||
checksum = "c780290ccf4fb26629baa7a1081e68ced113f1d3ec302fa5948f1c381ebf06c6"
|
||||
dependencies = [
|
||||
"heck 0.5.0",
|
||||
"proc-macro2",
|
||||
@ -1617,9 +1617,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "regex"
|
||||
version = "1.10.4"
|
||||
version = "1.10.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c117dbdfde9c8308975b6a18d71f3f385c89461f7b3fb054288ecf2a2058ba4c"
|
||||
checksum = "b91213439dad192326a0d7c6ee3955910425f441d7038e0d6933b0aec5c4517f"
|
||||
dependencies = [
|
||||
"aho-corasick",
|
||||
"memchr",
|
||||
|
@ -1 +1 @@
|
||||
{"version":"0.2","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"],"language":"en","flagWords":[]}
|
||||
{"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"]}
|
@ -9,18 +9,18 @@ homepage = "https://yazi-rs.github.io"
|
||||
repository = "https://github.com/sxyazi/yazi"
|
||||
|
||||
[dependencies]
|
||||
regex = "1.10.4"
|
||||
regex = "1.10.5"
|
||||
yazi-adapter = { path = "../yazi-adapter", version = "0.2.5" }
|
||||
yazi-config = { path = "../yazi-config", version = "0.2.5" }
|
||||
yazi-shared = { path = "../yazi-shared", version = "0.2.5" }
|
||||
|
||||
# External dependencies
|
||||
clap = { version = "4.5.4", features = [ "derive" ] }
|
||||
clap = { version = "4.5.7", features = [ "derive" ] }
|
||||
serde = { version = "1.0.203", features = [ "derive" ] }
|
||||
|
||||
[build-dependencies]
|
||||
clap = { version = "4.5.4", features = [ "derive" ] }
|
||||
clap_complete = "4.5.2"
|
||||
clap_complete_nushell = "4.5.1"
|
||||
clap_complete_fig = "4.5.0"
|
||||
clap = { version = "4.5.7", features = [ "derive" ] }
|
||||
clap_complete = "4.5.5"
|
||||
clap_complete_nushell = "4.5.2"
|
||||
clap_complete_fig = "4.5.1"
|
||||
vergen = { version = "8.3.1", features = [ "build", "git", "gitcl" ] }
|
||||
|
@ -14,7 +14,7 @@ yazi-shared = { path = "../yazi-shared", version = "0.2.5" }
|
||||
|
||||
# External dependencies
|
||||
anyhow = "1.0.86"
|
||||
clap = { version = "4.5.4", features = [ "derive" ] }
|
||||
clap = { version = "4.5.7", features = [ "derive" ] }
|
||||
crossterm = "0.27.0"
|
||||
md-5 = "0.10.6"
|
||||
serde_json = "1.0.117"
|
||||
@ -23,10 +23,10 @@ toml_edit = "0.22.14"
|
||||
|
||||
[build-dependencies]
|
||||
anyhow = "1.0.86"
|
||||
clap = { version = "4.5.4", features = [ "derive" ] }
|
||||
clap_complete = "4.5.2"
|
||||
clap_complete_fig = "4.5.0"
|
||||
clap_complete_nushell = "4.5.1"
|
||||
clap = { version = "4.5.7", features = [ "derive" ] }
|
||||
clap_complete = "4.5.5"
|
||||
clap_complete_fig = "4.5.1"
|
||||
clap_complete_nushell = "4.5.2"
|
||||
serde_json = "1.0.117"
|
||||
vergen = { version = "8.3.1", features = [ "build", "git", "gitcl" ] }
|
||||
|
||||
|
@ -27,7 +27,7 @@ futures = "0.3.30"
|
||||
notify = { version = "6.1.1", default-features = false, features = [ "macos_fsevent" ] }
|
||||
parking_lot = "0.12.3"
|
||||
ratatui = "0.26.3"
|
||||
regex = "1.10.4"
|
||||
regex = "1.10.5"
|
||||
scopeguard = "1.2.0"
|
||||
serde = "1.0.203"
|
||||
shell-words = "1.1.0"
|
||||
|
@ -1,9 +1,10 @@
|
||||
use std::path::PathBuf;
|
||||
use std::collections::HashMap;
|
||||
|
||||
use anyhow::Result;
|
||||
use tokio::fs;
|
||||
use yazi_config::popup::InputCfg;
|
||||
use yazi_proxy::{InputProxy, ManagerProxy};
|
||||
use yazi_shared::{event::Cmd, fs::{maybe_exists, File, FilesOp, Url}};
|
||||
use yazi_proxy::{InputProxy, TabProxy, WATCHER};
|
||||
use yazi_shared::{event::Cmd, fs::{maybe_exists, ok_or_not_found, symlink_realpath, File, FilesOp, Url}};
|
||||
|
||||
use crate::manager::Manager;
|
||||
|
||||
@ -24,29 +25,42 @@ impl Manager {
|
||||
let Some(Ok(name)) = result.recv().await else {
|
||||
return Ok(());
|
||||
};
|
||||
if name.is_empty() {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
let path = cwd.join(&name);
|
||||
if !opt.force && maybe_exists(&path).await {
|
||||
let new = cwd.join(&name);
|
||||
if !opt.force && maybe_exists(&new).await {
|
||||
match InputProxy::show(InputCfg::overwrite()).recv().await {
|
||||
Some(Ok(c)) if c == "y" || c == "Y" => (),
|
||||
_ => return Ok(()),
|
||||
}
|
||||
}
|
||||
|
||||
if name.ends_with('/') || name.ends_with('\\') {
|
||||
fs::create_dir_all(&path).await?;
|
||||
} else {
|
||||
fs::create_dir_all(&path.parent().unwrap()).await.ok();
|
||||
fs::File::create(&path).await?;
|
||||
}
|
||||
|
||||
let child =
|
||||
Url::from(path.components().take(cwd.components().count() + 1).collect::<PathBuf>());
|
||||
if let Ok(f) = File::from(child.clone()).await {
|
||||
FilesOp::Creating(cwd, vec![f]).emit();
|
||||
ManagerProxy::hover(Some(child));
|
||||
}
|
||||
Ok::<(), anyhow::Error>(())
|
||||
Self::create_do(new, name.ends_with('/') || name.ends_with('\\')).await
|
||||
});
|
||||
}
|
||||
|
||||
async fn create_do(new: Url, dir: bool) -> Result<()> {
|
||||
let Some(parent) = new.parent_url() else { return Ok(()) };
|
||||
let _permit = WATCHER.acquire().await.unwrap();
|
||||
|
||||
if dir {
|
||||
fs::create_dir_all(&new).await?;
|
||||
} else if let Ok(real) = symlink_realpath(&new).await {
|
||||
ok_or_not_found(fs::remove_file(&new).await)?;
|
||||
FilesOp::Deleting(parent.clone(), vec![Url::from(real)]).emit();
|
||||
fs::File::create(&new).await?;
|
||||
} else {
|
||||
fs::create_dir_all(&parent).await.ok();
|
||||
ok_or_not_found(fs::remove_file(&new).await)?;
|
||||
fs::File::create(&new).await?;
|
||||
}
|
||||
|
||||
if let Ok(f) = File::from(new.clone()).await {
|
||||
FilesOp::Upserting(parent, HashMap::from_iter([(f.url(), f)])).emit();
|
||||
TabProxy::reveal(&new)
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
@ -4,8 +4,8 @@ use anyhow::Result;
|
||||
use tokio::fs;
|
||||
use yazi_config::popup::InputCfg;
|
||||
use yazi_dds::Pubsub;
|
||||
use yazi_proxy::{InputProxy, ManagerProxy, WATCHER};
|
||||
use yazi_shared::{event::Cmd, fs::{maybe_exists, File, FilesOp, Url}};
|
||||
use yazi_proxy::{InputProxy, TabProxy, WATCHER};
|
||||
use yazi_shared::{event::Cmd, fs::{maybe_exists, ok_or_not_found, symlink_realpath, File, FilesOp, Url}};
|
||||
|
||||
use crate::manager::Manager;
|
||||
|
||||
@ -77,19 +77,23 @@ impl Manager {
|
||||
}
|
||||
|
||||
async fn rename_do(tab: usize, old: Url, new: Url) -> Result<()> {
|
||||
let Some(p_old) = old.parent_url() else { return Ok(()) };
|
||||
let Some(p_new) = new.parent_url() else { return Ok(()) };
|
||||
let _permit = WATCHER.acquire().await.unwrap();
|
||||
|
||||
let overwritten = symlink_realpath(&new).await;
|
||||
fs::rename(&old, &new).await?;
|
||||
if old.parent() != new.parent() {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
let file = File::from(new.clone()).await?;
|
||||
if let Ok(p) = overwritten {
|
||||
ok_or_not_found(fs::rename(&p, &new).await)?;
|
||||
FilesOp::Deleting(p_new.clone(), vec![Url::from(p)]).emit();
|
||||
}
|
||||
Pubsub::pub_from_rename(tab, &old, &new);
|
||||
|
||||
FilesOp::Deleting(file.parent().unwrap(), vec![new.clone()]).emit();
|
||||
FilesOp::Upserting(file.parent().unwrap(), HashMap::from_iter([(old, file)])).emit();
|
||||
Ok(ManagerProxy::hover(Some(new)))
|
||||
let file = File::from(new.clone()).await?;
|
||||
FilesOp::Deleting(p_old, vec![old]).emit();
|
||||
FilesOp::Upserting(p_new, HashMap::from_iter([(new.clone(), file)])).emit();
|
||||
Ok(TabProxy::reveal(&new))
|
||||
}
|
||||
|
||||
fn empty_url_part(url: &Url, by: &str) -> String {
|
||||
|
@ -1,4 +1,4 @@
|
||||
use std::{collections::{HashMap, HashSet}, time::{Duration, SystemTime}};
|
||||
use std::{borrow::Cow, collections::{HashMap, HashSet}, time::{Duration, SystemTime}};
|
||||
|
||||
use anyhow::Result;
|
||||
use notify::{RecommendedWatcher, RecursiveMode, Watcher as _Watcher};
|
||||
@ -8,7 +8,7 @@ use tokio_stream::{wrappers::UnboundedReceiverStream, StreamExt};
|
||||
use tracing::error;
|
||||
use yazi_plugin::isolate;
|
||||
use yazi_proxy::WATCHER;
|
||||
use yazi_shared::{fs::{File, FilesOp, Url}, RoCell};
|
||||
use yazi_shared::{fs::{symlink_realpath_with, File, FilesOp, Url}, RoCell};
|
||||
|
||||
use super::Linked;
|
||||
use crate::folder::{Files, Folder};
|
||||
@ -35,8 +35,8 @@ impl Watcher {
|
||||
Default::default(),
|
||||
);
|
||||
|
||||
tokio::spawn(Self::on_in(in_rx, watcher.unwrap()));
|
||||
tokio::spawn(Self::on_out(out_rx));
|
||||
tokio::spawn(Self::fan_in(in_rx, watcher.unwrap()));
|
||||
tokio::spawn(Self::fan_out(out_rx));
|
||||
Self { tx: in_tx }
|
||||
}
|
||||
|
||||
@ -65,7 +65,7 @@ impl Watcher {
|
||||
});
|
||||
}
|
||||
|
||||
async fn on_in(mut rx: watch::Receiver<HashSet<Url>>, mut watcher: RecommendedWatcher) {
|
||||
async fn fan_in(mut rx: watch::Receiver<HashSet<Url>>, mut watcher: RecommendedWatcher) {
|
||||
loop {
|
||||
let (mut to_unwatch, mut to_watch): (HashSet<_>, HashSet<_>) = {
|
||||
let (new, old) = (&*rx.borrow_and_update(), &*WATCHED.read());
|
||||
@ -91,27 +91,39 @@ impl Watcher {
|
||||
}
|
||||
}
|
||||
|
||||
async fn on_out(rx: UnboundedReceiver<Url>) {
|
||||
async fn fan_out(rx: UnboundedReceiver<Url>) {
|
||||
// TODO: revert this once a new notification is implemented
|
||||
let rx = UnboundedReceiverStream::new(rx).chunks_timeout(1000, Duration::from_millis(50));
|
||||
pin!(rx);
|
||||
|
||||
while let Some(urls) = rx.next().await {
|
||||
while let Some(chunk) = rx.next().await {
|
||||
let urls: HashSet<_> = chunk.into_iter().collect();
|
||||
let mut cached: HashMap<_, _> = HashMap::new();
|
||||
|
||||
let _permit = WATCHER.acquire().await.unwrap();
|
||||
let mut reload = Vec::with_capacity(urls.len());
|
||||
|
||||
for u in urls.into_iter().collect::<HashSet<_>>() {
|
||||
let Some(parent) = u.parent_url() else { continue };
|
||||
|
||||
let Ok(file) = File::from(u.clone()).await else {
|
||||
FilesOp::Deleting(parent, vec![u]).emit();
|
||||
for url in urls {
|
||||
let Some(parent) = url.parent_url() else { continue };
|
||||
let Ok(file) = File::from(url.clone()).await else {
|
||||
FilesOp::Deleting(parent, vec![url]).emit();
|
||||
continue;
|
||||
};
|
||||
|
||||
let real = if file.is_link() {
|
||||
symlink_realpath_with(&url, &mut cached).await
|
||||
} else {
|
||||
fs::canonicalize(&url).await.map(Cow::Owned)
|
||||
};
|
||||
if !real.is_ok_and(|p| p == *url) {
|
||||
FilesOp::Deleting(parent, vec![url]).emit();
|
||||
continue;
|
||||
}
|
||||
|
||||
if !file.is_dir() {
|
||||
reload.push(file.clone());
|
||||
}
|
||||
FilesOp::Upserting(parent, HashMap::from_iter([(u, file)])).emit();
|
||||
FilesOp::Upserting(parent, HashMap::from_iter([(url, file)])).emit();
|
||||
}
|
||||
|
||||
if reload.is_empty() {
|
||||
|
@ -63,11 +63,15 @@ impl Body<'static> {
|
||||
if matches!(
|
||||
kind,
|
||||
"hi"
|
||||
| "hey" | "bye"
|
||||
| "cd" | "hover"
|
||||
| "hey"
|
||||
| "bye"
|
||||
| "cd"
|
||||
| "hover"
|
||||
| "rename"
|
||||
| "bulk" | "yank"
|
||||
| "move" | "trash"
|
||||
| "bulk"
|
||||
| "yank"
|
||||
| "move"
|
||||
| "trash"
|
||||
| "delete"
|
||||
) {
|
||||
bail!("Cannot construct system event");
|
||||
|
@ -5,7 +5,7 @@ use futures::{future::BoxFuture, FutureExt};
|
||||
use tokio::{fs, io::{self, ErrorKind::{AlreadyExists, NotFound}}, sync::mpsc};
|
||||
use tracing::warn;
|
||||
use yazi_config::TASKS;
|
||||
use yazi_shared::fs::{calculate_size, copy_with_progress, maybe_exists, path_relative_to, Url};
|
||||
use yazi_shared::fs::{calculate_size, copy_with_progress, maybe_exists, ok_or_not_found, path_relative_to, Url};
|
||||
|
||||
use super::{FileOp, FileOpDelete, FileOpLink, FileOpPaste, FileOpTrash};
|
||||
use crate::{TaskOp, TaskProg, LOW, NORMAL};
|
||||
@ -26,12 +26,9 @@ impl File {
|
||||
pub async fn work(&self, op: FileOp) -> Result<()> {
|
||||
match op {
|
||||
FileOp::Paste(mut task) => {
|
||||
match fs::remove_file(&task.to).await {
|
||||
Err(e) if e.kind() != NotFound => Err(e)?,
|
||||
_ => {}
|
||||
}
|
||||
|
||||
ok_or_not_found(fs::remove_file(&task.to).await)?;
|
||||
let mut it = copy_with_progress(&task.from, &task.to, task.meta.as_ref().unwrap());
|
||||
|
||||
while let Some(res) = it.recv().await {
|
||||
match res {
|
||||
Ok(0) => {
|
||||
@ -83,21 +80,17 @@ impl File {
|
||||
src
|
||||
};
|
||||
|
||||
match fs::remove_file(&task.to).await {
|
||||
Err(e) if e.kind() != NotFound => Err(e)?,
|
||||
_ => {
|
||||
#[cfg(unix)]
|
||||
{
|
||||
fs::symlink(src, &task.to).await?
|
||||
}
|
||||
#[cfg(windows)]
|
||||
{
|
||||
if meta.is_dir() {
|
||||
fs::symlink_dir(src, &task.to).await?
|
||||
} else {
|
||||
fs::symlink_file(src, &task.to).await?
|
||||
}
|
||||
}
|
||||
ok_or_not_found(fs::remove_file(&task.to).await)?;
|
||||
#[cfg(unix)]
|
||||
{
|
||||
fs::symlink(src, &task.to).await?
|
||||
}
|
||||
#[cfg(windows)]
|
||||
{
|
||||
if meta.is_dir() {
|
||||
fs::symlink_dir(src, &task.to).await?
|
||||
} else {
|
||||
fs::symlink_file(src, &task.to).await?
|
||||
}
|
||||
}
|
||||
|
||||
@ -134,12 +127,8 @@ impl File {
|
||||
}
|
||||
|
||||
pub async fn paste(&self, mut task: FileOpPaste) -> Result<()> {
|
||||
if task.cut {
|
||||
match fs::rename(&task.from, &task.to).await {
|
||||
Ok(_) => return self.succ(task.id),
|
||||
Err(e) if e.kind() == NotFound => return self.succ(task.id),
|
||||
_ => {}
|
||||
}
|
||||
if task.cut && ok_or_not_found(fs::rename(&task.from, &task.to).await).is_ok() {
|
||||
return self.succ(task.id);
|
||||
}
|
||||
|
||||
if task.meta.is_none() {
|
||||
|
@ -19,7 +19,7 @@ futures = "0.3.30"
|
||||
parking_lot = "0.12.3"
|
||||
percent-encoding = "2.3.1"
|
||||
ratatui = "0.26.3"
|
||||
regex = "1.10.4"
|
||||
regex = "1.10.5"
|
||||
serde = { version = "1.0.203", features = [ "derive" ] }
|
||||
shell-words = "1.1.0"
|
||||
tokio = { version = "1.38.0", features = [ "full" ] }
|
||||
|
@ -1,11 +1,13 @@
|
||||
use std::{collections::VecDeque, fs::Metadata, path::{Path, PathBuf}};
|
||||
use std::{borrow::Cow, collections::{HashMap, VecDeque}, fs::Metadata, path::{Path, PathBuf}};
|
||||
|
||||
use anyhow::Result;
|
||||
use filetime::{set_file_mtime, FileTime};
|
||||
use tokio::{fs, io, select, sync::{mpsc, oneshot}, time};
|
||||
|
||||
#[inline]
|
||||
pub async fn must_exists(p: impl AsRef<Path>) -> bool { fs::symlink_metadata(p).await.is_ok() }
|
||||
|
||||
#[inline]
|
||||
pub async fn maybe_exists(p: impl AsRef<Path>) -> bool {
|
||||
match fs::symlink_metadata(p).await {
|
||||
Ok(_) => true,
|
||||
@ -13,6 +15,61 @@ pub async fn maybe_exists(p: impl AsRef<Path>) -> bool {
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn ok_or_not_found(result: io::Result<()>) -> io::Result<()> {
|
||||
match result {
|
||||
Ok(()) => Ok(()),
|
||||
Err(e) if e.kind() == io::ErrorKind::NotFound => Ok(()),
|
||||
Err(_) => result,
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub async fn symlink_realpath(path: &Path) -> io::Result<PathBuf> {
|
||||
if fs::symlink_metadata(path).await?.is_symlink() {
|
||||
symlink_realpath_with(path, &mut HashMap::new()).await.map(|p| p.into_owned())
|
||||
} else {
|
||||
fs::canonicalize(path).await
|
||||
}
|
||||
}
|
||||
|
||||
// realpath(3) without resolving symlinks. This is useful for case-insensitive
|
||||
// filesystems.
|
||||
//
|
||||
// Make sure the file of the path exists and is a symlink.
|
||||
pub async fn symlink_realpath_with<'a>(
|
||||
path: &'a Path,
|
||||
cached: &'a mut HashMap<PathBuf, PathBuf>,
|
||||
) -> io::Result<Cow<'a, Path>> {
|
||||
let lowercased: PathBuf = path.as_os_str().to_ascii_lowercase().into();
|
||||
if lowercased == path {
|
||||
return Ok(Cow::Borrowed(path));
|
||||
}
|
||||
|
||||
let Some(parent) = path.parent() else {
|
||||
return Ok(Cow::Borrowed(path));
|
||||
};
|
||||
|
||||
let case = parent.as_os_str().as_encoded_bytes().iter().any(|&b| b.is_ascii_uppercase());
|
||||
if !cached.contains_key(parent) {
|
||||
let mut it = fs::read_dir(parent).await?;
|
||||
while let Some(entry) = it.next_entry().await? {
|
||||
let p = entry.path();
|
||||
if case || p.file_name().unwrap().as_encoded_bytes().iter().any(|&b| b.is_ascii_uppercase()) {
|
||||
cached.insert(p.as_os_str().to_ascii_lowercase().into(), p);
|
||||
}
|
||||
}
|
||||
cached.insert(parent.to_owned(), PathBuf::new());
|
||||
}
|
||||
|
||||
Ok(
|
||||
cached
|
||||
.get(&lowercased)
|
||||
.filter(|p| !p.as_os_str().is_empty())
|
||||
.map_or_else(|| Cow::Borrowed(path), |p| Cow::Borrowed(p)),
|
||||
)
|
||||
}
|
||||
|
||||
pub async fn calculate_size(path: &Path) -> u64 {
|
||||
let mut total = 0;
|
||||
let mut stack = VecDeque::from([path.to_path_buf()]);
|
||||
|
Loading…
Reference in New Issue
Block a user