yazi/yazi-fs/src/engine/traits.rs
三咲雅 misaki masa 9accf929f4
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
feat: trash bin (#4144)
2026-07-22 03:13:58 +08:00

272 lines
7.1 KiB
Rust

use std::io;
use tokio::{io::{AsyncRead, AsyncSeek, AsyncWrite, AsyncWriteExt}, sync::mpsc};
use yazi_macro::ok_or_not_found;
use yazi_shared::{path::{DynPath, PathBufDyn}, strand::{AsStrand, StrandCow}, url::{AsUrl, Url, UrlBuf}};
use crate::{cha::{Cha, ChaType}, engine::{Attrs, Capabilities}, file::{File, FileExtra}};
pub trait Engine: Sized {
type File: AsyncRead + AsyncSeek + AsyncWrite + Unpin;
type Demand: FileBuilder<File = Self::File>;
type ReadDir: DirReader + 'static;
type UrlCow;
type Me<'a>: Engine;
fn absolute(&self) -> impl Future<Output = io::Result<Self::UrlCow>>;
fn canonicalize(&self) -> impl Future<Output = io::Result<UrlBuf>>;
fn capabilities(&self) -> impl Future<Output = io::Result<Capabilities>>;
fn casefold(&self) -> impl Future<Output = io::Result<UrlBuf>>;
fn copy<P>(&self, to: P, attrs: Attrs) -> impl Future<Output = io::Result<u64>>
where
P: DynPath;
fn copy_progressive<P, A>(&self, to: P, attrs: A) -> io::Result<mpsc::Receiver<io::Result<u64>>>
where
P: DynPath,
A: Into<Attrs>;
fn create(&self) -> impl Future<Output = io::Result<Self::File>> {
async move { self.demand().write(true).create(true).truncate(true).open(self.url()).await }
}
fn create_dir(&self) -> impl Future<Output = io::Result<()>>;
fn create_dir_all(&self) -> impl Future<Output = io::Result<()>> {
async move {
let mut url = self.url();
if url.loc().is_empty() {
return Ok(());
}
let mut stack = Vec::new();
loop {
match Self::new(url).await?.create_dir().await {
Ok(()) => break,
Err(e) if e.kind() == io::ErrorKind::NotFound => {
if let Some(parent) = url.parent() {
stack.push(url);
url = parent;
} else {
return Err(io::Error::other("failed to create whole tree"));
}
}
Err(_) if Self::new(url).await?.metadata().await.is_ok_and(|m| m.is_dir()) => break,
Err(e) => return Err(e),
}
}
while let Some(u) = stack.pop() {
match Self::new(u).await?.create_dir().await {
Ok(()) => {}
Err(_) if Self::new(u).await?.metadata().await.is_ok_and(|m| m.is_dir()) => {}
Err(e) => return Err(e),
}
}
Ok(())
}
}
fn create_new(&self) -> impl Future<Output = io::Result<Self::File>> {
async move { self.demand().write(true).create_new(true).open(self.url()).await }
}
fn demand(&self) -> Self::Demand { Self::Demand::default() }
fn file(&self) -> impl Future<Output = io::Result<File>> {
async move {
let cha = self.symlink_metadata().await?;
let (mut followed, mut link_to) = (None, None);
if cha.is_link() {
followed = self.metadata().await.ok();
link_to = self.read_link().await.ok();
}
Ok(File {
url: self.url().to_owned(),
cha: cha.follow(followed),
extra: FileExtra::new(link_to, None),
})
}
}
fn hard_link<P>(&self, to: P) -> impl Future<Output = io::Result<()>>
where
P: DynPath;
fn metadata(&self) -> impl Future<Output = io::Result<Cha>>;
fn new<'a>(url: Url<'a>) -> impl Future<Output = io::Result<Self::Me<'a>>>;
fn open(&self) -> impl Future<Output = io::Result<Self::File>> {
async move { self.demand().read(true).open(self.url()).await }
}
fn read_dir(self) -> impl Future<Output = io::Result<Self::ReadDir>>;
fn read_link(&self) -> impl Future<Output = io::Result<PathBufDyn>>;
fn revalidate(&self, file: File) -> impl Future<Output = io::Result<Option<File>>> {
async move {
let new = self.file().await?;
if new.cha.hits(file.cha) { Ok(None) } else { Ok(Some(new)) }
}
}
fn remove_dir(&self) -> impl Future<Output = io::Result<()>>;
fn remove_dir_all(&self) -> impl Future<Output = io::Result<()>> {
async fn remove_dir_all_impl<P>(url: Url<'_>) -> io::Result<()>
where
P: Engine,
{
let mut it = ok_or_not_found!(P::new(url).await?.read_dir().await, return Ok(()));
while let Some(child) = it.next().await? {
let ft = ok_or_not_found!(child.file_type().await, continue);
let result = if ft.is_dir() {
Box::pin(remove_dir_all_impl::<P>(child.url().as_url())).await
} else {
P::new(child.url().as_url()).await?.remove_file().await
};
() = ok_or_not_found!(result);
}
Ok(ok_or_not_found!(P::new(url).await?.remove_dir().await))
}
async move {
let cha = ok_or_not_found!(self.symlink_metadata().await, return Ok(()));
if cha.is_link() {
self.remove_file().await
} else {
remove_dir_all_impl::<Self>(self.url()).await
}
}
}
fn remove_dir_clean(&self) -> impl Future<Output = io::Result<()>> {
let root = self.url().to_owned();
async move {
let mut stack = vec![(root, false)];
let mut result = Ok(());
while let Some((dir, visited)) = stack.pop() {
let Ok(engine) = Self::new(dir.as_url()).await else {
continue;
};
if visited {
result = engine.remove_dir().await;
} else if let Ok(mut it) = engine.read_dir().await {
stack.push((dir, true));
while let Ok(Some(ent)) = it.next().await {
if ent.file_type().await.is_ok_and(|t| t.is_dir()) {
stack.push((ent.url(), false));
}
}
}
}
result
}
}
fn remove_file(&self) -> impl Future<Output = io::Result<()>>;
fn rename<P>(&self, to: P) -> impl Future<Output = io::Result<()>>
where
P: DynPath;
fn set_attrs(&self, attrs: Attrs) -> impl Future<Output = io::Result<()>>;
fn symlink<S, F>(&self, original: S, _is_dir: F) -> impl Future<Output = io::Result<()>>
where
S: AsStrand,
F: AsyncFnOnce() -> io::Result<bool>;
fn symlink_dir<S>(&self, original: S) -> impl Future<Output = io::Result<()>>
where
S: AsStrand,
{
self.symlink(original, async || Ok(true))
}
fn symlink_file<S>(&self, original: S) -> impl Future<Output = io::Result<()>>
where
S: AsStrand,
{
self.symlink(original, async || Ok(false))
}
fn symlink_metadata(&self) -> impl Future<Output = io::Result<Cha>>;
fn trash(&self) -> impl Future<Output = io::Result<()>>;
fn url(&self) -> Url<'_>;
fn write<C>(&self, contents: C) -> impl Future<Output = io::Result<()>>
where
C: AsRef<[u8]>,
{
async move { self.create().await?.write_all(contents.as_ref()).await }
}
}
// --- DirReader
pub trait DirReader {
type Entry: FileHolder;
#[must_use]
fn next(&mut self) -> impl Future<Output = io::Result<Option<Self::Entry>>>;
}
// --- FileHolder
pub trait FileHolder {
#[must_use]
fn file(&self) -> impl Future<Output = io::Result<File>>;
#[must_use]
fn file_type(&self) -> impl Future<Output = io::Result<ChaType>>;
#[must_use]
fn metadata(&self) -> impl Future<Output = io::Result<Cha>>;
#[must_use]
fn name(&self) -> StrandCow<'_>;
#[must_use]
fn path(&self) -> PathBufDyn;
#[must_use]
fn url(&self) -> UrlBuf;
}
// --- FileBuilder
pub trait FileBuilder: Sized + Default {
type File: AsyncRead + AsyncSeek + AsyncWrite + Unpin;
fn append(&mut self, append: bool) -> &mut Self;
fn attrs(&mut self, attrs: Attrs) -> &mut Self;
fn create(&mut self, create: bool) -> &mut Self;
fn create_new(&mut self, create_new: bool) -> &mut Self;
fn open<U>(&self, url: U) -> impl Future<Output = io::Result<Self::File>>
where
U: AsUrl;
fn read(&mut self, read: bool) -> &mut Self;
fn truncate(&mut self, truncate: bool) -> &mut Self;
fn write(&mut self, write: bool) -> &mut Self;
}