tokio-fs-ext 0.5.1

Extend tokio fs to be compatible with native and wasm
Documentation
use std::{
    ffi::OsString,
    fmt::Debug,
    fs::Metadata,
    io,
    path::{Path, PathBuf},
    str::FromStr,
    task::{Context, Poll},
};

use futures::{TryStreamExt, stream::StreamExt};
use js_sys::{Array, JsString};
use wasm_bindgen::JsCast;
use wasm_bindgen_futures::stream::JsStream;
use web_sys::FileSystemHandle;

use super::{
    metadata::FileType,
    opfs::{OpfsError, open_dir},
};

pub async fn read_dir(path: impl AsRef<Path>) -> io::Result<ReadDir> {
    let dir_handle = open_dir(&path, super::opfs::OpenDirType::NotCreate).await?;
    let entries = JsStream::from(dir_handle.entries())
        .map(|handle| {
            handle.map_or_else(
                |err| Err(OpfsError::from(err).into_io_err()),
                |entry| {
                    Ok({
                        let js_array = Array::from(&entry);

                        let name = OsString::from_str(
                            JsString::from(js_array.get(0))
                                .as_string()
                                .ok_or(io::Error::from(io::ErrorKind::InvalidFilename))?
                                .as_str(),
                        )
                        .map_err(|_| io::Error::from(io::ErrorKind::InvalidFilename))?;

                        let path = path.as_ref().join(&name);

                        let file_type = js_array
                            .get(1)
                            .unchecked_into::<FileSystemHandle>()
                            .kind()
                            .into();

                        DirEntry {
                            file_type,
                            path,
                            name,
                        }
                    })
                },
            )
        })
        .try_collect()
        .await?;

    Ok(ReadDir { entries })
}

#[derive(Debug)]
pub struct ReadDir {
    entries: Vec<DirEntry>,
}

impl ReadDir {
    pub async fn next_entry(&mut self) -> io::Result<Option<DirEntry>> {
        Ok(self.entries.pop())
    }

    pub fn poll_next_entry(&mut self, _cx: &mut Context<'_>) -> Poll<io::Result<Option<DirEntry>>> {
        Poll::Ready(Ok(self.entries.pop()))
    }
}

impl Iterator for ReadDir {
    type Item = io::Result<DirEntry>;

    fn next(&mut self) -> Option<io::Result<DirEntry>> {
        self.entries.pop().map(Result::Ok)
    }
}

#[derive(Debug)]
pub struct DirEntry {
    file_type: FileType,
    name: OsString,
    path: PathBuf,
}

impl DirEntry {
    pub fn path(&self) -> PathBuf {
        self.path.clone()
    }

    pub fn file_name(&self) -> OsString {
        self.name.clone()
    }

    pub fn file_type(&self) -> io::Result<FileType> {
        Ok(self.file_type)
    }

    pub async fn metadata(&self) -> io::Result<Metadata> {
        todo!()
    }
}