Skip to main content

async_fs_io/
directory.rs

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