use std::io;
use std::path::{Path, PathBuf};
use std::sync::Arc;
use std::time::SystemTime;
pub trait ReadStorage {
fn read(&self, path: &Path) -> impl Future<Output = io::Result<Vec<u8>>>;
fn read_to_string(&self, path: &Path) -> impl Future<Output = io::Result<String>>;
fn read_dir(&self, path: &Path) -> impl Future<Output = io::Result<Vec<DirEntry>>>;
fn metadata(&self, path: &Path) -> impl Future<Output = io::Result<Metadata>>;
fn try_exists(&self, path: &Path) -> impl Future<Output = io::Result<bool>> {
async move {
match self.metadata(path).await {
Ok(_) => Ok(true),
Err(e) if e.kind() == io::ErrorKind::NotFound => Ok(false),
Err(e) => Err(e),
}
}
}
}
impl<S: ReadStorage + ?Sized> ReadStorage for &S {
async fn read(&self, path: &Path) -> io::Result<Vec<u8>> {
(**self).read(path).await
}
async fn read_to_string(&self, path: &Path) -> io::Result<String> {
(**self).read_to_string(path).await
}
async fn read_dir(&self, path: &Path) -> io::Result<Vec<DirEntry>> {
(**self).read_dir(path).await
}
async fn metadata(&self, path: &Path) -> io::Result<Metadata> {
(**self).metadata(path).await
}
async fn try_exists(&self, path: &Path) -> io::Result<bool> {
(**self).try_exists(path).await
}
}
impl<S: ReadStorage + ?Sized> ReadStorage for Arc<S> {
async fn read(&self, path: &Path) -> io::Result<Vec<u8>> {
(**self).read(path).await
}
async fn read_to_string(&self, path: &Path) -> io::Result<String> {
(**self).read_to_string(path).await
}
async fn read_dir(&self, path: &Path) -> io::Result<Vec<DirEntry>> {
(**self).read_dir(path).await
}
async fn metadata(&self, path: &Path) -> io::Result<Metadata> {
(**self).metadata(path).await
}
async fn try_exists(&self, path: &Path) -> io::Result<bool> {
(**self).try_exists(path).await
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct DirEntry {
path: PathBuf,
file_type: FileType,
}
impl DirEntry {
pub fn new(path: impl Into<PathBuf>, file_type: FileType) -> Self {
Self {
path: path.into(),
file_type,
}
}
pub fn path(&self) -> &Path {
&self.path
}
pub fn file_name(&self) -> Option<&std::ffi::OsStr> {
self.path.file_name()
}
pub fn file_type(&self) -> FileType {
self.file_type
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct Metadata {
file_type: FileType,
len: u64,
modified: Option<SystemTime>,
}
impl Metadata {
pub fn new(file_type: FileType, len: u64, modified: Option<SystemTime>) -> Self {
Self {
file_type,
len,
modified,
}
}
pub fn file_type(&self) -> FileType {
self.file_type
}
pub fn is_file(&self) -> bool {
self.file_type.is_file()
}
pub fn is_dir(&self) -> bool {
self.file_type.is_dir()
}
pub fn len(&self) -> u64 {
self.len
}
pub fn is_empty(&self) -> bool {
self.len == 0
}
pub fn modified(&self) -> io::Result<SystemTime> {
self.modified
.ok_or_else(|| io::Error::new(io::ErrorKind::Unsupported, "modified time unavailable"))
}
}
#[derive(Debug, Clone, Copy, Default)]
pub struct StdFs;
impl ReadStorage for StdFs {
async fn read(&self, path: &Path) -> io::Result<Vec<u8>> {
std::fs::read(path)
}
async fn read_to_string(&self, path: &Path) -> io::Result<String> {
std::fs::read_to_string(path)
}
async fn read_dir(&self, path: &Path) -> io::Result<Vec<DirEntry>> {
std::fs::read_dir(path)?
.map(|entry| {
let entry = entry?;
Ok(DirEntry::new(
entry.path(),
convert_file_type(entry.file_type()?),
))
})
.collect()
}
async fn metadata(&self, path: &Path) -> io::Result<Metadata> {
let md = std::fs::metadata(path)?;
Ok(Metadata::new(
convert_file_type(md.file_type()),
md.len(),
md.modified().ok(),
))
}
}
fn convert_file_type(ft: std::fs::FileType) -> FileType {
if ft.is_dir() {
FileType::DIR
} else if ft.is_file() {
FileType::FILE
} else {
FileType::SYMLINK
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct FileType {
is_dir: bool,
is_file: bool,
is_symlink: bool,
}
impl FileType {
pub const FILE: FileType = FileType {
is_dir: false,
is_file: true,
is_symlink: false,
};
pub const DIR: FileType = FileType {
is_dir: true,
is_file: false,
is_symlink: false,
};
pub const SYMLINK: FileType = FileType {
is_dir: false,
is_file: false,
is_symlink: true,
};
pub fn is_file(&self) -> bool {
self.is_file
}
pub fn is_dir(&self) -> bool {
self.is_dir
}
pub fn is_symlink(&self) -> bool {
self.is_symlink
}
}