//! Module defining and implementing resource loaders. use std::fmt; use std::fs::{self, File}; use std::iter; use std::io::{self, BufReader, Read}; use std::path::{Path, PathBuf}; use std::sync::Arc; use glob; use super::Loader; /// Loader for file paths from given directory. /// /// The resources here are just file *paths* (`std::path::PathBuf`), /// and no substantial "loading" is performing (only path resolution). /// /// This isn't particularly useful on its own, but can be wrapped around /// to make more interesting loaders. pub struct PathLoader<'pl> { directory: PathBuf, predicate: Arc bool + Send + Sync + 'pl>, } impl<'pl> PathLoader<'pl> { /// Create a loader which gives out paths to any file within a directory /// whose base name matches the requested resource name. #[inline] pub fn new>(directory: D) -> Self { Self::with_predicate(directory, |_| true) } /// Create a loader which only gives out paths to files /// in given directory that have the specified extension. #[inline] pub fn for_extension, S>(directory: D, extension: S) -> Self where S: ToString { Self::for_extensions(directory, iter::once(extension)) } /// Create a loader which only gives out paths to files /// in given directory that have one of the extensions specified. pub fn for_extensions, I, S>(directory: D, extensions: I) -> Self where I: IntoIterator, S: ToString { Self::with_predicate(directory, { let extensions: Vec<_> = extensions.into_iter() .map(|e| e.to_string()).map(|e| e.trim().to_lowercase()) .collect(); move |path| { let ext = path.extension().and_then(|e| e.to_str()) .map(|s| s.trim().to_lowercase()); extensions.iter().any(|e| Some(e) == ext.as_ref()) } }) } /// Create a loader which gives out paths to files within a directory /// that additionally match a specified boolean predicate. pub fn with_predicate(directory: D, predicate: P) -> Self where D: AsRef, P: Fn(&Path) -> bool + Send + Sync + 'pl { PathLoader{ directory: directory.as_ref().to_owned(), predicate: Arc::new(predicate), } } } impl<'pl> Loader for PathLoader<'pl> { type Item = PathBuf; type Err = io::Error; /// "Load" a path "resource" from the loader's directory. fn load<'n>(&self, name: &'n str) -> Result { let file_part = format!("{}.*", name); let pattern = format!("{}", self.directory.join(file_part).display()); trace!("Globbing with {}", pattern); let glob_iter = match glob::glob(&pattern) { Ok(it) => it, Err(e) => { error!("Failed to glob over files with {}: {}", pattern, e); return Err(io::Error::new(io::ErrorKind::Other, e)); }, }; let matches: Vec<_> = glob_iter .filter_map(Result::ok) // TODO: report those errors .filter(|f| (self.predicate)(f)) .collect(); match matches.len() { 0 => Err(io::Error::new(io::ErrorKind::NotFound, format!("resource `{}` not found in {}", name, self.directory.display()))), 1 => Ok(matches.into_iter().next().unwrap()), c => Err(io::Error::new(io::ErrorKind::InvalidInput, format!("ambiguous resource name `{}` matching {} files in {}", name, c, self.directory.display()))), } } } impl<'pl> fmt::Debug for PathLoader<'pl> { fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result { fmt.debug_struct("PathLoader") .field("directory", &self.directory) .finish() } } /// Loader for files in given directory. /// /// The resources it doles out are just file handles (`std::fs::File`). /// Wrappers around this loaded can then implement their own decoding. #[derive(Debug)] pub struct FileLoader<'pl> { inner: PathLoader<'pl>, } // Constructors that for convenience are delegating to the PathLoader ones. impl<'pl> FileLoader<'pl> { /// Create loader for files within given directory. #[inline] pub fn new>(directory: D) -> Self { FileLoader{inner: PathLoader::new(directory)} } /// Create loader for files within given directory /// that have the specified file extension. #[inline] pub fn for_extension, S>(directory: D, extension: S) -> Self where S: ToString { FileLoader{inner: PathLoader::for_extension(directory, extension)} } /// Create a loader which only loads files from the specified directory /// that have one of the extensions given. #[inline] pub fn for_extensions, I, S>(directory: D, extensions: I) -> Self where I: IntoIterator, S: ToString { FileLoader{inner: PathLoader::for_extensions(directory, extensions)} } /// Create a loader which only loads files from the specified directory /// that additionally match a specified boolean predicate for their paths. #[inline] pub fn with_path_predicate(directory: D, predicate: P) -> Self where D: AsRef, P: Fn(&Path) -> bool + Send + Sync + 'pl { FileLoader{inner: PathLoader::with_predicate(directory, predicate)} } // TODO: add filtering based on file metadata too } impl<'pl> Loader for FileLoader<'pl> { type Item = File; type Err = io::Error; /// Load a `File` resource from loader's directory. fn load<'n>(&self, name: &'n str) -> Result { let path = self.inner.load(name)?; fs::OpenOptions::new().read(true).open(path) } } /// Wrapper around `FileLoader` that loads the entire content of the files. /// /// The content is given out as simple vector of bytes, i.e. `Vec`. #[derive(Debug)] pub struct BytesLoader<'fl> { inner: FileLoader<'fl>, } impl<'fl> BytesLoader<'fl> { /// Wrap a FileLoader within the BytesLoader. #[inline] pub fn new(inner: FileLoader<'fl>) -> Self { BytesLoader{inner} } } impl<'fl> From> for BytesLoader<'fl> { fn from(input: FileLoader<'fl>) -> Self { Self::new(input) } } impl<'fl> Loader for BytesLoader<'fl> { type Item = Vec; type Err = io::Error; /// Load a file resource as its byte content. fn load<'n>(&self, name: &'n str) -> Result { let file = self.inner.load(name)?; let mut bytes = match file.metadata() { Ok(stat) => Vec::with_capacity(stat.len() as usize), Err(e) => { warn!("Failed to stat file of resource `{}` to obtain its size: {}", name, e); Vec::new() }, }; let mut reader = BufReader::new(file); reader.read_to_end(&mut bytes)?; Ok(bytes) } }