tokio_fs_ext/fs/wasm/
read_dir.rs

1use std::{
2    ffi::OsString,
3    fmt::Debug,
4    io,
5    path::{Path, PathBuf},
6    str::FromStr,
7    task::{Context, Poll},
8};
9
10use futures::{TryStreamExt, stream::StreamExt};
11use js_sys::{Array, JsString};
12use wasm_bindgen::JsCast;
13use wasm_bindgen_futures::stream::JsStream;
14use web_sys::FileSystemHandle;
15
16use super::{
17    metadata::{FileType, Metadata},
18    opfs::{OpfsError, open_dir},
19};
20
21pub async fn read_dir(path: impl AsRef<Path>) -> io::Result<ReadDir> {
22    let dir_handle = open_dir(&path, super::opfs::OpenDirType::NotCreate).await?;
23    let entries = JsStream::from(dir_handle.entries())
24        .map(|handle| {
25            handle.map_or_else(
26                |err| Err(OpfsError::from(err).into_io_err()),
27                |entry| {
28                    Ok({
29                        let js_array = Array::from(&entry);
30
31                        let name = OsString::from_str(
32                            JsString::from(js_array.get(0))
33                                .as_string()
34                                .ok_or(io::Error::from(io::ErrorKind::InvalidFilename))?
35                                .as_str(),
36                        )
37                        .map_err(|_| io::Error::from(io::ErrorKind::InvalidFilename))?;
38
39                        let path = path.as_ref().join(&name);
40
41                        let file_type = js_array
42                            .get(1)
43                            .unchecked_into::<FileSystemHandle>()
44                            .kind()
45                            .into();
46
47                        DirEntry {
48                            file_type,
49                            path,
50                            name,
51                        }
52                    })
53                },
54            )
55        })
56        .try_collect()
57        .await?;
58
59    Ok(ReadDir { entries })
60}
61
62#[derive(Debug)]
63pub struct ReadDir {
64    entries: Vec<DirEntry>,
65}
66
67impl ReadDir {
68    pub async fn next_entry(&mut self) -> io::Result<Option<DirEntry>> {
69        Ok(self.entries.pop())
70    }
71
72    pub fn poll_next_entry(&mut self, _cx: &mut Context<'_>) -> Poll<io::Result<Option<DirEntry>>> {
73        Poll::Ready(Ok(self.entries.pop()))
74    }
75}
76
77impl Iterator for ReadDir {
78    type Item = io::Result<DirEntry>;
79
80    fn next(&mut self) -> Option<io::Result<DirEntry>> {
81        self.entries.pop().map(Result::Ok)
82    }
83}
84
85impl FromIterator<DirEntry> for ReadDir {
86    fn from_iter<T: IntoIterator<Item = DirEntry>>(iter: T) -> Self {
87        ReadDir {
88            entries: iter.into_iter().collect(),
89        }
90    }
91}
92
93#[derive(Debug)]
94pub struct DirEntry {
95    file_type: FileType,
96    name: OsString,
97    path: PathBuf,
98}
99
100impl DirEntry {
101    /// Create a new `DirEntry`.
102    ///
103    /// This is useful for constructing mock directory entries for testing.
104    pub fn new(path: PathBuf, name: OsString, file_type: FileType) -> Self {
105        Self {
106            file_type,
107            name,
108            path,
109        }
110    }
111
112    pub fn path(&self) -> PathBuf {
113        self.path.clone()
114    }
115
116    pub fn file_name(&self) -> OsString {
117        self.name.clone()
118    }
119
120    pub fn file_type(&self) -> io::Result<FileType> {
121        Ok(self.file_type)
122    }
123
124    pub async fn metadata(&self) -> io::Result<Metadata> {
125        todo!()
126    }
127}