perf: introduce URN to speed up large directory file updates, sorting and locating

This commit is contained in:
sxyazi 2024-09-09 15:26:09 +08:00
parent d20b3d8408
commit fe2af1025d
No known key found for this signature in database
12 changed files with 148 additions and 47 deletions

View file

@ -95,6 +95,7 @@ impl Manager {
} }
} }
// 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(); FilesOp::Upserting(cwd, succeeded).emit();

View file

@ -28,9 +28,12 @@ impl Tab {
let Some(parent) = opt.target.parent_url() else { let Some(parent) = opt.target.parent_url() else {
return; return;
}; };
let Ok(file) = File::from_dummy(opt.target.clone(), None) else {
return;
};
self.cd(parent.clone()); self.cd(parent.clone());
FilesOp::Creating(parent, vec![File::from_dummy(opt.target.clone(), None)]).emit(); FilesOp::Creating(parent, vec![file]).emit();
ManagerProxy::hover(Some(opt.target), self.idx); ManagerProxy::hover(Some(opt.target), self.idx);
} }
} }

View file

@ -44,11 +44,10 @@ impl Tab {
handle.abort(); handle.abort();
} }
let mut cwd = self.current.cwd.clone(); let cwd = self.current.cwd.to_search(&opt.subject);
let hidden = self.conf.show_hidden; let hidden = self.conf.show_hidden;
self.search = Some(tokio::spawn(async move { self.search = Some(tokio::spawn(async move {
cwd = cwd.into_search(opt.subject.clone());
let rx = if opt.via == SearchOptVia::Rg { let rx = if opt.via == SearchOptVia::Rg {
external::rg(external::RgOpt { external::rg(external::RgOpt {
cwd: cwd.clone(), cwd: cwd.clone(),

View file

@ -45,8 +45,8 @@ impl Deref for Files {
} }
impl Files { impl Files {
pub async fn from_dir(url: &Url) -> std::io::Result<UnboundedReceiver<File>> { pub async fn from_dir(dir: &Url) -> std::io::Result<UnboundedReceiver<File>> {
let mut it = fs::read_dir(url).await?; let mut it = fs::read_dir(dir).await?;
let (tx, rx) = mpsc::unbounded_channel(); let (tx, rx) = mpsc::unbounded_channel();
tokio::spawn(async move { tokio::spawn(async move {
@ -55,10 +55,13 @@ impl Files {
_ = tx.closed() => break, _ = tx.closed() => break,
result = item.metadata() => { result = item.metadata() => {
let url = Url::from(item.path()); let url = Url::from(item.path());
_ = tx.send(match result { let file = match result {
Ok(meta) => File::from_meta(url, meta).await, Ok(meta) => File::from_meta(url, meta).await,
Err(_) => File::from_dummy(url, item.file_type().await.ok()) Err(_) => File::from_dummy(url, item.file_type().await.ok())
}); };
if let Ok(f) = file {
_ = tx.send(f);
}
} }
} }
} }
@ -66,23 +69,26 @@ impl Files {
Ok(rx) Ok(rx)
} }
pub async fn from_dir_bulk(url: &Url) -> std::io::Result<Vec<File>> { pub async fn from_dir_bulk(dir: &Url) -> std::io::Result<Vec<File>> {
let mut it = fs::read_dir(url).await?; let mut it = fs::read_dir(dir).await?;
let mut items = Vec::with_capacity(5000); let mut entries = Vec::with_capacity(5000);
while let Ok(Some(item)) = it.next_entry().await { while let Ok(Some(entry)) = it.next_entry().await {
items.push(item); entries.push(entry);
} }
let (first, rest) = items.split_at(items.len() / 3); let (first, rest) = entries.split_at(entries.len() / 3);
let (second, third) = rest.split_at(items.len() / 3); let (second, third) = rest.split_at(entries.len() / 3);
async fn go(entities: &[DirEntry]) -> Vec<File> { async fn go(entries: &[DirEntry]) -> Vec<File> {
let mut files = Vec::with_capacity(entities.len() / 3 + 1); let mut files = Vec::with_capacity(entries.len() / 3 + 1);
for entry in entities { for entry in entries {
let url = Url::from(entry.path()); let url = Url::from(entry.path());
files.push(match entry.metadata().await { let file = match entry.metadata().await {
Ok(meta) => File::from_meta(url, meta).await, Ok(meta) => File::from_meta(url, meta).await,
Err(_) => File::from_dummy(url, entry.file_type().await.ok()), Err(_) => File::from_dummy(url, entry.file_type().await.ok()),
}); };
if let Ok(f) = file {
files.push(f);
}
} }
files files
} }

View file

@ -20,10 +20,10 @@ impl FilesSorter {
let by_alphabetical = |a: &File, b: &File| { let by_alphabetical = |a: &File, b: &File| {
if self.sensitive { if self.sensitive {
return self.cmp(a.name(), b.name(), self.promote(a, b)); self.cmp(a.name(), b.name(), self.promote(a, b))
} else {
self.cmp(a.name().to_ascii_uppercase(), b.name().to_ascii_uppercase(), self.promote(a, b))
} }
self.cmp(a.name().to_ascii_uppercase(), b.name().to_ascii_uppercase(), self.promote(a, b))
}; };
match self.by { match self.by {

View file

@ -29,7 +29,7 @@ pub fn fd(opt: FdOpt) -> Result<UnboundedReceiver<File>> {
tokio::spawn(async move { tokio::spawn(async move {
while let Ok(Some(line)) = it.next_line().await { while let Ok(Some(line)) = it.next_line().await {
if let Ok(file) = File::from(opt.cwd.join(line)).await { if let Ok(file) = File::from_search(&opt.cwd, opt.cwd.join(line)).await {
tx.send(file).ok(); tx.send(file).ok();
} }
} }

View file

@ -28,7 +28,7 @@ pub fn rg(opt: RgOpt) -> Result<UnboundedReceiver<File>> {
tokio::spawn(async move { tokio::spawn(async move {
while let Ok(Some(line)) = it.next_line().await { while let Ok(Some(line)) = it.next_line().await {
if let Ok(file) = File::from(opt.cwd.join(line)).await { if let Ok(file) = File::from_search(&opt.cwd, opt.cwd.join(line)).await {
tx.send(file).ok(); tx.send(file).ok();
} }
} }

View file

@ -54,7 +54,7 @@ pub fn install(lua: &Lua) -> mlua::Result<()> {
), ),
( (
"read_dir", "read_dir",
lua.create_async_function(|lua, (url, options): (UrlRef, Table)| async move { lua.create_async_function(|lua, (dir, options): (UrlRef, Table)| async move {
let glob = if let Ok(s) = options.raw_get::<_, mlua::String>("glob") { let glob = if let Ok(s) = options.raw_get::<_, mlua::String>("glob") {
Some( Some(
GlobBuilder::new(s.to_str()?) GlobBuilder::new(s.to_str()?)
@ -73,7 +73,7 @@ pub fn install(lua: &Lua) -> mlua::Result<()> {
let limit = options.raw_get("limit").unwrap_or(usize::MAX); let limit = options.raw_get("limit").unwrap_or(usize::MAX);
let resolve = options.raw_get("resolve").unwrap_or(false); let resolve = options.raw_get("resolve").unwrap_or(false);
let mut it = match fs::read_dir(&*url).await { let mut it = match fs::read_dir(&*dir).await {
Ok(it) => it, Ok(it) => it,
Err(e) => return (Value::Nil, e.raw_os_error()).into_lua_multi(lua), Err(e) => return (Value::Nil, e.raw_os_error()).into_lua_multi(lua),
}; };
@ -98,7 +98,9 @@ pub fn install(lua: &Lua) -> mlua::Result<()> {
yazi_shared::fs::File::from_dummy(url, next.file_type().await.ok()) yazi_shared::fs::File::from_dummy(url, next.file_type().await.ok())
}; };
files.push(File::cast(lua, file)?); if let Ok(f) = file {
files.push(File::cast(lua, f)?);
}
} }
let tbl = lua.create_table_with_capacity(files.len(), 0)?; let tbl = lua.create_table_with_capacity(files.len(), 0)?;

View file

@ -3,12 +3,13 @@ use std::{cell::Cell, ffi::OsStr, fs::{FileType, Metadata}, ops::Deref};
use anyhow::Result; use anyhow::Result;
use tokio::fs; use tokio::fs;
use super::Location;
use crate::{fs::{Cha, ChaKind, Url}, theme::IconCache}; use crate::{fs::{Cha, ChaKind, Url}, theme::IconCache};
#[derive(Clone, Debug, Default)] #[derive(Clone, Debug, Default)]
pub struct File { pub struct File {
pub cha: Cha, pub cha: Cha,
pub url: Url, location: Location,
pub link_to: Option<Url>, pub link_to: Option<Url>,
pub icon: Cell<IconCache>, pub icon: Cell<IconCache>,
} }
@ -29,16 +30,38 @@ impl File {
#[inline] #[inline]
pub async fn from(url: Url) -> Result<Self> { pub async fn from(url: Url) -> Result<Self> {
let meta = fs::symlink_metadata(&url).await?; let meta = fs::symlink_metadata(&url).await?;
Ok(Self::from_meta(url, meta).await) Self::from_meta(url, meta).await
} }
pub async fn from_meta(url: Url, mut meta: Metadata) -> Self { #[inline]
pub async fn from_meta(url: Url, meta: Metadata) -> Result<Self> {
Self::from_loc(Location::from(url)?, meta).await
}
#[inline]
pub fn from_dummy(url: Url, ft: Option<FileType>) -> Result<Self> {
Ok(Self {
cha: ft.map_or_else(Cha::dummy, Cha::from),
location: Location::from(url)?,
link_to: None,
icon: Default::default(),
})
}
#[inline]
pub async fn from_search(cwd: &Url, url: Url) -> Result<Self> {
let loc = Location::from_search(cwd, url)?;
let meta = fs::symlink_metadata(loc.url()).await?;
Self::from_loc(loc, meta).await
}
async fn from_loc(loc: Location, mut meta: Metadata) -> Result<Self> {
let mut ck = ChaKind::empty(); let mut ck = ChaKind::empty();
let (is_link, mut link_to) = (meta.is_symlink(), None); let (is_link, mut link_to) = (meta.is_symlink(), None);
if is_link { if is_link {
meta = fs::metadata(&url).await.unwrap_or(meta); meta = fs::metadata(loc.url()).await.unwrap_or(meta);
link_to = fs::read_link(&url).await.map(Url::from).ok(); link_to = fs::read_link(loc.url()).await.map(Url::from).ok();
} }
if is_link && meta.is_symlink() { if is_link && meta.is_symlink() {
@ -48,7 +71,7 @@ impl File {
} }
#[cfg(unix)] #[cfg(unix)]
if url.is_hidden() { if loc.url().is_hidden() {
ck |= ChaKind::HIDDEN; ck |= ChaKind::HIDDEN;
} }
#[cfg(windows)] #[cfg(windows)]
@ -59,26 +82,29 @@ impl File {
} }
} }
Self { cha: Cha::from(meta).with_kind(ck), url, link_to, icon: Default::default() } Ok(Self {
} cha: Cha::from(meta).with_kind(ck),
location: loc,
#[inline] link_to,
pub fn from_dummy(url: Url, ft: Option<FileType>) -> Self { icon: Default::default(),
Self { cha: ft.map_or_else(Cha::dummy, Cha::from), url: url.to_owned(), ..Default::default() } })
} }
} }
impl File { impl File {
// --- Url // --- Location
#[inline] #[inline]
pub fn url_owned(&self) -> Url { self.url.clone() } pub fn url(&self) -> &Url { self.location.url() }
#[inline] #[inline]
pub fn name(&self) -> Option<&OsStr> { self.url.file_name() } pub fn url_owned(&self) -> Url { self.url().clone() }
#[inline] #[inline]
pub fn stem(&self) -> Option<&OsStr> { self.url.file_stem() } pub fn name(&self) -> &OsStr { self.location.name() }
#[inline] #[inline]
pub fn parent(&self) -> Option<Url> { self.url.parent_url() } pub fn stem(&self) -> Option<&OsStr> { self.url().file_stem() }
#[inline]
pub fn parent(&self) -> Option<Url> { self.url().parent_url() }
} }

View file

@ -0,0 +1,62 @@
use std::{ffi::OsStr, path::Path};
use anyhow::{bail, Result};
use super::Url;
#[derive(Clone, Debug)]
pub(super) struct Location {
url: Url,
urn: *const OsStr,
name: *const OsStr,
}
unsafe impl Send for Location {}
impl Default for Location {
fn default() -> Self {
let url = Url::default();
let urn = url.as_os_str() as *const OsStr;
let name = url.as_os_str() as *const OsStr;
Self { url, urn, name }
}
}
impl Location {
pub(super) fn from(url: Url) -> Result<Self> {
if url.is_search() {
bail!("url is from search results: {url:?}");
}
let Some(name) = url.file_name() else {
bail!("url has no filename: {url:?}");
};
let urn = name as *const OsStr;
let name = name as *const OsStr;
Ok(Self { url, urn, name })
}
pub(super) fn from_search(cwd: &Url, url: Url) -> Result<Self> {
if !url.is_search() {
bail!("url is not from search results: {url:?}");
}
let Some(name) = url.file_name() else {
bail!("url has no filename: {url:?}");
};
let urn = url.strip_prefix(cwd).unwrap_or(&url).as_os_str() as *const OsStr;
let name = name as *const OsStr;
Ok(Self { url, urn, name })
}
}
impl Location {
#[inline]
pub(super) fn url(&self) -> &Url { &self.url }
#[inline]
pub(super) fn urn(&self) -> &Path { Path::new(unsafe { &*self.urn }) }
#[inline]
pub(super) fn name(&self) -> &OsStr { unsafe { &*self.name } }
}

View file

@ -1,6 +1,7 @@
mod cha; mod cha;
mod file; mod file;
mod fns; mod fns;
mod location;
mod op; mod op;
mod path; mod path;
mod url; mod url;
@ -8,6 +9,7 @@ mod url;
pub use cha::*; pub use cha::*;
pub use file::*; pub use file::*;
pub use fns::*; pub use fns::*;
pub use location::*;
pub use op::*; pub use op::*;
pub use path::*; pub use path::*;
pub use url::*; pub use url::*;

View file

@ -179,12 +179,12 @@ impl Url {
pub fn is_search(&self) -> bool { self.scheme == UrlScheme::Search } pub fn is_search(&self) -> bool { self.scheme == UrlScheme::Search }
#[inline] #[inline]
pub fn to_search(&self, frag: String) -> Self { self.clone().into_search(frag) } pub fn to_search(&self, frag: &str) -> Self { self.clone().into_search(frag) }
#[inline] #[inline]
pub fn into_search(mut self, frag: String) -> Self { pub fn into_search(mut self, frag: &str) -> Self {
self.scheme = UrlScheme::Search; self.scheme = UrlScheme::Search;
self.frag = frag; self.frag = frag.to_owned();
self self
} }