use core::future::Future;
use core::pin::Pin;
use core::task::{Context, Poll, ready};
use std::collections::VecDeque;
use std::ffi::OsString;
use std::fs::{FileType, Metadata};
use std::io;
use std::path::{Path, PathBuf};
use futures_core::Stream;
use super::ops::blocking;
use crate::task::Join;
const PAGE: usize = 32;
type Page = (
Option<Box<std::fs::ReadDir>>,
VecDeque<io::Result<DirEntry>>,
);
pub async fn read_dir(path: impl AsRef<Path>) -> io::Result<ReadDir> {
let path = path.as_ref().to_path_buf();
let dir = blocking(move || std::fs::read_dir(path)).await?;
Ok(ReadDir {
state: State::Idle(Some(Box::new(dir)), VecDeque::new()),
})
}
#[derive(Debug)]
pub struct ReadDir {
state: State,
}
#[derive(Debug)]
enum State {
Idle(
Option<Box<std::fs::ReadDir>>,
VecDeque<io::Result<DirEntry>>,
),
Busy(Join<Page>),
}
impl ReadDir {
pub async fn next_entry(&mut self) -> io::Result<Option<DirEntry>> {
core::future::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(dir, page) => {
if let Some(entry) = page.pop_front() {
return Poll::Ready(entry.map(Some));
}
let Some(mut dir) = dir.take() else {
return Poll::Ready(Ok(None));
};
self.state = State::Busy(
crate::runtime::spawn_blocking(move || {
let mut page = VecDeque::with_capacity(PAGE);
for _ in 0..PAGE {
match dir.next() {
Some(entry) => page.push_back(entry.map(DirEntry::new)),
None => return (None, page),
}
}
(Some(dir), page)
})
.join(),
);
}
State::Busy(job) => match ready!(Pin::new(job).poll(cx)) {
Ok((dir, page)) => self.state = State::Idle(dir, page),
Err(lost) => {
self.state = State::Idle(None, VecDeque::new());
return Poll::Ready(Err(lost.into()));
}
},
}
}
}
}
impl Stream for ReadDir {
type Item = io::Result<DirEntry>;
fn poll_next(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Option<Self::Item>> {
self.get_mut()
.poll_next_entry(cx)
.map(|entry| entry.transpose())
}
}
#[derive(Debug)]
pub struct DirEntry {
inner: std::sync::Arc<std::fs::DirEntry>,
file_type: Result<FileType, (io::ErrorKind, String)>,
}
impl DirEntry {
fn new(entry: std::fs::DirEntry) -> Self {
let file_type = entry
.file_type()
.map_err(|err| (err.kind(), err.to_string()));
Self {
inner: std::sync::Arc::new(entry),
file_type,
}
}
pub fn path(&self) -> PathBuf {
self.inner.path()
}
pub fn file_name(&self) -> OsString {
self.inner.file_name()
}
#[cfg(unix)]
pub fn ino(&self) -> u64 {
std::os::unix::fs::DirEntryExt::ino(&*self.inner)
}
pub async fn file_type(&self) -> io::Result<FileType> {
self.file_type
.clone()
.map_err(|(kind, message)| io::Error::new(kind, message))
}
pub async fn metadata(&self) -> io::Result<Metadata> {
let entry = std::sync::Arc::clone(&self.inner);
blocking(move || entry.metadata()).await
}
}