Compare commits

..

2 commits

Author SHA1 Message Date
dev_Hakaze
68ef7cdf96
Merge 751d5729bc into 9accf929f4 2026-07-22 03:32:22 +08:00
三咲雅 misaki masa
9accf929f4
feat: trash bin (#4144)
Some checks are pending
Cachix / Publish Flake (push) Waiting to run
Check / clippy (push) Waiting to run
Check / rustfmt (push) Waiting to run
Check / stylua (push) Waiting to run
Draft / build-unix (gcc-aarch64-linux-gnu, ubuntu-latest, aarch64-unknown-linux-gnu) (push) Waiting to run
Draft / build-unix (gcc-i686-linux-gnu, ubuntu-latest, i686-unknown-linux-gnu) (push) Waiting to run
Draft / build-unix (gcc-riscv64-linux-gnu, ubuntu-latest, riscv64gc-unknown-linux-gnu) (push) Waiting to run
Draft / build-unix (gcc-sparc64-linux-gnu, ubuntu-latest, sparc64-unknown-linux-gnu) (push) Waiting to run
Draft / build-unix (macos-latest, aarch64-apple-darwin) (push) Waiting to run
Draft / build-unix (macos-latest, x86_64-apple-darwin) (push) Waiting to run
Draft / build-unix (ubuntu-latest, x86_64-unknown-linux-gnu) (push) Waiting to run
Draft / build-windows (windows-latest, aarch64-pc-windows-msvc) (push) Waiting to run
Draft / build-windows (windows-latest, x86_64-pc-windows-msvc) (push) Waiting to run
Draft / build-musl (aarch64-unknown-linux-musl) (push) Waiting to run
Draft / build-musl (x86_64-unknown-linux-musl) (push) Waiting to run
Draft / build-snap (amd64, ubuntu-latest) (push) Waiting to run
Draft / build-snap (arm64, ubuntu-24.04-arm) (push) Waiting to run
Draft / snap (push) Blocked by required conditions
Draft / draft (push) Blocked by required conditions
Draft / nightly (push) Blocked by required conditions
Test / test (macos-latest) (push) Waiting to run
Test / test (ubuntu-latest) (push) Waiting to run
Test / test (windows-latest) (push) Waiting to run
2026-07-22 03:13:58 +08:00
22 changed files with 99 additions and 236 deletions

1
Cargo.lock generated
View file

@ -4976,7 +4976,6 @@ dependencies = [
"regex",
"serde",
"serde_with",
"strum",
"toml",
"tracing",
"yazi-binding",

View file

@ -4,7 +4,7 @@ use anyhow::Result;
use futures::{StreamExt, stream::FuturesUnordered};
use hashbrown::HashSet;
use yazi_core::mgr::OpenOpt;
use yazi_fs::{FsAuth, FsUrl, engine::{Engine, local::Local}};
use yazi_fs::{FsSpec, engine::{Engine, local::Local}};
use yazi_macro::succ;
use yazi_parser::mgr::DownloadForm;
use yazi_proxy::MgrProxy;
@ -74,12 +74,10 @@ impl Actor for Download {
impl Download {
async fn prepare(urls: &[UrlBuf]) {
let stamp_roots = urls.iter().filter_map(|u| u.auth().stamp_root());
let bucket_dirs = urls.iter().filter_map(|u| u.parent()?.cache_bucket());
let dirs: HashSet<_> = stamp_roots.chain(bucket_dirs).collect();
for dir in dirs {
Local::regular(&dir).create_dir_all().await.ok();
let roots: HashSet<_> = urls.iter().filter_map(|u| u.auth().cache()).collect();
for mut root in roots {
root.push("%lock");
Local::regular(&root).create_dir_all().await.ok();
}
}
}

View file

@ -219,8 +219,9 @@ macro_rules! impl_file_fields {
Ok(PathBufDyn::from(me.content_path()))
});
$fields.add_cached_field("cache", |_, me| {
use yazi_fs::FsUrl;
use yazi_shared::path::PathBufDyn;
Ok(me.cache().map(PathBufDyn::from))
Ok(me.url.cache().map(PathBufDyn::from))
});
};
}

View file

@ -37,6 +37,5 @@ ratatui-widgets = { workspace = true }
regex = { workspace = true }
serde = { workspace = true }
serde_with = { workspace = true }
strum = { workspace = true }
toml = { workspace = true }
tracing = { workspace = true }

View file

@ -2,13 +2,13 @@ use std::{fmt::Debug, str::FromStr};
use anyhow::{Result, bail};
use globset::{Candidate, GlobBuilder};
use serde_with::DeserializeFromStr;
use strum::EnumIs;
use serde::Deserialize;
use yazi_shared::{auth::Auth, url::AsUrl};
use crate::Mixable;
#[derive(Clone, DeserializeFromStr)]
#[derive(Clone, Deserialize)]
#[serde(try_from = "String")]
pub struct Pattern {
inner: globset::GlobMatcher,
scheme: PatternScheme,
@ -98,14 +98,21 @@ impl FromStr for Pattern {
}
}
impl Mixable for Pattern {
fn any_file(&self) -> bool { self.is_star && !self.is_dir && self.scheme.is_any() }
// FIXME: remove
impl TryFrom<String> for Pattern {
type Error = anyhow::Error;
fn any_dir(&self) -> bool { self.is_star && self.is_dir && self.scheme.is_any() }
fn try_from(s: String) -> Result<Self, Self::Error> { Self::from_str(s.as_str()) }
}
impl Mixable for Pattern {
fn any_file(&self) -> bool { self.is_star && !self.is_dir }
fn any_dir(&self) -> bool { self.is_star && self.is_dir }
}
// --- Scheme
#[derive(Clone, Debug, EnumIs)]
#[derive(Clone, Debug)]
enum PatternScheme {
Any,
Local,

View file

@ -67,9 +67,9 @@ impl UserData for Cha {
}
fn add_methods<M: UserDataMethods<Self>>(methods: &mut M) {
methods.add_method("hash", |lua, me, long: bool| {
methods.add_method("hash", |_, me, long: bool| {
Ok(if long {
lua.create_string(me.hash_u128_str(&mut [0; 26]))
format!("{:x}", me.hash_u128())
} else {
Err("Short hash not supported".into_lua_err())?
})

View file

@ -48,7 +48,7 @@ impl Cwd {
pub fn ensure(url: Url) -> Cow<Path> {
use std::{io::ErrorKind::{AlreadyExists, NotADirectory, NotFound}, path::Component as C};
let Some(cache) = url.cache_bucket() else {
let Some(cache) = url.cache() else {
return url.working_path();
};

View file

@ -1,4 +1,4 @@
use std::{borrow::Cow, ops::Deref, path::{Path, PathBuf}};
use std::{borrow::Cow, ops::Deref, path::Path};
use serde::{Deserialize, Serialize};
use yazi_shared::{path::PathDyn, strand::Strand, url::{AsUrl, Url, UrlBuf, UrlLike}};
@ -40,11 +40,6 @@ impl AsUrl for &File {
}
impl File {
#[inline]
pub fn cache(&self) -> Option<PathBuf> {
if self.is_dir() { self.url.cache_bucket() } else { self.url.cache_entry() }
}
#[inline]
pub fn from_dummy(url: impl Into<UrlBuf>, r#type: Option<ChaType>) -> Self {
let url = url.into();
@ -64,7 +59,7 @@ impl File {
} else if let Some(local) = self.url.as_local() {
local.into()
} else {
self.cache().expect("non-local URL should have a cache path").into()
self.url.cache().expect("non-local URL should have a cache path").into()
}
}
}

View file

@ -9,10 +9,6 @@ use crate::{cha::Cha, file::FileSig};
pub trait FsHash64 {
fn hash_u64(&self) -> u64;
fn hash_u64_str<'a>(&self, buf: &'a mut [u8; 13]) -> &'a str {
BASE32_NOPAD.encode_mut_str(&self.hash_u64().to_be_bytes(), buf)
}
}
impl FsHash64 for UrlBuf {
@ -27,7 +23,7 @@ impl FsHash64 for FileSig<'_> {
pub trait FsHash128 {
fn hash_u128(&self) -> u128;
fn hash_u128_str<'a>(&self, buf: &'a mut [u8; 26]) -> &'a str {
fn hash_base32<'a>(&self, buf: &'a mut [u8; 26]) -> &'a str {
BASE32_NOPAD.encode_mut_str(&self.hash_u128().to_be_bytes(), buf)
}
}
@ -79,11 +75,11 @@ impl FsHash128 for FileSig<'_> {
inventory::submit! {
UrlBufInventory {
register: |registry| {
registry.add_method("hash", |lua, me, long: bool| {
registry.add_method("hash", |_, me, long: bool| {
Ok(if long {
lua.create_string(me.hash_u128_str(&mut [0; 26]))
format!("{:x}", me.hash_u128())
} else {
lua.create_string(me.hash_u64_str(&mut [0; 13]))
format!("{:x}", me.hash_u64())
})
});
}

View file

@ -2,7 +2,7 @@ extern crate self as yazi_fs;
yazi_macro::mod_pub!(cha file mounts path engine trash);
yazi_macro::mod_flat!(auth cwd entries filter fns hash op sorter sorting splatter stage url xdg);
yazi_macro::mod_flat!(cwd spec entries filter fns hash op sorter sorting splatter stage url xdg);
pub fn init() {
CWD.init(<_>::default());

View file

@ -5,19 +5,12 @@ use yazi_shim::{mlua::UserDataFieldsExt, strum::IntoStr};
use crate::{FsHash128, Xdg};
pub trait FsAuth {
fn cache_root(&self) -> Option<PathBuf>;
fn stamp_root(&self) -> Option<PathBuf> {
self.cache_root().map(|mut root| {
root.push("%stamp");
root
})
}
pub trait FsSpec {
fn cache(&self) -> Option<PathBuf>;
}
impl FsAuth for Auth {
fn cache_root(&self) -> Option<PathBuf> {
impl FsSpec for Auth {
fn cache(&self) -> Option<PathBuf> {
match self.kind {
AuthKind::Regular | AuthKind::Search => None,
AuthKind::Mount | AuthKind::Hub | AuthKind::Scope | AuthKind::Sftp => {
@ -25,7 +18,7 @@ impl FsAuth for Auth {
"{}_{}_{}",
self.kind.into_str(),
self.scheme,
self.domain.hash_u128_str(&mut [0; 26])
self.domain.hash_base32(&mut [0; 26])
)))
}
}
@ -36,8 +29,7 @@ impl FsAuth for Auth {
inventory::submit! {
SpecInventory {
register: |registry| {
registry.add_cached_field("cache", |_, me| Ok(me.cache_root().map(PathBufDyn::from)));
registry.add_cached_field("stamp", |_, me| Ok(me.stamp_root().map(PathBufDyn::from)));
registry.add_cached_field("cache", |_, me| Ok(me.cache().map(PathBufDyn::from)));
}
}
}

View file

@ -4,38 +4,28 @@ use mlua::UserDataFields;
use yazi_shared::url::{AsUrl, Url, UrlBuf, UrlBufInventory, UrlCow, UrlLike};
use yazi_shim::mlua::UserDataFieldsExt;
use crate::{FsAuth, FsHash128};
use crate::{FsHash128, FsSpec};
pub trait FsUrl<'a> {
fn cache_bucket(&self) -> Option<PathBuf>;
fn cache(&self) -> Option<PathBuf>;
fn cache_entry(&self) -> Option<PathBuf>;
fn stamp_entry(&self) -> Option<PathBuf>;
fn cache_lock(&self) -> Option<PathBuf>;
fn working_path(self) -> Cow<'a, Path>;
}
impl<'a> FsUrl<'a> for Url<'a> {
fn cache_bucket(&self) -> Option<PathBuf> {
self.auth().cache_root().map(|mut root| {
root.push(self.hash_u128_str(&mut [0; 26]));
fn cache(&self) -> Option<PathBuf> {
self.auth().cache().map(|mut root| {
root.push(self.hash_base32(&mut [0; 26]));
root
})
}
fn cache_entry(&self) -> Option<PathBuf> {
let parent = self.parent()?;
parent.auth().cache_root().map(|mut root| {
root.push(parent.hash_u128_str(&mut [0; 26]));
root.push(self.hash_u128_str(&mut [0; 26]));
root
})
}
fn stamp_entry(&self) -> Option<PathBuf> {
self.auth().stamp_root().map(|mut root| {
root.push(self.hash_u128_str(&mut [0; 26]));
fn cache_lock(&self) -> Option<PathBuf> {
self.auth().cache().map(|mut root| {
root.push("%lock");
root.push(self.hash_base32(&mut [0; 26]));
root
})
}
@ -44,41 +34,37 @@ impl<'a> FsUrl<'a> for Url<'a> {
match self {
Self::Regular(loc) | Self::Search { loc, .. } => loc.as_inner().into(),
Self::Mount { .. } | Self::Hub { .. } | Self::Scope { .. } | Self::Sftp { .. } => {
self.cache_bucket().expect("non-local URL should have a cache path").into()
self.cache().expect("non-local URL should have a cache path").into()
}
}
}
}
impl FsUrl<'_> for UrlBuf {
fn cache_bucket(&self) -> Option<PathBuf> { self.as_url().cache_bucket() }
fn cache(&self) -> Option<PathBuf> { self.as_url().cache() }
fn cache_entry(&self) -> Option<PathBuf> { self.as_url().cache_entry() }
fn stamp_entry(&self) -> Option<PathBuf> { self.as_url().stamp_entry() }
fn cache_lock(&self) -> Option<PathBuf> { self.as_url().cache_lock() }
fn working_path(self) -> Cow<'static, Path> {
match self {
Self::Regular(loc) | Self::Search { loc, .. } => loc.into_inner().into(),
Self::Mount { .. } | Self::Hub { .. } | Self::Scope { .. } | Self::Sftp { .. } => {
self.cache_bucket().expect("non-local URL should have a cache path").into()
self.cache().expect("non-local URL should have a cache path").into()
}
}
}
}
impl<'a> FsUrl<'a> for UrlCow<'a> {
fn cache_bucket(&self) -> Option<PathBuf> { self.as_url().cache_bucket() }
fn cache(&self) -> Option<PathBuf> { self.as_url().cache() }
fn cache_entry(&self) -> Option<PathBuf> { self.as_url().cache_entry() }
fn stamp_entry(&self) -> Option<PathBuf> { self.as_url().stamp_entry() }
fn cache_lock(&self) -> Option<PathBuf> { self.as_url().cache_lock() }
fn working_path(self) -> Cow<'a, Path> {
match self {
Self::Regular(loc) | Self::Search { loc, .. } => loc.into_inner(),
Self::Mount { .. } | Self::Hub { .. } | Self::Scope { .. } | Self::Sftp { .. } => {
self.cache_bucket().expect("non-local URL should have a cache path").into()
self.cache().expect("non-local URL should have a cache path").into()
}
}
}
@ -88,6 +74,7 @@ impl<'a> FsUrl<'a> for UrlCow<'a> {
inventory::submit! {
UrlBufInventory {
register: |registry| {
registry.add_cached_field("cache", |_, me| Ok(me.cache()));
registry.add_cached_field("domain", |lua, me| {
yazi_binding::deprecate!(lua, "{}: `Url.domain` is deprecated, use `Url.spec.domain` instead.");
lua.create_string(&*me.spec().domain)

View file

@ -2,16 +2,16 @@ local M = {}
local function stale_cache(file)
local url = file.url
local stamp = url.spec.stamp:join(url:hash(true))
local lock = url.spec.cache:join(string.format("%%lock/%s", url:hash(true)))
local fd = fs.access():read(true):open(Url(stamp))
local fd = fs.access():read(true):open(Url(lock))
if not fd then
return true
end
local sig = fd:read(26)
local hash = fd:read(32)
ya.drop(fd)
return sig ~= file.cha:hash(true)
return hash ~= file.cha:hash(true)
end
function M:fetch(job)

View file

@ -29,7 +29,7 @@ impl Utils {
}
let sig = Sig(FileSig(f), t.raw_get("skip").unwrap_or_default());
Ok(Some(UrlBuf::from(YAZI.preview.cache_dir.join(sig.hash_u128_str(&mut [0; 26])))))
Ok(Some(UrlBuf::from(YAZI.preview.cache_dir.join(sig.hash_base32(&mut [0; 26])))))
})
})
}

View file

@ -6,7 +6,7 @@ use tracing::warn;
use yazi_config::YAZI;
use yazi_fs::{Cwd, FsHash128, FsUrl, cha::Cha, engine::{Attrs, Engine, FileHolder, local::Local}, ok_or_not_found, path::path_relative_to};
use yazi_shared::{path::PathCow, url::{AsUrl, UrlCow, UrlLike}};
use yazi_vfs::{Stamp, VfsCha, engine::{self, DirEntry}, maybe_exists, unique_file};
use yazi_vfs::{VfsCha, engine::{self, DirEntry}, maybe_exists, unique_file};
use super::{FileInCopy, FileInDelete, FileInHardlink, FileInLink, FileInTrash};
use crate::{LOW, NORMAL, TaskOp, TaskOps, TasksProxy, ctx, file::{FileIn, FileInCut, FileInDownload, FileInUpload, FileOutCopy, FileOutCopyDo, FileOutCut, FileOutCutDo, FileOutDelete, FileOutDeleteDo, FileOutDownload, FileOutDownloadDo, FileOutHardlink, FileOutHardlinkDo, FileOutLink, FileOutTrash, FileOutUpload, FileOutUploadDo, Transaction, Traverse}, hook::{HookInOutCopy, HookInOutCut, HookInOutHardlink, HookInOutLink}, ok_or_not_found};
@ -342,7 +342,7 @@ impl File {
) -> Result<(), FileOutDownloadDo> {
let cha = task.cha.unwrap();
let cache = ctx!(task, task.target.cache_entry(), "Cannot determine cache path")?;
let cache = ctx!(task, task.target.cache(), "Cannot determine cache path")?;
let cache_tmp = ctx!(task, Transaction::tmp(&cache).await, "Cannot determine download cache")?;
let mut rx = ctx!(task, engine::copy_progressive(&task.target, &cache_tmp, cha).await)?;
@ -350,8 +350,12 @@ impl File {
match rx.recv().await.unwrap_or(Ok(0)) {
Ok(0) => {
Local::regular(&cache).remove_dir_all().await.ok();
ctx!(task, Stamp::write(cha, task.target.as_url()).await)?;
ctx!(task, engine::rename(cache_tmp, cache).await, "Cannot persist downloaded file")?;
let lock = ctx!(task, task.target.cache_lock(), "Cannot determine cache lock")?;
let hash = format!("{:x}", cha.hash_u128());
ctx!(task, Local::regular(&lock).write(hash).await, "Cannot lock cache")?;
break;
}
Ok(n) => self.ops.out(task.id, FileOutDownloadDo::Adv(n)),
@ -406,9 +410,13 @@ impl File {
pub(crate) async fn upload_do(&self, task: FileInUpload) -> Result<(), FileOutUploadDo> {
let cha = task.cha.unwrap();
let cache = ctx!(task, task.cache.as_ref(), "Cannot determine cache path")?;
let lock = ctx!(task, task.target.cache_lock(), "Cannot determine cache lock")?;
let stamp = ctx!(task, Stamp::read(&task.target).await)?;
ctx!(task, stamp.validate(cha, task.target.as_url()))?;
let hash = ctx!(task, Local::regular(&lock).read_to_string().await, "Cannot read cache lock")?;
let hash = ctx!(task, u128::from_str_radix(&hash, 16), "Cannot parse hash from lock")?;
if hash != cha.hash_u128() {
Err(anyhow!("Failed to work on: {task:?}: remote file has changed since last download"))?;
}
let tmp =
ctx!(task, Transaction::tmp(&task.target).await, "Cannot determine temporary upload path")?;
@ -428,7 +436,7 @@ impl File {
Ok(0) => {
let cha =
ctx!(task, Self::cha(&task.target, true, None).await, "Cannot stat original file")?;
if stamp.sig() != cha.hash_u128_str(&mut [0; 26]) {
if hash != cha.hash_u128() {
Err(anyhow!("Failed to work on: {task:?}: remote file has changed during upload"))?;
}
@ -436,7 +444,8 @@ impl File {
let cha =
ctx!(task, Self::cha(&task.target, true, None).await, "Cannot stat uploaded file")?;
ctx!(task, Stamp::write(cha, task.target.as_url()).await)?;
let hash = format!("{:x}", cha.hash_u128());
ctx!(task, Local::regular(&lock).write(hash).await, "Cannot lock cache")?;
break;
}

View file

@ -131,13 +131,13 @@ impl Traverse for FileInUpload {
self.cha = Some(super::File::cha(self.from(), self.follow(), None).await?)
}
if self.cache.is_none() {
self.cache = self.target.cache_entry();
self.cache = self.target.cache();
}
Ok(self.cha.unwrap())
}
fn spawn(&self, from: UrlBuf, _to: Option<UrlBuf>, cha: Cha) -> Self {
Self { id: self.id, cha: Some(cha), cache: from.cache_entry(), target: from }
Self { id: self.id, cha: Some(cha), cache: from.cache(), target: from }
}
fn to(&self) -> Option<Url<'_>> { None }

View file

@ -1,5 +1,5 @@
yazi_macro::mod_pub!(engine);
yazi_macro::mod_flat!(cha entries file fns stamp);
yazi_macro::mod_flat!(cha entries file fns);
pub fn init() { engine::init(); }

View file

@ -1,108 +0,0 @@
use std::{io, path::Path, str};
use yazi_fs::{FsAuth, FsHash128, FsUrl, cha::Cha, engine::{Engine, local::Local}};
use yazi_shared::{strand::{AsStrand, StrandCow}, url::{AsUrl, Url, UrlBuf}};
pub struct Stamp(Vec<u8>);
impl Stamp {
const SIG_LEN: usize = 26;
pub async fn read<U>(url: U) -> io::Result<Self>
where
U: AsUrl,
{
let path =
url.as_url().stamp_entry().ok_or_else(|| io::Error::other("Cannot determine cache stamp"))?;
Self::read_at(&path).await
}
async fn read_at(path: &Path) -> io::Result<Self> {
let data = Local::regular(path)
.read()
.await
.map_err(|e| io::Error::new(e.kind(), format!("Cannot read cache stamp: {e}")))?;
Self::try_from(data)
.map_err(|e| io::Error::new(e.kind(), format!("Cannot parse cache stamp: {e}")))
}
pub async fn resolve<U, S>(dir: U, key: S) -> io::Result<UrlBuf>
where
U: AsUrl,
S: AsStrand,
{
let dir = dir.as_url();
let key = key.as_strand();
let mut path =
dir.auth().stamp_root().ok_or_else(|| io::Error::other("Cannot determine stamp root"))?;
path.push(key.as_os()?);
let stamp = Self::read_at(&path).await?;
let name = StrandCow::with(dir.kind(), stamp.name()).map_err(io::Error::other)?;
let url = dir.try_join(name)?;
if url.hash_u128_str(&mut [0; Self::SIG_LEN]).as_bytes() != key.encoded_bytes() {
return Err(io::Error::new(io::ErrorKind::InvalidData, "Cache stamp does not match entry"));
}
Ok(url)
}
pub async fn write(cha: Cha, url: Url<'_>) -> io::Result<()> {
let path = url.stamp_entry().ok_or_else(|| io::Error::other("Cannot determine cache stamp"))?;
let data = Self::encode(cha, url)?;
Local::regular(&path)
.write(data)
.await
.map_err(|e| io::Error::new(e.kind(), format!("Cannot write cache stamp: {e}")))
}
fn encode(cha: Cha, url: Url) -> io::Result<Vec<u8>> {
let name = url.name().ok_or_else(|| io::Error::other("URL has no filename"))?;
let mut buf = Vec::with_capacity(Self::SIG_LEN + name.len());
buf.extend_from_slice(cha.hash_u128_str(&mut [0; Self::SIG_LEN]).as_bytes());
buf.extend_from_slice(name.encoded_bytes());
Ok(buf)
}
pub fn validate(&self, cha: Cha, url: Url) -> io::Result<()> {
let name = url.name().ok_or_else(|| io::Error::other("URL has no filename"))?;
if self.name() != name.encoded_bytes() {
return Err(io::Error::new(io::ErrorKind::InvalidData, "Cache stamp does not match target"));
}
if self.sig() != cha.hash_u128_str(&mut [0; Self::SIG_LEN]) {
return Err(io::Error::new(
io::ErrorKind::InvalidData,
"Remote file has changed since last download",
));
}
Ok(())
}
#[inline]
pub fn sig(&self) -> &str { unsafe { str::from_utf8_unchecked(&self.0[..Self::SIG_LEN]) } }
#[inline]
pub fn name(&self) -> &[u8] { &self.0[Self::SIG_LEN..] }
}
impl TryFrom<Vec<u8>> for Stamp {
type Error = io::Error;
fn try_from(value: Vec<u8>) -> Result<Self, Self::Error> {
let (sig, _) = value
.split_at_checked(Self::SIG_LEN)
.filter(|(_, n)| !n.is_empty())
.ok_or_else(|| io::Error::new(io::ErrorKind::InvalidData, "Invalid cache stamp"))?;
str::from_utf8(sig).map_err(|e| io::Error::new(io::ErrorKind::InvalidData, e))?;
Ok(Self(value))
}
}

View file

@ -1,12 +1,12 @@
use tokio::sync::mpsc;
use yazi_shared::{auth::AuthKind, url::{AsUrl, Url, UrlBuf, UrlCow, UrlLike}};
use crate::{WATCHED, local::LINKED, r#virtual::VirtualReport};
use crate::{WATCHED, local::LINKED};
#[derive(Clone)]
pub(crate) struct Reporter {
pub(super) local_tx: mpsc::UnboundedSender<UrlBuf>,
pub(super) virtual_tx: mpsc::UnboundedSender<VirtualReport>,
pub(super) virtual_tx: mpsc::UnboundedSender<(UrlBuf, bool)>,
}
impl Reporter {
@ -45,8 +45,11 @@ impl Reporter {
// Virtual caches
let Some(dir) = watched.find_by_cache(parent.loc()) else { continue };
let Some(key) = url.name() else { continue };
self.virtual_tx.send(VirtualReport::Cache(dir, key.to_owned())).ok();
let Some(name) = url.name() else { continue };
if let Ok(u) = dir.try_join(name) {
self.virtual_tx.send((u, true)).ok();
}
self.virtual_tx.send((dir, false)).ok();
}
}
@ -56,7 +59,7 @@ impl Reporter {
return;
}
self.virtual_tx.send(VirtualReport::Url(parent.to_owned())).ok();
self.virtual_tx.send(VirtualReport::Url(url.into_owned())).ok();
self.virtual_tx.send((parent.to_owned(), false)).ok();
self.virtual_tx.send((url.into_owned(), false)).ok();
}
}

View file

@ -1,25 +1,19 @@
use std::{io, time::{Duration, SystemTime}};
use hashbrown::{HashMap, HashSet};
use hashbrown::HashMap;
use notify::Result;
use tokio::{pin, sync::mpsc::UnboundedReceiver};
use tokio_stream::{StreamExt, wrappers::UnboundedReceiverStream};
use yazi_fs::FilesOp;
use yazi_shared::{strand::StrandBuf, url::{UrlBuf, UrlLike}};
use yazi_vfs::{Stamp, engine};
use yazi_shared::url::{UrlBuf, UrlLike};
use yazi_vfs::engine;
use crate::{MgrProxy, WATCHER, Watchee};
pub(crate) struct Virtual;
#[derive(Hash, PartialEq, Eq)]
pub(crate) enum VirtualReport {
Url(UrlBuf),
Cache(UrlBuf, StrandBuf),
}
impl Virtual {
pub(crate) fn serve(rx: UnboundedReceiver<VirtualReport>) -> Self {
pub(crate) fn serve(rx: UnboundedReceiver<(UrlBuf, bool)>) -> Self {
tokio::spawn(Self::changed(rx));
Self
@ -29,23 +23,14 @@ impl Virtual {
pub(crate) fn unwatch(&mut self, _watchee: &Watchee) -> Result<()> { Ok(()) }
async fn changed(rx: UnboundedReceiver<VirtualReport>) {
async fn changed(rx: UnboundedReceiver<(UrlBuf, bool)>) {
let rx = UnboundedReceiverStream::new(rx).chunks_timeout(1000, Duration::from_millis(250));
pin!(rx);
while let Some(chunk) = rx.next().await {
let reports: HashSet<_> = chunk.into_iter().collect();
let mut urls = HashMap::with_capacity(reports.len());
for report in reports {
match report {
VirtualReport::Url(url) => _ = urls.entry(url).or_insert(false),
VirtualReport::Cache(dir, key) if let Ok(file) = Stamp::resolve(&dir, &key).await => {
urls.insert(file, true);
urls.entry(dir).or_insert(false);
}
VirtualReport::Cache(..) => {}
}
let mut urls = HashMap::with_capacity(chunk.len());
for (u, upload) in chunk {
urls.entry(u).and_modify(|b| *b |= upload).or_insert(upload);
}
let _permit = WATCHER.acquire().await.unwrap();

View file

@ -22,7 +22,7 @@ impl Deref for Watched {
impl Watched {
pub(super) fn insert(&mut self, watchee: Watchee<'static>) {
let url = watchee.as_url();
if let Some(cache) = url.cache_bucket() {
if let Some(cache) = url.cache() {
self.caches.insert(cache, url.into());
}
self.urls.insert(watchee);
@ -32,7 +32,7 @@ impl Watched {
if !self.urls.remove(watchee) {
return false;
}
if let Some(cache) = watchee.as_url().cache_bucket() {
if let Some(cache) = watchee.as_url().cache() {
self.caches.remove(&cache);
}
true

View file

@ -36,7 +36,7 @@ impl Watcher {
for file in it.map(Into::into) {
if !file.url.is_absolute() {
continue;
} else if let Some(cache) = file.url.cache_bucket() {
} else if let Some(cache) = file.url.cache() {
urls.insert(cache.into());
}
urls.insert(file.url.clone());