mirror of
https://github.com/sxyazi/yazi.git
synced 2026-07-25 08:41:05 +00:00
perf: introduce URN to speed up large directory file updates (#1652)
This commit is contained in:
parent
dfe2884110
commit
7222e178a3
20 changed files with 176 additions and 169 deletions
|
|
@ -6,7 +6,7 @@ use tokio::{fs::{self, OpenOptions}, io::{stdin, AsyncReadExt, AsyncWriteExt}};
|
||||||
use yazi_config::{OPEN, PREVIEW};
|
use yazi_config::{OPEN, PREVIEW};
|
||||||
use yazi_dds::Pubsub;
|
use yazi_dds::Pubsub;
|
||||||
use yazi_proxy::{AppProxy, TasksProxy, HIDER, WATCHER};
|
use yazi_proxy::{AppProxy, TasksProxy, HIDER, WATCHER};
|
||||||
use yazi_shared::{fs::{max_common_root, maybe_exists, paths_to_same_file, File, FilesOp, Url}, terminal_clear};
|
use yazi_shared::{fs::{max_common_root, maybe_exists, paths_to_same_file, File, Url}, terminal_clear};
|
||||||
|
|
||||||
use crate::manager::Manager;
|
use crate::manager::Manager;
|
||||||
|
|
||||||
|
|
@ -98,7 +98,8 @@ impl Manager {
|
||||||
// FIXME: consider old and new in the different directories
|
// FIXME: consider old and new in the different directories
|
||||||
if !succeeded.is_empty() {
|
if !succeeded.is_empty() {
|
||||||
Pubsub::pub_from_bulk(succeeded.iter().map(|(u, f)| (u, f.url())).collect());
|
Pubsub::pub_from_bulk(succeeded.iter().map(|(u, f)| (u, f.url())).collect());
|
||||||
FilesOp::Upserting(cwd, succeeded).emit();
|
// FIXME 0
|
||||||
|
// FilesOp::Upserting(cwd, succeeded).emit();
|
||||||
}
|
}
|
||||||
drop(permit);
|
drop(permit);
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,10 +1,10 @@
|
||||||
use std::collections::HashMap;
|
use std::collections::{HashMap, HashSet};
|
||||||
|
|
||||||
use anyhow::Result;
|
use anyhow::Result;
|
||||||
use tokio::fs;
|
use tokio::fs;
|
||||||
use yazi_config::popup::{ConfirmCfg, InputCfg};
|
use yazi_config::popup::{ConfirmCfg, InputCfg};
|
||||||
use yazi_proxy::{ConfirmProxy, InputProxy, TabProxy, WATCHER};
|
use yazi_proxy::{ConfirmProxy, InputProxy, TabProxy, WATCHER};
|
||||||
use yazi_shared::{event::Cmd, fs::{maybe_exists, ok_or_not_found, symlink_realpath, File, FilesOp, Url}};
|
use yazi_shared::{event::Cmd, fs::{maybe_exists, ok_or_not_found, realname, File, FilesOp, Url, UrnBuf}};
|
||||||
|
|
||||||
use crate::manager::Manager;
|
use crate::manager::Manager;
|
||||||
|
|
||||||
|
|
@ -48,9 +48,9 @@ impl Manager {
|
||||||
|
|
||||||
if dir {
|
if dir {
|
||||||
fs::create_dir_all(&new).await?;
|
fs::create_dir_all(&new).await?;
|
||||||
} else if let Ok(real) = symlink_realpath(&new).await {
|
} else if let Some(real) = realname(&new).await {
|
||||||
ok_or_not_found(fs::remove_file(&new).await)?;
|
ok_or_not_found(fs::remove_file(&new).await)?;
|
||||||
FilesOp::Deleting(parent.clone(), vec![Url::from(real)]).emit();
|
FilesOp::Deleting(parent.clone(), HashSet::from_iter([UrnBuf::_from(real)])).emit();
|
||||||
fs::File::create(&new).await?;
|
fs::File::create(&new).await?;
|
||||||
} else {
|
} else {
|
||||||
fs::create_dir_all(&parent).await.ok();
|
fs::create_dir_all(&parent).await.ok();
|
||||||
|
|
@ -59,7 +59,7 @@ impl Manager {
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Ok(f) = File::from(new.clone()).await {
|
if let Ok(f) = File::from(new.clone()).await {
|
||||||
FilesOp::Upserting(parent, HashMap::from_iter([(f.url_owned(), f)])).emit();
|
FilesOp::Upserting(parent, HashMap::from_iter([(f.urn_owned(), f)])).emit();
|
||||||
TabProxy::reveal(&new)
|
TabProxy::reveal(&new)
|
||||||
}
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
|
|
|
||||||
|
|
@ -1,11 +1,11 @@
|
||||||
use std::collections::HashMap;
|
use std::collections::{HashMap, HashSet};
|
||||||
|
|
||||||
use anyhow::Result;
|
use anyhow::Result;
|
||||||
use tokio::fs;
|
use tokio::fs;
|
||||||
use yazi_config::popup::{ConfirmCfg, InputCfg};
|
use yazi_config::popup::{ConfirmCfg, InputCfg};
|
||||||
use yazi_dds::Pubsub;
|
use yazi_dds::Pubsub;
|
||||||
use yazi_proxy::{ConfirmProxy, InputProxy, TabProxy, WATCHER};
|
use yazi_proxy::{ConfirmProxy, InputProxy, TabProxy, WATCHER};
|
||||||
use yazi_shared::{event::Cmd, fs::{maybe_exists, ok_or_not_found, paths_to_same_file, symlink_realpath, File, FilesOp, Url}};
|
use yazi_shared::{event::Cmd, fs::{maybe_exists, ok_or_not_found, paths_to_same_file, realname, File, FilesOp, Url, UrnBuf}};
|
||||||
|
|
||||||
use crate::manager::Manager;
|
use crate::manager::Manager;
|
||||||
|
|
||||||
|
|
@ -74,22 +74,27 @@ impl Manager {
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn rename_do(tab: usize, old: Url, new: Url) -> Result<()> {
|
async fn rename_do(tab: usize, old: Url, new: Url) -> Result<()> {
|
||||||
let Some(p_old) = old.parent_url() else { return Ok(()) };
|
let Some((p_old, n_old)) = old.pair() else { return Ok(()) };
|
||||||
let Some(p_new) = new.parent_url() else { return Ok(()) };
|
let Some((p_new, n_new)) = new.pair() else { return Ok(()) };
|
||||||
let _permit = WATCHER.acquire().await.unwrap();
|
let _permit = WATCHER.acquire().await.unwrap();
|
||||||
|
|
||||||
let overwritten = symlink_realpath(&new).await;
|
let overwritten = realname(&new).await;
|
||||||
fs::rename(&old, &new).await?;
|
fs::rename(&old, &new).await?;
|
||||||
|
|
||||||
if let Ok(o) = overwritten {
|
if let Some(o) = overwritten {
|
||||||
ok_or_not_found(fs::rename(&o, &new).await)?;
|
ok_or_not_found(fs::rename(p_new.join(&o), &new).await)?;
|
||||||
FilesOp::Deleting(p_new.clone(), vec![Url::from(o)]).emit();
|
FilesOp::Deleting(p_new.clone(), HashSet::from_iter([UrnBuf::_from(o)])).emit();
|
||||||
}
|
}
|
||||||
Pubsub::pub_from_rename(tab, &old, &new);
|
Pubsub::pub_from_rename(tab, &old, &new);
|
||||||
|
|
||||||
let file = File::from(new.clone()).await?;
|
let file = File::from(new.clone()).await?;
|
||||||
FilesOp::Deleting(p_old, vec![old]).emit();
|
if p_new == p_old {
|
||||||
FilesOp::Upserting(p_new, HashMap::from_iter([(new.clone(), file)])).emit();
|
FilesOp::Upserting(p_old, HashMap::from_iter([(n_old, file)])).emit();
|
||||||
|
} else {
|
||||||
|
FilesOp::Deleting(p_old, HashSet::from_iter([n_old])).emit();
|
||||||
|
FilesOp::Upserting(p_new, HashMap::from_iter([(n_new, file)])).emit();
|
||||||
|
}
|
||||||
|
|
||||||
Ok(TabProxy::reveal(&new))
|
Ok(TabProxy::reveal(&new))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -26,7 +26,7 @@ impl Manager {
|
||||||
|
|
||||||
let mut ops = vec![opt.op];
|
let mut ops = vec![opt.op];
|
||||||
for u in LINKED.read().from_dir(ops[0].cwd()) {
|
for u in LINKED.read().from_dir(ops[0].cwd()) {
|
||||||
ops.push(ops[0].chroot(u));
|
ops.push(ops[0].rebase(u));
|
||||||
}
|
}
|
||||||
|
|
||||||
for op in ops {
|
for op in ops {
|
||||||
|
|
@ -60,10 +60,7 @@ impl Manager {
|
||||||
|
|
||||||
fn update_parent(tab: &mut Tab, op: Cow<FilesOp>) {
|
fn update_parent(tab: &mut Tab, op: Cow<FilesOp>) {
|
||||||
let urn = tab.cwd().urn_owned();
|
let urn = tab.cwd().urn_owned();
|
||||||
// FIXME
|
let leave = matches!(*op, FilesOp::Deleting(_, ref urns) if urns.contains(&urn));
|
||||||
let leave = false;
|
|
||||||
// let leave = matches!(*op, FilesOp::Deleting(_, ref urls) if
|
|
||||||
// urls.contains(&urn));
|
|
||||||
|
|
||||||
if let Some(f) = tab.parent.as_mut() {
|
if let Some(f) = tab.parent.as_mut() {
|
||||||
render!(f.update(op.into_owned()));
|
render!(f.update(op.into_owned()));
|
||||||
|
|
@ -111,8 +108,8 @@ impl Manager {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn update_history(tab: &mut Tab, op: Cow<FilesOp>) {
|
fn update_history(tab: &mut Tab, op: Cow<FilesOp>) {
|
||||||
let leave = tab.parent.as_ref().and_then(|f| f.loc.parent_url().map(|p| (&f.loc, p))).is_some_and(
|
let leave = tab.parent.as_ref().and_then(|f| f.loc.parent_url().map(|p| (p, f.loc.urn()))).is_some_and(
|
||||||
|(p, pp)| matches!(*op, FilesOp::Deleting(ref parent, ref urls) if *parent == pp && urls.contains(p)),
|
|(p, n)| matches!(*op, FilesOp::Deleting(ref parent, ref urns) if *parent == p && urns.contains(n)),
|
||||||
);
|
);
|
||||||
|
|
||||||
let folder = tab.history.entry(op.cwd().clone()).or_insert_with(|| Folder::from(op.cwd()));
|
let folder = tab.history.entry(op.cwd().clone()).or_insert_with(|| Folder::from(op.cwd()));
|
||||||
|
|
|
||||||
|
|
@ -9,7 +9,7 @@ use tracing::error;
|
||||||
use yazi_fs::{Files, Folder};
|
use yazi_fs::{Files, Folder};
|
||||||
use yazi_plugin::isolate;
|
use yazi_plugin::isolate;
|
||||||
use yazi_proxy::WATCHER;
|
use yazi_proxy::WATCHER;
|
||||||
use yazi_shared::{fs::{symlink_realname, Cha, File, FilesOp, Url}, RoCell};
|
use yazi_shared::{fs::{realname_unchecked, Cha, File, FilesOp, Url}, RoCell};
|
||||||
|
|
||||||
use super::Linked;
|
use super::Linked;
|
||||||
|
|
||||||
|
|
@ -124,27 +124,26 @@ impl Watcher {
|
||||||
let _permit = WATCHER.acquire().await.unwrap();
|
let _permit = WATCHER.acquire().await.unwrap();
|
||||||
let mut reload = Vec::with_capacity(urls.len());
|
let mut reload = Vec::with_capacity(urls.len());
|
||||||
|
|
||||||
for url in urls {
|
for u in urls {
|
||||||
let Some(name) = url.file_name() else { continue };
|
let Some((parent, urn)) = u.pair() else { continue };
|
||||||
let Some(parent) = url.parent_url() else { continue };
|
let Ok(file) = File::from(u).await else {
|
||||||
|
FilesOp::Deleting(parent, HashSet::from_iter([urn])).emit();
|
||||||
let Ok(file) = File::from(url.clone()).await else {
|
|
||||||
FilesOp::Deleting(parent, vec![url]).emit();
|
|
||||||
continue;
|
continue;
|
||||||
};
|
};
|
||||||
|
|
||||||
let eq = (!file.is_link() && fs::canonicalize(&url).await.is_ok_and(|p| p == *url))
|
let u = file.url();
|
||||||
|| symlink_realname(&url, &mut cached).await.is_ok_and(|s| s == name);
|
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| s == urn._deref()._as_path());
|
||||||
|
|
||||||
if !eq {
|
if !eq {
|
||||||
FilesOp::Deleting(parent, vec![url]).emit();
|
FilesOp::Deleting(parent, HashSet::from_iter([urn])).emit();
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
if !file.is_dir() {
|
if !file.is_dir() {
|
||||||
reload.push(file.clone());
|
reload.push(file.clone());
|
||||||
}
|
}
|
||||||
FilesOp::Upserting(parent, HashMap::from_iter([(url, file)])).emit();
|
FilesOp::Upserting(parent, HashMap::from_iter([(urn, file)])).emit();
|
||||||
}
|
}
|
||||||
|
|
||||||
if reload.is_empty() {
|
if reload.is_empty() {
|
||||||
|
|
|
||||||
|
|
@ -39,17 +39,10 @@ impl Yanked {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn apply_op(&mut self, op: &FilesOp) {
|
pub fn apply_op(&mut self, op: &FilesOp) {
|
||||||
let (removal, addition) = match op {
|
let (removal, addition) = op.diff_recoverable(|u| self.contains(u));
|
||||||
FilesOp::Deleting(_, urls) => (urls.iter().collect(), vec![]),
|
|
||||||
FilesOp::Updating(_, urls) | FilesOp::Upserting(_, urls) => {
|
|
||||||
urls.iter().filter(|(u, _)| self.contains(u)).map(|(u, f)| (u, f.url_owned())).unzip()
|
|
||||||
}
|
|
||||||
_ => (vec![], vec![]),
|
|
||||||
};
|
|
||||||
|
|
||||||
if !removal.is_empty() {
|
if !removal.is_empty() {
|
||||||
let old = self.urls.len();
|
let old = self.urls.len();
|
||||||
self.urls.retain(|u| !removal.contains(&u));
|
self.urls.retain(|u| !removal.contains(u));
|
||||||
self.revision += (old != self.urls.len()) as u64;
|
self.revision += (old != self.urls.len()) as u64;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -18,23 +18,24 @@ impl Selected {
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn add(&mut self, url: &Url) -> bool { self.add_same(&[url]) == 1 }
|
pub fn add(&mut self, url: &Url) -> bool { self.add_same(&[url]) == 1 }
|
||||||
|
|
||||||
pub fn add_many(&mut self, urls: &[&Url], same: bool) -> usize {
|
pub fn add_many(&mut self, urls: &[impl AsRef<Url>], same: bool) -> usize {
|
||||||
if same {
|
if same {
|
||||||
return self.add_same(urls);
|
return self.add_same(urls);
|
||||||
}
|
}
|
||||||
|
|
||||||
let mut grouped: HashMap<_, Vec<_>> = Default::default();
|
let mut grouped: HashMap<_, Vec<_>> = Default::default();
|
||||||
for &u in urls {
|
for u in urls {
|
||||||
if let Some(p) = u.parent_url() {
|
if let Some(p) = u.as_ref().parent_url() {
|
||||||
grouped.entry(p).or_default().push(u);
|
grouped.entry(p).or_default().push(u);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
grouped.into_values().map(|v| self.add_same(&v)).sum()
|
grouped.into_values().map(|v| self.add_same(&v)).sum()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn add_same(&mut self, urls: &[&Url]) -> usize {
|
fn add_same(&mut self, urls: &[impl AsRef<Url>]) -> usize {
|
||||||
// If it has appeared as a parent
|
// If it has appeared as a parent
|
||||||
let urls: Vec<_> = urls.iter().filter(|&&u| !self.parents.contains_key(u)).collect();
|
let urls: Vec<_> =
|
||||||
|
urls.iter().map(|u| u.as_ref()).filter(|&u| !self.parents.contains_key(u)).collect();
|
||||||
if urls.is_empty() {
|
if urls.is_empty() {
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
@ -52,7 +53,7 @@ impl Selected {
|
||||||
}
|
}
|
||||||
|
|
||||||
let (now, len) = (timestamp_us(), self.inner.len());
|
let (now, len) = (timestamp_us(), self.inner.len());
|
||||||
self.inner.extend(urls.iter().enumerate().map(|(i, &&u)| (u.clone(), now + i as u64)));
|
self.inner.extend(urls.iter().enumerate().map(|(i, &u)| (u.clone(), now + i as u64)));
|
||||||
|
|
||||||
for u in parents {
|
for u in parents {
|
||||||
*self.parents.entry(u).or_insert(0) += self.inner.len() - len;
|
*self.parents.entry(u).or_insert(0) += self.inner.len() - len;
|
||||||
|
|
@ -103,14 +104,7 @@ impl Selected {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn apply_op(&mut self, op: &FilesOp) {
|
pub fn apply_op(&mut self, op: &FilesOp) {
|
||||||
let (removal, addition) = match op {
|
let (removal, addition) = op.diff_recoverable(|u| self.contains_key(u));
|
||||||
FilesOp::Deleting(_, urls) => (urls.iter().collect(), vec![]),
|
|
||||||
FilesOp::Updating(_, urls) | FilesOp::Upserting(_, urls) => {
|
|
||||||
urls.iter().filter(|&(u, _)| self.contains_key(u)).map(|(u, f)| (u, f.url())).unzip()
|
|
||||||
}
|
|
||||||
_ => (vec![], vec![]),
|
|
||||||
};
|
|
||||||
|
|
||||||
if !removal.is_empty() {
|
if !removal.is_empty() {
|
||||||
self.remove_many(&removal, !op.cwd().is_search());
|
self.remove_many(&removal, !op.cwd().is_search());
|
||||||
}
|
}
|
||||||
|
|
@ -196,7 +190,7 @@ mod tests {
|
||||||
fn insert_many_empty_urls_list() {
|
fn insert_many_empty_urls_list() {
|
||||||
let mut s = Selected::default();
|
let mut s = Selected::default();
|
||||||
|
|
||||||
assert_eq!(0, s.add_same(&[]));
|
assert_eq!(0, s.add_same(&[] as &[&Url]));
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
|
|
||||||
|
|
@ -72,7 +72,7 @@ impl Tasks {
|
||||||
targets
|
targets
|
||||||
.iter()
|
.iter()
|
||||||
.filter(|f| {
|
.filter(|f| {
|
||||||
f.is_dir() && !targets.sizes.contains_key(f.url()) && !loading.contains(f.url())
|
f.is_dir() && !targets.sizes.contains_key(f.urn()) && !loading.contains(f.url())
|
||||||
})
|
})
|
||||||
.map(|f| f.url())
|
.map(|f| f.url())
|
||||||
.collect()
|
.collect()
|
||||||
|
|
|
||||||
|
|
@ -39,7 +39,7 @@ impl File {
|
||||||
|
|
||||||
reg.add_field_method_get("idx", |_, me| Ok(me.idx + 1));
|
reg.add_field_method_get("idx", |_, me| Ok(me.idx + 1));
|
||||||
reg.add_method("size", |_, me, ()| {
|
reg.add_method("size", |_, me, ()| {
|
||||||
Ok(if me.is_dir() { me.folder().files.sizes.get(me.url()).copied() } else { Some(me.len) })
|
Ok(if me.is_dir() { me.folder().files.sizes.get(me.urn()).copied() } else { Some(me.len) })
|
||||||
});
|
});
|
||||||
reg.add_method("mime", |lua, me, ()| {
|
reg.add_method("mime", |lua, me, ()| {
|
||||||
let cx = lua.named_registry_value::<CtxRef>("cx")?;
|
let cx = lua.named_registry_value::<CtxRef>("cx")?;
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,7 @@ use std::{collections::{HashMap, HashSet}, mem, ops::Deref, sync::atomic::Orderi
|
||||||
|
|
||||||
use tokio::{fs::{self, DirEntry}, select, sync::mpsc::{self, UnboundedReceiver}};
|
use tokio::{fs::{self, DirEntry}, select, sync::mpsc::{self, UnboundedReceiver}};
|
||||||
use yazi_config::{manager::SortBy, MANAGER};
|
use yazi_config::{manager::SortBy, MANAGER};
|
||||||
use yazi_shared::fs::{maybe_exists, Cha, File, FilesOp, Url, Urn, FILES_TICKET};
|
use yazi_shared::fs::{maybe_exists, Cha, File, FilesOp, Url, Urn, UrnBuf, FILES_TICKET};
|
||||||
|
|
||||||
use super::{FilesSorter, Filter};
|
use super::{FilesSorter, Filter};
|
||||||
|
|
||||||
|
|
@ -13,7 +13,7 @@ pub struct Files {
|
||||||
version: u64,
|
version: u64,
|
||||||
pub revision: u64,
|
pub revision: u64,
|
||||||
|
|
||||||
pub sizes: HashMap<Url, u64>,
|
pub sizes: HashMap<UrnBuf, u64>,
|
||||||
|
|
||||||
sorter: FilesSorter,
|
sorter: FilesSorter,
|
||||||
filter: Option<Filter>,
|
filter: Option<Filter>,
|
||||||
|
|
@ -99,7 +99,7 @@ impl Files {
|
||||||
pub async fn assert_stale(cwd: &Url, cha: Cha) -> Option<Cha> {
|
pub async fn assert_stale(cwd: &Url, cha: Cha) -> Option<Cha> {
|
||||||
match fs::metadata(cwd).await.map(Cha::from) {
|
match fs::metadata(cwd).await.map(Cha::from) {
|
||||||
Ok(c) if !c.is_dir() => {
|
Ok(c) if !c.is_dir() => {
|
||||||
// FIXME: use `ErrorKind::NotADirectory` instead once it gets stabilized
|
// TODO: use `ErrorKind::NotADirectory` instead once it gets stabilized
|
||||||
FilesOp::IOErr(cwd.clone(), std::io::ErrorKind::AlreadyExists).emit();
|
FilesOp::IOErr(cwd.clone(), std::io::ErrorKind::AlreadyExists).emit();
|
||||||
}
|
}
|
||||||
Ok(c) if c.hits(cha) => {}
|
Ok(c) if c.hits(cha) => {}
|
||||||
|
|
@ -107,8 +107,8 @@ impl Files {
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
if maybe_exists(cwd).await {
|
if maybe_exists(cwd).await {
|
||||||
FilesOp::IOErr(cwd.clone(), e.kind()).emit();
|
FilesOp::IOErr(cwd.clone(), e.kind()).emit();
|
||||||
} else if let Some(p) = cwd.parent_url() {
|
} else if let Some((p, n)) = cwd.pair() {
|
||||||
FilesOp::Deleting(p, vec![cwd.clone()]).emit();
|
FilesOp::Deleting(p, HashSet::from_iter([n])).emit();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -150,7 +150,7 @@ impl Files {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn update_size(&mut self, sizes: HashMap<Url, u64>) {
|
pub fn update_size(&mut self, sizes: HashMap<UrnBuf, u64>) {
|
||||||
if sizes.is_empty() {
|
if sizes.is_empty() {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
@ -197,31 +197,30 @@ impl Files {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(unix)]
|
#[cfg(unix)]
|
||||||
pub fn update_deleting(&mut self, urls: Vec<Url>) {
|
pub fn update_deleting(&mut self, urns: HashSet<UrnBuf>) {
|
||||||
if urls.is_empty() {
|
if urns.is_empty() {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
macro_rules! go {
|
macro_rules! go {
|
||||||
($dist:expr, $src:expr, $inc:literal) => {
|
($dist:expr, $src:expr, $inc:literal) => {
|
||||||
let mut todo: HashSet<_> = $src.into_iter().collect();
|
|
||||||
let len = $dist.len();
|
let len = $dist.len();
|
||||||
|
$dist.retain(|f| !$src.remove(f.urn()));
|
||||||
$dist.retain(|f| !todo.remove(f.url()));
|
|
||||||
if $dist.len() != len {
|
if $dist.len() != len {
|
||||||
self.revision += $inc;
|
self.revision += $inc;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
let (hidden, items) = if let Some(filter) = &self.filter {
|
let (mut hidden, mut items) = if let Some(filter) = &self.filter {
|
||||||
urls.into_iter().partition(|u| {
|
urns.into_iter().partition(|u| {
|
||||||
(!self.show_hidden && u.is_hidden()) || !u.file_name().is_some_and(|s| filter.matches(s))
|
(!self.show_hidden && u._deref().is_hidden())
|
||||||
|
|| !u._deref().name().is_some_and(|s| filter.matches(s))
|
||||||
})
|
})
|
||||||
} else if self.show_hidden {
|
} else if self.show_hidden {
|
||||||
(vec![], urls)
|
(HashSet::new(), urns)
|
||||||
} else {
|
} else {
|
||||||
urls.into_iter().partition(|u| u.is_hidden())
|
urns.into_iter().partition(|u| u._deref().is_hidden())
|
||||||
};
|
};
|
||||||
|
|
||||||
if !items.is_empty() {
|
if !items.is_empty() {
|
||||||
|
|
@ -233,31 +232,29 @@ impl Files {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(windows)]
|
#[cfg(windows)]
|
||||||
pub fn update_deleting(&mut self, urls: Vec<Url>) {
|
pub fn update_deleting(&mut self, mut urns: HashSet<UrnBuf>) {
|
||||||
macro_rules! go {
|
macro_rules! go {
|
||||||
($dist:expr, $src:expr, $inc:literal) => {
|
($dist:expr, $src:expr, $inc:literal) => {
|
||||||
let len = $dist.len();
|
let len = $dist.len();
|
||||||
|
$dist.retain(|f| !$src.remove(f.urn()));
|
||||||
$dist.retain(|f| !$src.remove(f.url()));
|
|
||||||
if $dist.len() != len {
|
if $dist.len() != len {
|
||||||
self.revision += $inc;
|
self.revision += $inc;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
let mut urls: HashSet<_> = urls.into_iter().collect();
|
if !urns.is_empty() {
|
||||||
if !urls.is_empty() {
|
go!(self.items, urns, 1);
|
||||||
go!(self.items, urls, 1);
|
|
||||||
}
|
}
|
||||||
if !urls.is_empty() {
|
if !urns.is_empty() {
|
||||||
go!(self.hidden, urls, 0);
|
go!(self.hidden, urns, 0);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn update_updating(
|
pub fn update_updating(
|
||||||
&mut self,
|
&mut self,
|
||||||
files: HashMap<Url, File>,
|
files: HashMap<UrnBuf, File>,
|
||||||
) -> (HashMap<Url, File>, HashMap<Url, File>) {
|
) -> (HashMap<UrnBuf, File>, HashMap<UrnBuf, File>) {
|
||||||
if files.is_empty() {
|
if files.is_empty() {
|
||||||
return Default::default();
|
return Default::default();
|
||||||
}
|
}
|
||||||
|
|
@ -266,7 +263,7 @@ impl Files {
|
||||||
($dist:expr, $src:expr, $inc:literal) => {
|
($dist:expr, $src:expr, $inc:literal) => {
|
||||||
let mut b = true;
|
let mut b = true;
|
||||||
for i in 0..$dist.len() {
|
for i in 0..$dist.len() {
|
||||||
if let Some(f) = $src.remove($dist[i].url()) {
|
if let Some(f) = $src.remove($dist[i].urn()) {
|
||||||
b &= $dist[i].cha.hits(f.cha);
|
b &= $dist[i].cha.hits(f.cha);
|
||||||
$dist[i] = f;
|
$dist[i] = f;
|
||||||
|
|
||||||
|
|
@ -298,7 +295,7 @@ impl Files {
|
||||||
(hidden, items)
|
(hidden, items)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn update_upserting(&mut self, files: HashMap<Url, File>) {
|
pub fn update_upserting(&mut self, files: HashMap<UrnBuf, File>) {
|
||||||
if files.is_empty() {
|
if files.is_empty() {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -52,7 +52,7 @@ impl Folder {
|
||||||
FilesOp::IOErr(..) => self.files.update_ioerr(),
|
FilesOp::IOErr(..) => self.files.update_ioerr(),
|
||||||
|
|
||||||
FilesOp::Creating(_, files) => self.files.update_creating(files),
|
FilesOp::Creating(_, files) => self.files.update_creating(files),
|
||||||
FilesOp::Deleting(_, urls) => self.files.update_deleting(urls),
|
FilesOp::Deleting(_, urns) => self.files.update_deleting(urns),
|
||||||
FilesOp::Updating(_, files) => _ = self.files.update_updating(files),
|
FilesOp::Updating(_, files) => _ = self.files.update_updating(files),
|
||||||
FilesOp::Upserting(_, files) => self.files.update_upserting(files),
|
FilesOp::Upserting(_, files) => self.files.update_upserting(files),
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
use std::{cmp::Ordering, collections::HashMap, mem};
|
use std::{cmp::Ordering, collections::HashMap, mem};
|
||||||
|
|
||||||
use yazi_config::manager::SortBy;
|
use yazi_config::manager::SortBy;
|
||||||
use yazi_shared::{fs::{File, Url}, natsort, LcgRng, Transliterator};
|
use yazi_shared::{fs::{File, UrnBuf}, natsort, LcgRng, Transliterator};
|
||||||
|
|
||||||
#[derive(Clone, Copy, Default, PartialEq)]
|
#[derive(Clone, Copy, Default, PartialEq)]
|
||||||
pub struct FilesSorter {
|
pub struct FilesSorter {
|
||||||
|
|
@ -13,7 +13,7 @@ pub struct FilesSorter {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl FilesSorter {
|
impl FilesSorter {
|
||||||
pub(super) fn sort(&self, items: &mut Vec<File>, sizes: &HashMap<Url, u64>) {
|
pub(super) fn sort(&self, items: &mut Vec<File>, sizes: &HashMap<UrnBuf, u64>) {
|
||||||
if items.is_empty() {
|
if items.is_empty() {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
@ -51,8 +51,8 @@ impl FilesSorter {
|
||||||
SortBy::Alphabetical => items.sort_unstable_by(by_alphabetical),
|
SortBy::Alphabetical => items.sort_unstable_by(by_alphabetical),
|
||||||
SortBy::Natural => self.sort_naturally(items),
|
SortBy::Natural => self.sort_naturally(items),
|
||||||
SortBy::Size => items.sort_unstable_by(|a, b| {
|
SortBy::Size => items.sort_unstable_by(|a, b| {
|
||||||
let aa = if a.is_dir() { sizes.get(a.url()).copied() } else { None };
|
let aa = if a.is_dir() { sizes.get(a.urn()).copied() } else { None };
|
||||||
let bb = if b.is_dir() { sizes.get(b.url()).copied() } else { None };
|
let bb = if b.is_dir() { sizes.get(b.urn()).copied() } else { None };
|
||||||
let ord = self.cmp(aa.unwrap_or(a.len), bb.unwrap_or(b.len), self.promote(a, b));
|
let ord = self.cmp(aa.unwrap_or(a.len), bb.unwrap_or(b.len), self.promote(a, b));
|
||||||
if ord == Ordering::Equal { by_alphabetical(a, b) } else { ord }
|
if ord == Ordering::Equal { by_alphabetical(a, b) } else { ord }
|
||||||
}),
|
}),
|
||||||
|
|
|
||||||
|
|
@ -111,7 +111,8 @@ 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 {
|
||||||
// FIXME: handle errors
|
// FIXME: should return a `std::io::Result` to handle errors such as
|
||||||
|
// permission denied
|
||||||
Url::cast(lua, yazi_shared::fs::unique_name(url.clone()).await)
|
Url::cast(lua, yazi_shared::fs::unique_name(url.clone()).await)
|
||||||
})?,
|
})?,
|
||||||
),
|
),
|
||||||
|
|
|
||||||
|
|
@ -92,7 +92,8 @@ impl Prework {
|
||||||
}
|
}
|
||||||
|
|
||||||
let parent = buf[0].0.parent_url().unwrap();
|
let parent = buf[0].0.parent_url().unwrap();
|
||||||
FilesOp::Size(parent, HashMap::from_iter(buf)).emit();
|
// FIXME 0
|
||||||
|
// FilesOp::Size(parent, HashMap::from_iter(buf)).emit();
|
||||||
});
|
});
|
||||||
self.prog.send(TaskProg::Adv(task.id, 1, 0))?;
|
self.prog.send(TaskProg::Adv(task.id, 1, 0))?;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -81,7 +81,7 @@ impl File {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(unix)]
|
#[cfg(unix)]
|
||||||
if loc.url().is_hidden() {
|
if loc.urn().is_hidden() {
|
||||||
ck |= ChaKind::HIDDEN;
|
ck |= ChaKind::HIDDEN;
|
||||||
}
|
}
|
||||||
#[cfg(windows)]
|
#[cfg(windows)]
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
use std::{borrow::Cow, collections::{HashMap, VecDeque}, ffi::{OsStr, OsString}, fs::Metadata, path::{Path, PathBuf}};
|
use std::{borrow::Cow, collections::{HashMap, HashSet, VecDeque}, ffi::{OsStr, OsString}, fs::Metadata, path::{Path, PathBuf}};
|
||||||
|
|
||||||
use anyhow::{bail, Result};
|
use anyhow::{bail, Result};
|
||||||
use tokio::{fs, io, select, sync::{mpsc, oneshot}, time};
|
use tokio::{fs, io, select, sync::{mpsc, oneshot}, time};
|
||||||
|
|
@ -76,19 +76,22 @@ async fn _paths_to_same_file(a: &Path, b: &Path) -> std::io::Result<bool> {
|
||||||
Ok(final_name(a).await? == final_name(b).await?)
|
Ok(final_name(a).await? == final_name(b).await?)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn symlink_realpath(path: &Path) -> Result<PathBuf> {
|
pub async fn realname(p: &Path) -> Option<OsString> {
|
||||||
let p = fs::canonicalize(path).await?;
|
let name = p.file_name()?;
|
||||||
if p == path {
|
if p == fs::canonicalize(p).await.ok()? {
|
||||||
return Ok(p);
|
return None;
|
||||||
}
|
}
|
||||||
|
|
||||||
let Some(parent) = path.parent() else { bail!("no parent") };
|
realname_unchecked(p, &mut HashMap::new())
|
||||||
symlink_realname(path, &mut HashMap::new()).await.map(|n| parent.join(n))
|
.await
|
||||||
|
.ok()
|
||||||
|
.filter(|s| s != name)
|
||||||
|
.map(|s| s.into_owned())
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(unix)]
|
#[cfg(unix)]
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn test_symlink_realpath() {
|
async fn test_realname_unchecked() {
|
||||||
fs::remove_dir_all("/tmp/issue-1173").await.ok();
|
fs::remove_dir_all("/tmp/issue-1173").await.ok();
|
||||||
fs::create_dir_all("/tmp/issue-1173/real-dir").await.unwrap();
|
fs::create_dir_all("/tmp/issue-1173/real-dir").await.unwrap();
|
||||||
fs::File::create("/tmp/issue-1173/A").await.unwrap();
|
fs::File::create("/tmp/issue-1173/A").await.unwrap();
|
||||||
|
|
@ -97,64 +100,52 @@ async fn test_symlink_realpath() {
|
||||||
fs::symlink("/tmp/issue-1173/b", "/tmp/issue-1173/D").await.unwrap();
|
fs::symlink("/tmp/issue-1173/b", "/tmp/issue-1173/D").await.unwrap();
|
||||||
fs::symlink("real-dir", "/tmp/issue-1173/link-dir").await.unwrap();
|
fs::symlink("real-dir", "/tmp/issue-1173/link-dir").await.unwrap();
|
||||||
|
|
||||||
async fn check(a: &str, b: &str) {
|
let c = &mut HashMap::new();
|
||||||
let expected = if a == b || cfg!(windows) || cfg!(target_os = "macos") {
|
async fn check(a: &str, b: &str, c: &mut HashMap<PathBuf, HashSet<OsString>>) {
|
||||||
Some(PathBuf::from(b))
|
assert_eq!(realname_unchecked(Path::new(a), c).await.ok(), Some(OsStr::new(b).into()));
|
||||||
} else {
|
|
||||||
None
|
|
||||||
};
|
|
||||||
assert_eq!(symlink_realpath(Path::new(a)).await.ok(), expected);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
check("/tmp/issue-1173/a", "/tmp/issue-1173/A").await;
|
check("/tmp/issue-1173/a", "A", c).await;
|
||||||
check("/tmp/issue-1173/A", "/tmp/issue-1173/A").await;
|
check("/tmp/issue-1173/A", "A", c).await;
|
||||||
|
|
||||||
check("/tmp/issue-1173/b", "/tmp/issue-1173/b").await;
|
check("/tmp/issue-1173/b", "b", c).await;
|
||||||
check("/tmp/issue-1173/B", "/tmp/issue-1173/b").await;
|
check("/tmp/issue-1173/B", "b", c).await;
|
||||||
|
|
||||||
check("/tmp/issue-1173/link-dir/c", "/tmp/issue-1173/link-dir/C").await;
|
check("/tmp/issue-1173/link-dir/c", "C", c).await;
|
||||||
check("/tmp/issue-1173/link-dir/C", "/tmp/issue-1173/link-dir/C").await;
|
check("/tmp/issue-1173/link-dir/C", "C", c).await;
|
||||||
|
|
||||||
check("/tmp/issue-1173/d", "/tmp/issue-1173/D").await;
|
check("/tmp/issue-1173/d", "D", c).await;
|
||||||
check("/tmp/issue-1173/D", "/tmp/issue-1173/D").await;
|
check("/tmp/issue-1173/D", "D", c).await;
|
||||||
}
|
}
|
||||||
|
|
||||||
// realpath(3) without resolving symlinks. This is useful for case-insensitive
|
// realpath(3) without resolving symlinks. This is useful for case-insensitive
|
||||||
// filesystems.
|
// filesystems.
|
||||||
//
|
//
|
||||||
// Make sure the file of the path exists.
|
// Make sure the file of the path exists.
|
||||||
pub async fn symlink_realname<'a>(
|
pub async fn realname_unchecked<'a>(
|
||||||
path: &'a Path,
|
path: &'a Path,
|
||||||
cached: &'a mut HashMap<PathBuf, HashMap<OsString, OsString>>,
|
cached: &'a mut HashMap<PathBuf, HashSet<OsString>>,
|
||||||
) -> Result<Cow<'a, OsStr>> {
|
) -> Result<Cow<'a, OsStr>> {
|
||||||
let Some(name) = path.file_name() else { bail!("no file name") };
|
let Some(name) = path.file_name() else { bail!("no filename") };
|
||||||
let Some(parent) = path.parent() else { return Ok(name.into()) };
|
let Some(parent) = path.parent() else { return Ok(Cow::Borrowed(name)) };
|
||||||
|
|
||||||
if !cached.contains_key(parent) {
|
if !cached.contains_key(parent) {
|
||||||
let mut map = HashMap::new();
|
let mut set = HashSet::new();
|
||||||
let mut it = fs::read_dir(parent).await?;
|
let mut it = fs::read_dir(parent).await?;
|
||||||
while let Some(entry) = it.next_entry().await? {
|
while let Some(entry) = it.next_entry().await? {
|
||||||
let n = entry.file_name();
|
set.insert(entry.file_name());
|
||||||
if n.as_encoded_bytes().iter().all(|&b| b.is_ascii_lowercase()) {
|
|
||||||
map.insert(n, OsString::new());
|
|
||||||
} else {
|
|
||||||
map.insert(n.to_ascii_lowercase(), n);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
cached.insert(parent.to_owned(), map);
|
cached.insert(parent.to_owned(), set);
|
||||||
}
|
}
|
||||||
|
|
||||||
let c = &cached[parent];
|
let c = &cached[parent];
|
||||||
if let Some(s) = c.get(name) {
|
if c.contains(name) {
|
||||||
return if s.is_empty() { Ok(name.into()) } else { Ok(s.into()) };
|
Ok(Cow::Borrowed(name))
|
||||||
|
} else if let Some(n) = c.iter().find(|&n| n.eq_ignore_ascii_case(name)) {
|
||||||
|
Ok(Cow::Borrowed(n))
|
||||||
|
} else {
|
||||||
|
bail!("no such file")
|
||||||
}
|
}
|
||||||
|
|
||||||
let lowercased = name.to_ascii_lowercase();
|
|
||||||
if let Some(s) = c.get(&lowercased) {
|
|
||||||
return if s.is_empty() { Ok(lowercased.into()) } else { Ok(s.into()) };
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(name.into())
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn calculate_size(path: &Path) -> u64 {
|
pub async fn calculate_size(path: &Path) -> u64 {
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
use std::{collections::HashMap, sync::atomic::{AtomicU64, Ordering}};
|
use std::{collections::{HashMap, HashSet}, sync::atomic::{AtomicU64, Ordering}};
|
||||||
|
|
||||||
use super::{Cha, File};
|
use super::{Cha, File, UrnBuf};
|
||||||
use crate::{emit, event::Cmd, fs::Url, Layer};
|
use crate::{emit, event::Cmd, fs::Url, Layer};
|
||||||
|
|
||||||
pub static FILES_TICKET: AtomicU64 = AtomicU64::new(0);
|
pub static FILES_TICKET: AtomicU64 = AtomicU64::new(0);
|
||||||
|
|
@ -10,13 +10,13 @@ pub enum FilesOp {
|
||||||
Full(Url, Vec<File>, Cha),
|
Full(Url, Vec<File>, Cha),
|
||||||
Part(Url, Vec<File>, u64),
|
Part(Url, Vec<File>, u64),
|
||||||
Done(Url, Cha, u64),
|
Done(Url, Cha, u64),
|
||||||
Size(Url, HashMap<Url, u64>),
|
Size(Url, HashMap<UrnBuf, u64>),
|
||||||
IOErr(Url, std::io::ErrorKind),
|
IOErr(Url, std::io::ErrorKind),
|
||||||
|
|
||||||
Creating(Url, Vec<File>),
|
Creating(Url, Vec<File>),
|
||||||
Deleting(Url, Vec<Url>),
|
Deleting(Url, HashSet<UrnBuf>),
|
||||||
Updating(Url, HashMap<Url, File>),
|
Updating(Url, HashMap<UrnBuf, File>),
|
||||||
Upserting(Url, HashMap<Url, File>),
|
Upserting(Url, HashMap<UrnBuf, File>),
|
||||||
}
|
}
|
||||||
|
|
||||||
impl FilesOp {
|
impl FilesOp {
|
||||||
|
|
@ -47,15 +47,12 @@ impl FilesOp {
|
||||||
ticket
|
ticket
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn chroot(&self, new: &Url) -> Self {
|
pub fn rebase(&self, new: &Url) -> Self {
|
||||||
macro_rules! new {
|
|
||||||
($url:expr) => {{ new.join($url.file_name().unwrap()) }};
|
|
||||||
}
|
|
||||||
macro_rules! files {
|
macro_rules! files {
|
||||||
($files:expr) => {{ $files.iter().map(|f| f.rebase(new)).collect() }};
|
($files:expr) => {{ $files.iter().map(|f| f.rebase(new)).collect() }};
|
||||||
}
|
}
|
||||||
macro_rules! map {
|
macro_rules! map {
|
||||||
($map:expr) => {{ $map.iter().map(|(u, f)| (new!(u), f.rebase(new))).collect() }};
|
($map:expr) => {{ $map.iter().map(|(u, f)| (u.clone(), f.rebase(new))).collect() }};
|
||||||
}
|
}
|
||||||
|
|
||||||
let n = new.clone();
|
let n = new.clone();
|
||||||
|
|
@ -63,13 +60,29 @@ impl FilesOp {
|
||||||
Self::Full(_, files, mtime) => Self::Full(n, files!(files), *mtime),
|
Self::Full(_, files, mtime) => Self::Full(n, files!(files), *mtime),
|
||||||
Self::Part(_, files, ticket) => Self::Part(n, files!(files), *ticket),
|
Self::Part(_, files, ticket) => Self::Part(n, files!(files), *ticket),
|
||||||
Self::Done(_, mtime, ticket) => Self::Done(n, *mtime, *ticket),
|
Self::Done(_, mtime, ticket) => Self::Done(n, *mtime, *ticket),
|
||||||
Self::Size(_, map) => Self::Size(n, map.iter().map(|(u, &s)| (new!(u), s)).collect()),
|
Self::Size(_, map) => Self::Size(n, map.iter().map(|(u, &s)| (u.clone(), s)).collect()),
|
||||||
Self::IOErr(_, err) => Self::IOErr(n, *err),
|
Self::IOErr(_, err) => Self::IOErr(n, *err),
|
||||||
|
|
||||||
Self::Creating(_, files) => Self::Creating(n, files!(files)),
|
Self::Creating(_, files) => Self::Creating(n, files!(files)),
|
||||||
Self::Deleting(_, urls) => Self::Deleting(n, urls.iter().map(|u| new!(u)).collect()),
|
Self::Deleting(_, urns) => Self::Deleting(n, urns.clone()),
|
||||||
Self::Updating(_, map) => Self::Updating(n, map!(map)),
|
Self::Updating(_, map) => Self::Updating(n, map!(map)),
|
||||||
Self::Upserting(_, map) => Self::Upserting(n, map!(map)),
|
Self::Upserting(_, map) => Self::Upserting(n, map!(map)),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn diff_recoverable(&self, contains: impl Fn(&Url) -> bool) -> (Vec<Url>, Vec<Url>) {
|
||||||
|
match self {
|
||||||
|
Self::Deleting(cwd, urns) => {
|
||||||
|
(urns.iter().map(|u| cwd.join(u._deref()._as_path())).collect(), vec![])
|
||||||
|
}
|
||||||
|
Self::Updating(cwd, urns) | Self::Upserting(cwd, urns) => urns
|
||||||
|
.iter()
|
||||||
|
.filter(|&(u, f)| u != f.urn())
|
||||||
|
.map(|(u, f)| (cwd.join(u._deref()._as_path()), f))
|
||||||
|
.filter(|(u, _)| contains(u))
|
||||||
|
.map(|(u, f)| (u, f.url_owned()))
|
||||||
|
.unzip(),
|
||||||
|
_ => (vec![], vec![]),
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -72,8 +72,6 @@ fn _expand_path(p: &Path) -> PathBuf {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// FIXME: should return a `std::io::Result` to handle errors such as
|
|
||||||
// permission denied
|
|
||||||
pub async fn unique_name(mut u: Url) -> Url {
|
pub async fn unique_name(mut u: Url) -> 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 u;
|
return u;
|
||||||
|
|
|
||||||
|
|
@ -3,7 +3,7 @@ use std::{ffi::{OsStr, OsString}, fmt::{Debug, Display, Formatter}, ops::{Deref,
|
||||||
use percent_encoding::{percent_decode_str, percent_encode, AsciiSet, CONTROLS};
|
use percent_encoding::{percent_decode_str, percent_encode, AsciiSet, CONTROLS};
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
use super::Loc;
|
use super::{Loc, UrnBuf};
|
||||||
|
|
||||||
const ENCODE_SET: &AsciiSet = &CONTROLS.add(b'#');
|
const ENCODE_SET: &AsciiSet = &CONTROLS.add(b'#');
|
||||||
|
|
||||||
|
|
@ -150,6 +150,12 @@ impl Url {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
pub fn pair(&self) -> Option<(Self, UrnBuf)> {
|
||||||
|
let urn = UrnBuf::_from(self.path.file_name()?);
|
||||||
|
Some((self.parent_url()?, urn))
|
||||||
|
}
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn strip_prefix(&self, base: impl AsRef<Path>) -> Option<&Path> {
|
pub fn strip_prefix(&self, base: impl AsRef<Path>) -> Option<&Path> {
|
||||||
self.path.strip_prefix(base).ok()
|
self.path.strip_prefix(base).ok()
|
||||||
|
|
@ -158,12 +164,6 @@ impl Url {
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn into_os_string(self) -> OsString { self.path.into_os_string() }
|
pub fn into_os_string(self) -> OsString { self.path.into_os_string() }
|
||||||
|
|
||||||
#[cfg(unix)]
|
|
||||||
#[inline]
|
|
||||||
pub fn is_hidden(&self) -> bool {
|
|
||||||
self.file_name().map_or(false, |s| s.as_encoded_bytes().starts_with(b"."))
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn to_loc(&self, cwd: &Url) -> Loc { self.clone().into_loc(cwd) }
|
pub fn to_loc(&self, cwd: &Url) -> Loc { self.clone().into_loc(cwd) }
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
use std::{borrow::Borrow, path::{Path, PathBuf}};
|
use std::{borrow::Borrow, ffi::OsStr, path::{Path, PathBuf}};
|
||||||
|
|
||||||
#[derive(Debug, Eq, PartialEq)]
|
#[derive(Debug, Eq, PartialEq, Hash)]
|
||||||
#[repr(transparent)]
|
#[repr(transparent)]
|
||||||
pub struct Urn(Path);
|
pub struct Urn(Path);
|
||||||
|
|
||||||
|
|
@ -10,7 +10,16 @@ impl Urn {
|
||||||
unsafe { &*(p.as_ref() as *const Path as *const Self) }
|
unsafe { &*(p.as_ref() as *const Path as *const Self) }
|
||||||
}
|
}
|
||||||
|
|
||||||
// FIXME: remove this
|
#[inline]
|
||||||
|
pub fn name(&self) -> Option<&OsStr> { self.0.file_name() }
|
||||||
|
|
||||||
|
#[cfg(unix)]
|
||||||
|
#[inline]
|
||||||
|
pub fn is_hidden(&self) -> bool {
|
||||||
|
self.name().map_or(false, |s| s.as_encoded_bytes().starts_with(b"."))
|
||||||
|
}
|
||||||
|
|
||||||
|
// FIXME 1: remove this
|
||||||
pub fn _as_path(&self) -> &Path { &self.0 }
|
pub fn _as_path(&self) -> &Path { &self.0 }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -21,13 +30,21 @@ impl ToOwned for Urn {
|
||||||
}
|
}
|
||||||
|
|
||||||
// --- UrnBuf
|
// --- UrnBuf
|
||||||
|
#[derive(Clone, Debug, Eq, PartialEq, Hash)]
|
||||||
pub struct UrnBuf(PathBuf);
|
pub struct UrnBuf(PathBuf);
|
||||||
|
|
||||||
impl Borrow<Urn> for UrnBuf {
|
impl Borrow<Urn> for UrnBuf {
|
||||||
fn borrow(&self) -> &Urn { Urn::new(&self.0) }
|
fn borrow(&self) -> &Urn { Urn::new(&self.0) }
|
||||||
}
|
}
|
||||||
|
|
||||||
impl UrnBuf {
|
impl PartialEq<Urn> for UrnBuf {
|
||||||
// FIXME: remove this
|
fn eq(&self, other: &Urn) -> bool { self.0 == other.0 }
|
||||||
pub fn _deref(&self) -> &Urn { Urn::new(&self.0) }
|
}
|
||||||
|
|
||||||
|
impl UrnBuf {
|
||||||
|
// FIXME 1: remove this
|
||||||
|
pub fn _deref(&self) -> &Urn { Urn::new(&self.0) }
|
||||||
|
|
||||||
|
// FIXME 1: remove this
|
||||||
|
pub fn _from(p: impl Into<PathBuf>) -> Self { Self(p.into()) }
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue