use std::ffi::OsString;
use std::path::{
Path,
PathBuf,
};
use crate::{
FsError,
Operation,
};
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum DirectoryEntryKind {
File,
Directory,
Symlink,
Other,
}
#[derive(Debug, Clone)]
pub struct DirectoryEntry {
name: OsString,
path: PathBuf,
kind: DirectoryEntryKind,
}
impl DirectoryEntry {
#[must_use]
pub fn name(&self) -> &OsString {
&self.name
}
#[must_use]
pub fn path(&self) -> &Path {
&self.path
}
#[must_use]
pub const fn kind(&self) -> DirectoryEntryKind {
self.kind
}
}
pub struct DirectoryReader {
root: PathBuf,
inner: tokio::fs::ReadDir,
}
impl DirectoryReader {
pub async fn open(path: impl AsRef<Path>) -> Result<Self, FsError> {
let root = path.as_ref().to_owned();
let inner = tokio::fs::read_dir(&root)
.await
.map_err(|error| FsError::io(Operation::Directory, &root, error))?;
Ok(Self { root, inner })
}
pub async fn open_if_exists(path: impl AsRef<Path>) -> Result<Option<Self>, FsError> {
let root = path.as_ref().to_owned();
match tokio::fs::read_dir(&root).await {
Ok(inner) => Ok(Some(Self { root, inner })),
Err(error) if error.kind() == std::io::ErrorKind::NotFound => Ok(None),
Err(error) => Err(FsError::io(Operation::Directory, &root, error)),
}
}
pub async fn next(&mut self) -> Result<Option<DirectoryEntry>, FsError> {
let Some(entry) = self
.inner
.next_entry()
.await
.map_err(|error| FsError::io(Operation::Directory, &self.root, error))?
else {
return Ok(None);
};
let file_type = entry
.file_type()
.await
.map_err(|error| FsError::io(Operation::Directory, &self.root, error))?;
let kind = if file_type.is_file() {
DirectoryEntryKind::File
} else if file_type.is_dir() {
DirectoryEntryKind::Directory
} else if file_type.is_symlink() {
DirectoryEntryKind::Symlink
} else {
DirectoryEntryKind::Other
};
Ok(Some(DirectoryEntry {
name: entry.file_name(),
path: entry.path(),
kind,
}))
}
}
pub async fn ensure_dir(path: impl AsRef<Path>) -> Result<(), FsError> {
let path = path.as_ref();
tokio::fs::create_dir_all(path)
.await
.map_err(|error| FsError::io(Operation::Directory, path, error))
}
pub async fn remove_dir_all(path: impl AsRef<Path>) -> Result<(), FsError> {
let path = path.as_ref();
match tokio::fs::remove_dir_all(path).await {
Ok(()) => Ok(()),
Err(error) if error.kind() == std::io::ErrorKind::NotFound => Ok(()),
Err(error) => Err(FsError::io(Operation::Directory, path, error)),
}
}