use std::{
fs, io,
path::{Path, PathBuf},
};
use crate::api::{ResourceKind, ResourceLocation, ResourcePath};
pub trait EnumerateResources {
fn enumerate_resources(
&self,
namespace: &str,
kind: ResourceKind,
) -> Result<Vec<ResourceLocation<'static>>, io::Error>;
}
pub trait LoadResource {
fn load_resource(&self, location: &ResourceLocation) -> Result<Vec<u8>, io::Error>;
}
pub trait ResourceProvider: EnumerateResources + LoadResource {}
impl<T: EnumerateResources + LoadResource> ResourceProvider for T {}
pub struct FileSystemResourceProvider {
root: PathBuf,
}
impl FileSystemResourceProvider {
pub fn new(root: impl AsRef<Path>) -> Self {
Self {
root: PathBuf::from(root.as_ref()),
}
}
}
impl EnumerateResources for FileSystemResourceProvider {
fn enumerate_resources(
&self,
namespace: &str,
kind: ResourceKind,
) -> Result<Vec<ResourceLocation<'static>>, io::Error> {
let directory = ResourcePath::for_kind(&self.root, namespace, kind);
Ok(ResourceIter::new(directory, kind)?.collect())
}
}
impl LoadResource for FileSystemResourceProvider {
fn load_resource(&self, location: &ResourceLocation) -> Result<Vec<u8>, io::Error> {
let path = ResourcePath::for_resource(&self.root, location);
fs::read(path)
}
}
pub struct ResourceIter {
dir_iters: Vec<fs::ReadDir>,
kind: ResourceKind,
}
enum DirOrResource {
Dir(fs::ReadDir),
Resource(ResourceLocation<'static>),
}
impl ResourceIter {
pub fn new(directory: impl AsRef<Path>, kind: ResourceKind) -> Result<Self, io::Error> {
let dir_iter = fs::read_dir(directory)?;
Ok(Self {
dir_iters: vec![dir_iter],
kind,
})
}
#[inline]
fn next_dir_or_resource(&mut self) -> Option<DirOrResource> {
let dir_iter = self.dir_iters.last_mut().unwrap();
dir_iter
.filter_map(|dir_entry| {
dir_entry
.ok()
.and_then(|dir_entry| {
dir_entry
.file_type()
.ok()
.map(|file_type| (dir_entry, file_type))
})
.and_then(|(dir_entry, file_type)| {
if file_type.is_dir() {
fs::read_dir(dir_entry.path())
.ok()
.map(DirOrResource::Dir)
} else {
dir_entry.file_name().to_str().and_then(|file_name| {
(
!file_name.starts_with('_') &&
file_name.ends_with(self.kind.extension())
)
.then(|| {
let dot_index =
file_name.len() - self.kind.extension().len() - 1;
let location = ResourceLocation::new_owned(
self.kind,
String::from(&file_name[..dot_index]),
);
DirOrResource::Resource(location)
})
})
}
})
})
.next()
}
}
impl Iterator for ResourceIter {
type Item = ResourceLocation<'static>;
fn next(&mut self) -> Option<Self::Item> {
loop {
let next_dir_or_resource = self.next_dir_or_resource();
if next_dir_or_resource.is_none() {
if self.dir_iters.len() > 1 {
self.dir_iters.pop();
continue;
} else {
return None;
}
}
match next_dir_or_resource.unwrap() {
DirOrResource::Dir(dir_iter) => {
self.dir_iters.push(dir_iter);
continue;
}
DirOrResource::Resource(location) => return Some(location),
}
}
}
}