use std::{
collections::VecDeque,
fmt,
future::{Future, poll_fn},
io,
path::{Path, PathBuf},
pin::Pin,
task::{Context, Poll},
};
use crate::runtime::JoinHandle;
use super::{FileType, Metadata, metadata_impl};
const CHUNK_SIZE: usize = 32;
type Chunk = (VecDeque<io::Result<DirEntry>>, std::fs::ReadDir, bool);
pub async fn read_dir(path: impl AsRef<Path>) -> io::Result<ReadDir> {
let path = path.as_ref().to_owned();
let chunk = super::asyncify(move || Ok(next_chunk(std::fs::read_dir(path)?))).await?;
Ok(ReadDir {
state: State::Idle(Some(chunk)),
})
}
#[must_use = "directory entries are not read unless polled"]
pub struct ReadDir {
state: State,
}
enum State {
Idle(Option<Chunk>),
Pending(JoinHandle<Chunk>),
}
impl ReadDir {
pub async fn next_entry(&mut self) -> io::Result<Option<DirEntry>> {
poll_fn(|cx| self.poll_next_entry(cx)).await
}
pub fn poll_next_entry(&mut self, cx: &mut Context<'_>) -> Poll<io::Result<Option<DirEntry>>> {
loop {
match &mut self.state {
State::Idle(chunk) => {
let Some((entries, _, remain)) = chunk.as_mut() else {
return Poll::Ready(Ok(None));
};
if let Some(entry) = entries.pop_front() {
return Poll::Ready(entry.map(Some));
}
if !*remain {
return Poll::Ready(Ok(None));
}
let (_, read_dir, _) = chunk.take().expect("read directory state is present");
self.state = State::Pending(crate::runtime::spawn_blocking(move || next_chunk(read_dir)));
}
State::Pending(handle) => match Pin::new(handle).poll(cx) {
Poll::Pending => return Poll::Pending,
Poll::Ready(Ok(chunk)) => self.state = State::Idle(Some(chunk)),
Poll::Ready(Err(error)) => {
self.state = State::Idle(None);
if error.is_panic() {
std::panic::resume_unwind(error.into_panic());
}
return Poll::Ready(Err(io::Error::other(error.to_string())));
}
},
}
}
}
}
impl fmt::Debug for ReadDir {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("ReadDir").finish_non_exhaustive()
}
}
#[derive(Debug)]
pub struct DirEntry {
entry: std::fs::DirEntry,
file_type: Option<FileType>,
}
impl DirEntry {
pub fn path(&self) -> PathBuf {
self.entry.path()
}
pub fn file_name(&self) -> std::ffi::OsString {
self.entry.file_name()
}
pub async fn metadata(&self) -> io::Result<Metadata> {
metadata_impl(&self.path(), false).await
}
pub async fn file_type(&self) -> io::Result<FileType> {
if let Some(file_type) = self.file_type {
return Ok(file_type);
}
self.metadata().await.map(|metadata| metadata.file_type())
}
#[cfg(unix)]
pub fn ino(&self) -> u64 {
use std::os::unix::fs::DirEntryExt;
self.entry.ino()
}
}
fn next_chunk(mut read_dir: std::fs::ReadDir) -> Chunk {
let mut entries = VecDeque::with_capacity(CHUNK_SIZE);
let mut remain = true;
for _ in 0..CHUNK_SIZE {
match read_dir.next() {
Some(entry) => entries.push_back(entry.map(|entry| {
let file_type = entry.file_type().ok().map(FileType::from_std);
DirEntry { entry, file_type }
})),
None => {
remain = false;
break;
}
}
}
(entries, read_dir, remain)
}