Skip to main content

async_fs_io/
directory.rs

1//! Bounded, pull-based asynchronous directory traversal.
2
3use std::ffi::OsString;
4use std::path::{
5    Path,
6    PathBuf,
7};
8
9use crate::{
10    FsError,
11    Operation,
12};
13
14/// The kind of an asynchronously inspected directory entry.
15#[derive(Debug, Clone, Copy, PartialEq, Eq)]
16pub enum DirectoryEntryKind {
17    /// A regular file.
18    File,
19    /// A directory.
20    Directory,
21    /// A symbolic link.
22    Symlink,
23    /// Another filesystem entry kind.
24    Other,
25}
26
27/// One directory entry. A reader holds at most one entry in addition to the
28/// operating system's own directory stream buffer.
29#[derive(Debug, Clone)]
30pub struct DirectoryEntry {
31    name: OsString,
32    path: PathBuf,
33    kind: DirectoryEntryKind,
34}
35
36impl DirectoryEntry {
37    /// Return the entry name without its parent path.
38    #[must_use]
39    pub fn name(&self) -> &OsString {
40        &self.name
41    }
42
43    /// Return the complete entry path.
44    #[must_use]
45    pub fn path(&self) -> &Path {
46        &self.path
47    }
48
49    /// Return the entry kind.
50    #[must_use]
51    pub const fn kind(&self) -> DirectoryEntryKind {
52        self.kind
53    }
54}
55
56/// An asynchronous directory reader. Call [`Self::next`] repeatedly instead
57/// of collecting the directory into memory.
58pub struct DirectoryReader {
59    root: PathBuf,
60    inner: tokio::fs::ReadDir,
61}
62
63impl DirectoryReader {
64    /// Open a directory for asynchronous traversal.
65    pub async fn open(path: impl AsRef<Path>) -> Result<Self, FsError> {
66        let root = path.as_ref().to_owned();
67        let inner = tokio::fs::read_dir(&root)
68            .await
69            .map_err(|error| FsError::io(Operation::Directory, &root, error))?;
70        Ok(Self { root, inner })
71    }
72
73    /// Open a directory when it exists, returning `None` for a missing path.
74    pub async fn open_if_exists(path: impl AsRef<Path>) -> Result<Option<Self>, FsError> {
75        let root = path.as_ref().to_owned();
76        match tokio::fs::read_dir(&root).await {
77            Ok(inner) => Ok(Some(Self { root, inner })),
78            Err(error) if error.kind() == std::io::ErrorKind::NotFound => Ok(None),
79            Err(error) => Err(FsError::io(Operation::Directory, &root, error)),
80        }
81    }
82
83    /// Read the next entry, returning `None` at end of directory.
84    pub async fn next(&mut self) -> Result<Option<DirectoryEntry>, FsError> {
85        let Some(entry) = self
86            .inner
87            .next_entry()
88            .await
89            .map_err(|error| FsError::io(Operation::Directory, &self.root, error))?
90        else {
91            return Ok(None);
92        };
93        let file_type = entry
94            .file_type()
95            .await
96            .map_err(|error| FsError::io(Operation::Directory, &self.root, error))?;
97        let kind = if file_type.is_file() {
98            DirectoryEntryKind::File
99        } else if file_type.is_dir() {
100            DirectoryEntryKind::Directory
101        } else if file_type.is_symlink() {
102            DirectoryEntryKind::Symlink
103        } else {
104            DirectoryEntryKind::Other
105        };
106        Ok(Some(DirectoryEntry {
107            name: entry.file_name(),
108            path: entry.path(),
109            kind,
110        }))
111    }
112}
113
114/// Create a directory and all missing parents.
115pub async fn ensure_dir(path: impl AsRef<Path>) -> Result<(), FsError> {
116    let path = path.as_ref();
117    tokio::fs::create_dir_all(path)
118        .await
119        .map_err(|error| FsError::io(Operation::Directory, path, error))
120}
121
122/// Remove a directory tree if it exists.
123pub async fn remove_dir_all(path: impl AsRef<Path>) -> Result<(), FsError> {
124    let path = path.as_ref();
125    match tokio::fs::remove_dir_all(path).await {
126        Ok(()) => Ok(()),
127        Err(error) if error.kind() == std::io::ErrorKind::NotFound => Ok(()),
128        Err(error) => Err(FsError::io(Operation::Directory, path, error)),
129    }
130}