2025-07-21 01:41:03 +02:00
|
|
|
use std::fs::Metadata;
|
2026-04-12 19:55:56 +02:00
|
|
|
use std::path::{Path, PathBuf};
|
|
|
|
|
use tokio::fs;
|
2025-07-21 01:41:03 +02:00
|
|
|
use tokio::sync::mpsc;
|
|
|
|
|
|
2026-04-12 19:55:56 +02:00
|
|
|
pub type Result<T> = std::result::Result<T, Error>;
|
|
|
|
|
|
2026-04-13 21:11:07 +02:00
|
|
|
pub use tokio::fs::ReadDir;
|
|
|
|
|
|
2026-04-12 19:55:56 +02:00
|
|
|
#[derive(Debug, thiserror::Error)]
|
|
|
|
|
pub enum Error {
|
2026-04-13 21:11:07 +02:00
|
|
|
#[error("{0}: read dir: {1}")]
|
2026-04-12 19:55:56 +02:00
|
|
|
ReadDir(PathBuf, std::io::Error),
|
2026-04-13 21:11:07 +02:00
|
|
|
#[error("{0}: exists: {1}")]
|
|
|
|
|
Exists(PathBuf, std::io::Error),
|
2026-04-12 19:55:56 +02:00
|
|
|
#[error("{0}: read: {1}")]
|
|
|
|
|
Read(PathBuf, std::io::Error),
|
|
|
|
|
#[error("{0}: stat: {1}")]
|
|
|
|
|
Stat(PathBuf, std::io::Error),
|
2026-04-13 21:11:07 +02:00
|
|
|
#[error("{0}: create dir: {1}")]
|
|
|
|
|
CreateDir(PathBuf, std::io::Error),
|
2026-04-12 19:55:56 +02:00
|
|
|
#[error("{0}: write: {1}")]
|
|
|
|
|
Write(PathBuf, std::io::Error),
|
2026-04-13 21:11:07 +02:00
|
|
|
#[error("{0}: remove file: {1}")]
|
|
|
|
|
RemoveFile(PathBuf, std::io::Error),
|
|
|
|
|
#[error("{0}: symlink: {1}")]
|
|
|
|
|
Symlink(PathBuf, std::io::Error),
|
2026-04-12 19:55:56 +02:00
|
|
|
#[error("{0}: {1}")]
|
|
|
|
|
Other(PathBuf, String),
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
macro_rules! wrap_path {
|
|
|
|
|
($fn:ident $( ( $( $pname:ident : $ptype:ty ),* ) )? -> $result:ty, $err:ident) => {
|
|
|
|
|
pub async fn $fn(path: impl AsRef<Path>$($(, $pname: $ptype)*)?) -> Result<$result> {
|
|
|
|
|
let path = path.as_ref();
|
|
|
|
|
fs::$fn(path $($(, $pname)*)?).await.map_err(|e| Error::$err(path.into(), e))
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
|
2026-04-13 21:11:07 +02:00
|
|
|
wrap_path!(read_dir -> ReadDir, ReadDir);
|
|
|
|
|
wrap_path!(try_exists -> bool, Exists);
|
2026-04-12 19:55:56 +02:00
|
|
|
wrap_path!(read -> Vec<u8>, Read);
|
|
|
|
|
wrap_path!(read_to_string -> String, Read);
|
2026-04-13 21:11:07 +02:00
|
|
|
wrap_path!(create_dir -> (), CreateDir);
|
|
|
|
|
wrap_path!(create_dir_all -> (), CreateDir);
|
|
|
|
|
wrap_path!(remove_file -> (), RemoveFile);
|
|
|
|
|
wrap_path!(symlink(link_src: impl AsRef<Path>) -> (), Symlink);
|
|
|
|
|
wrap_path!(write(content: impl AsRef<[u8]>) -> (), Write);
|
2026-04-12 19:55:56 +02:00
|
|
|
|
2025-07-21 01:41:03 +02:00
|
|
|
pub fn spawn_walk_dir(
|
|
|
|
|
dir: impl Into<PathBuf> + Send + 'static,
|
|
|
|
|
) -> mpsc::Receiver<Result<(PathBuf, Metadata)>> {
|
|
|
|
|
let (tx, rx) = mpsc::channel(1);
|
|
|
|
|
tokio::spawn(walk_dir(dir, tx));
|
|
|
|
|
rx
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
pub async fn walk_dir(dir: impl Into<PathBuf>, tx: mpsc::Sender<Result<(PathBuf, Metadata)>>) {
|
|
|
|
|
let dir: PathBuf = dir.into();
|
|
|
|
|
|
|
|
|
|
let mut todo = std::collections::LinkedList::new();
|
|
|
|
|
if let Ok(rd) = read_dir(&dir).await {
|
|
|
|
|
todo.push_front(rd);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
while let Some(rd) = todo.front_mut() {
|
|
|
|
|
let entry = match rd.next_entry().await {
|
|
|
|
|
Ok(v) => v,
|
|
|
|
|
Err(e) => {
|
2026-04-12 19:55:56 +02:00
|
|
|
if tx.send(Err(Error::ReadDir(dir.clone(), e))).await.is_err() {
|
2025-07-21 01:41:03 +02:00
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
todo.pop_front(); // skip dir on error
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
let Some(entry) = entry else {
|
|
|
|
|
todo.pop_front();
|
|
|
|
|
continue;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
let Ok(md) = entry.metadata().await else {
|
|
|
|
|
continue;
|
|
|
|
|
};
|
|
|
|
|
let is_dir = md.is_dir();
|
|
|
|
|
|
|
|
|
|
let Ok(path) = entry.path().strip_prefix(&dir).map(|p| p.to_path_buf()) else {
|
|
|
|
|
continue; // sub-entry not in dir, weird but semantically, we ignore
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
if tx.send(Ok((path, md))).await.is_err() {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// recurse in sub directories
|
|
|
|
|
if is_dir {
|
|
|
|
|
if let Ok(rd) = read_dir(entry.path()).await {
|
|
|
|
|
todo.push_front(rd);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|