Files
dkl/src/fs.rs
T

90 lines
2.5 KiB
Rust
Raw Normal View History

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>;
#[derive(Debug, thiserror::Error)]
pub enum Error {
#[error("{0}: read_dir: {1}")]
ReadDir(PathBuf, std::io::Error),
#[error("{0}: read: {1}")]
Read(PathBuf, std::io::Error),
#[error("{0}: stat: {1}")]
Stat(PathBuf, std::io::Error),
#[error("{0}: write: {1}")]
Write(PathBuf, std::io::Error),
#[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))
}
};
}
wrap_path!(read_dir -> fs::ReadDir, ReadDir);
wrap_path!(read -> Vec<u8>, Read);
wrap_path!(read_to_string -> String, Read);
wrap_path!(write(content: &[u8]) -> (), Write);
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);
}
}
}
}