1use std::ffi::OsString;
4use std::path::{Path, PathBuf};
5
6use crate::{FsError, Operation};
7
8#[derive(Debug, Clone, Copy, PartialEq, Eq)]
10pub enum DirectoryEntryKind {
11 File,
13 Directory,
15 Symlink,
17 Other,
19}
20
21#[derive(Debug, Clone)]
24pub struct DirectoryEntry {
25 name: OsString,
26 path: PathBuf,
27 kind: DirectoryEntryKind,
28}
29
30impl DirectoryEntry {
31 #[must_use]
33 pub fn name(&self) -> &OsString {
34 &self.name
35 }
36
37 #[must_use]
39 pub fn path(&self) -> &Path {
40 &self.path
41 }
42
43 #[must_use]
45 pub const fn kind(&self) -> DirectoryEntryKind {
46 self.kind
47 }
48}
49
50pub struct DirectoryReader {
53 root: PathBuf,
54 inner: tokio::fs::ReadDir,
55}
56
57impl DirectoryReader {
58 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 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 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
108pub 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
116pub 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}