1use std::ffi::OsString;
4use std::path::{
5 Path,
6 PathBuf,
7};
8
9use crate::{
10 FsError,
11 Operation,
12};
13
14#[derive(Debug, Clone, Copy, PartialEq, Eq)]
16pub enum DirectoryEntryKind {
17 File,
19 Directory,
21 Symlink,
23 Other,
25}
26
27#[derive(Debug, Clone)]
30pub struct DirectoryEntry {
31 name: OsString,
32 path: PathBuf,
33 kind: DirectoryEntryKind,
34}
35
36impl DirectoryEntry {
37 #[must_use]
39 pub fn name(&self) -> &OsString {
40 &self.name
41 }
42
43 #[must_use]
45 pub fn path(&self) -> &Path {
46 &self.path
47 }
48
49 #[must_use]
51 pub const fn kind(&self) -> DirectoryEntryKind {
52 self.kind
53 }
54}
55
56pub struct DirectoryReader {
59 root: PathBuf,
60 inner: tokio::fs::ReadDir,
61}
62
63impl DirectoryReader {
64 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 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 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
114pub 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
122pub 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}