broker_tokio/fs/
read_dir.rs

1use crate::fs::{asyncify, sys};
2
3use std::ffi::OsString;
4use std::fs::{FileType, Metadata};
5use std::future::Future;
6use std::io;
7#[cfg(unix)]
8use std::os::unix::fs::DirEntryExt;
9use std::path::{Path, PathBuf};
10use std::pin::Pin;
11use std::sync::Arc;
12use std::task::Context;
13use std::task::Poll;
14
15/// Returns a stream over the entries within a directory.
16///
17/// This is an async version of [`std::fs::read_dir`](std::fs::read_dir)
18pub async fn read_dir(path: impl AsRef<Path>) -> io::Result<ReadDir> {
19    let path = path.as_ref().to_owned();
20    let std = asyncify(|| std::fs::read_dir(path)).await?;
21
22    Ok(ReadDir(State::Idle(Some(std))))
23}
24
25/// Stream of the entries in a directory.
26///
27/// This stream is returned from the [`read_dir`] function of this module and
28/// will yield instances of [`DirEntry`]. Through a [`DirEntry`]
29/// information like the entry's path and possibly other metadata can be
30/// learned.
31///
32/// # Errors
33///
34/// This [`Stream`] will return an [`Err`] if there's some sort of intermittent
35/// IO error during iteration.
36///
37/// [`read_dir`]: read_dir
38/// [`DirEntry`]: DirEntry
39/// [`Stream`]: crate::stream::Stream
40/// [`Err`]: std::result::Result::Err
41#[derive(Debug)]
42#[must_use = "streams do nothing unless polled"]
43pub struct ReadDir(State);
44
45#[derive(Debug)]
46enum State {
47    Idle(Option<std::fs::ReadDir>),
48    Pending(sys::Blocking<(Option<io::Result<std::fs::DirEntry>>, std::fs::ReadDir)>),
49}
50
51impl ReadDir {
52    /// Returns the next entry in the directory stream.
53    pub async fn next_entry(&mut self) -> io::Result<Option<DirEntry>> {
54        use crate::future::poll_fn;
55        poll_fn(|cx| self.poll_next_entry(cx)).await
56    }
57
58    #[doc(hidden)]
59    pub fn poll_next_entry(&mut self, cx: &mut Context<'_>) -> Poll<io::Result<Option<DirEntry>>> {
60        loop {
61            match self.0 {
62                State::Idle(ref mut std) => {
63                    let mut std = std.take().unwrap();
64
65                    self.0 = State::Pending(sys::run(move || {
66                        let ret = std.next();
67                        (ret, std)
68                    }));
69                }
70                State::Pending(ref mut rx) => {
71                    let (ret, std) = ready!(Pin::new(rx).poll(cx))?;
72                    self.0 = State::Idle(Some(std));
73
74                    let ret = match ret {
75                        Some(Ok(std)) => Ok(Some(DirEntry(Arc::new(std)))),
76                        Some(Err(e)) => Err(e),
77                        None => Ok(None),
78                    };
79
80                    return Poll::Ready(ret);
81                }
82            }
83        }
84    }
85}
86
87#[cfg(feature = "stream")]
88impl crate::stream::Stream for ReadDir {
89    type Item = io::Result<DirEntry>;
90
91    fn poll_next(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Option<Self::Item>> {
92        Poll::Ready(match ready!(self.poll_next_entry(cx)) {
93            Ok(Some(entry)) => Some(Ok(entry)),
94            Ok(None) => None,
95            Err(err) => Some(Err(err)),
96        })
97    }
98}
99
100/// Entries returned by the [`ReadDir`] stream.
101///
102/// [`ReadDir`]: struct.ReadDir.html
103///
104/// This is a specialized version of [`std::fs::DirEntry`] for usage from the
105/// Tokio runtime.
106///
107/// An instance of `DirEntry` represents an entry inside of a directory on the
108/// filesystem. Each entry can be inspected via methods to learn about the full
109/// path or possibly other metadata through per-platform extension traits.
110#[derive(Debug)]
111pub struct DirEntry(Arc<std::fs::DirEntry>);
112
113impl DirEntry {
114    /// Returns the full path to the file that this entry represents.
115    ///
116    /// The full path is created by joining the original path to `read_dir`
117    /// with the filename of this entry.
118    ///
119    /// # Examples
120    ///
121    /// ```no_run
122    /// use tokio::fs;
123    ///
124    /// # async fn dox() -> std::io::Result<()> {
125    /// let mut entries = fs::read_dir(".").await?;
126    ///
127    /// while let Some(entry) = entries.next_entry().await? {
128    ///     println!("{:?}", entry.path());
129    /// }
130    /// # Ok(())
131    /// # }
132    /// ```
133    ///
134    /// This prints output like:
135    ///
136    /// ```text
137    /// "./whatever.txt"
138    /// "./foo.html"
139    /// "./hello_world.rs"
140    /// ```
141    ///
142    /// The exact text, of course, depends on what files you have in `.`.
143    pub fn path(&self) -> PathBuf {
144        self.0.path()
145    }
146
147    /// Returns the bare file name of this directory entry without any other
148    /// leading path component.
149    ///
150    /// # Examples
151    ///
152    /// ```
153    /// use tokio::fs;
154    ///
155    /// # async fn dox() -> std::io::Result<()> {
156    /// let mut entries = fs::read_dir(".").await?;
157    ///
158    /// while let Some(entry) = entries.next_entry().await? {
159    ///     println!("{:?}", entry.file_name());
160    /// }
161    /// # Ok(())
162    /// # }
163    /// ```
164    pub fn file_name(&self) -> OsString {
165        self.0.file_name()
166    }
167
168    /// Return the metadata for the file that this entry points at.
169    ///
170    /// This function will not traverse symlinks if this entry points at a
171    /// symlink.
172    ///
173    /// # Platform-specific behavior
174    ///
175    /// On Windows this function is cheap to call (no extra system calls
176    /// needed), but on Unix platforms this function is the equivalent of
177    /// calling `symlink_metadata` on the path.
178    ///
179    /// # Examples
180    ///
181    /// ```
182    /// use tokio::fs;
183    ///
184    /// # async fn dox() -> std::io::Result<()> {
185    /// let mut entries = fs::read_dir(".").await?;
186    ///
187    /// while let Some(entry) = entries.next_entry().await? {
188    ///     if let Ok(metadata) = entry.metadata().await {
189    ///         // Now let's show our entry's permissions!
190    ///         println!("{:?}: {:?}", entry.path(), metadata.permissions());
191    ///     } else {
192    ///         println!("Couldn't get file type for {:?}", entry.path());
193    ///     }
194    /// }
195    /// # Ok(())
196    /// # }
197    /// ```
198    pub async fn metadata(&self) -> io::Result<Metadata> {
199        let std = self.0.clone();
200        asyncify(move || std.metadata()).await
201    }
202
203    /// Return the file type for the file that this entry points at.
204    ///
205    /// This function will not traverse symlinks if this entry points at a
206    /// symlink.
207    ///
208    /// # Platform-specific behavior
209    ///
210    /// On Windows and most Unix platforms this function is free (no extra
211    /// system calls needed), but some Unix platforms may require the equivalent
212    /// call to `symlink_metadata` to learn about the target file type.
213    ///
214    /// # Examples
215    ///
216    /// ```
217    /// use tokio::fs;
218    ///
219    /// # async fn dox() -> std::io::Result<()> {
220    /// let mut entries = fs::read_dir(".").await?;
221    ///
222    /// while let Some(entry) = entries.next_entry().await? {
223    ///     if let Ok(file_type) = entry.file_type().await {
224    ///         // Now let's show our entry's file type!
225    ///         println!("{:?}: {:?}", entry.path(), file_type);
226    ///     } else {
227    ///         println!("Couldn't get file type for {:?}", entry.path());
228    ///     }
229    /// }
230    /// # Ok(())
231    /// # }
232    /// ```
233    pub async fn file_type(&self) -> io::Result<FileType> {
234        let std = self.0.clone();
235        asyncify(move || std.file_type()).await
236    }
237}
238
239#[cfg(unix)]
240impl DirEntryExt for DirEntry {
241    fn ino(&self) -> u64 {
242        self.0.ino()
243    }
244}