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() {
Pubsub::pub_from_bulk(succeeded.iter().map(|(u, f)| (u, f.url())).collect());
FilesOp::Upserting(cwd, succeeded).emit();

View file

@ -28,9 +28,12 @@ impl Tab {
let Some(parent) = opt.target.parent_url() else {
return;
};
let Ok(file) = File::from_dummy(opt.target.clone(), None) else {
return;
};
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);
}
}

View file

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

View file

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

View file

@ -20,10 +20,10 @@ impl FilesSorter {
let by_alphabetical = |a: &File, b: &File| {
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 {

View file

@ -29,7 +29,7 @@ pub fn fd(opt: FdOpt) -> Result<UnboundedReceiver<File>> {
tokio::spawn(async move {
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();
}
}

View file

@ -28,7 +28,7 @@ pub fn rg(opt: RgOpt) -> Result<UnboundedReceiver<File>> {
tokio::spawn(async move {
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();
}
}

View file

@ -54,7 +54,7 @@ pub fn install(lua: &Lua) -> mlua::Result<()> {
),
(
"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") {
Some(
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 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,
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())
};
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)?;

View file

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

View file

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