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}