2024-11-20 14:52:26 +00:00
|
|
|
use std::{
|
2024-11-21 14:08:36 +00:00
|
|
|
collections::{hash_map::Iter, HashMap},
|
2024-11-20 14:52:26 +00:00
|
|
|
fmt::{self, Display},
|
2024-11-21 23:46:05 +00:00
|
|
|
io::Read,
|
2024-11-20 14:52:26 +00:00
|
|
|
};
|
|
|
|
|
2024-11-24 14:21:58 +00:00
|
|
|
use mime::Mime;
|
2024-11-20 14:52:26 +00:00
|
|
|
use thiserror::Error;
|
|
|
|
|
|
|
|
#[derive(Debug, Error)]
|
2024-11-21 23:46:05 +00:00
|
|
|
pub enum Error {
|
|
|
|
#[error("Asset could not be found")]
|
|
|
|
NotFound,
|
|
|
|
#[error("Asset could not be opened")]
|
|
|
|
Inaccessible,
|
|
|
|
|
|
|
|
#[error("An unexpected IO error occured when retrieving an asset {0}")]
|
|
|
|
UnexpectedError(std::io::Error),
|
|
|
|
}
|
|
|
|
|
|
|
|
impl From<std::io::Error> for Error {
|
|
|
|
fn from(err: std::io::Error) -> Error {
|
|
|
|
use std::io::ErrorKind::*;
|
|
|
|
|
|
|
|
match err.kind() {
|
|
|
|
NotFound => Error::NotFound,
|
|
|
|
PermissionDenied | UnexpectedEof => Error::Inaccessible,
|
|
|
|
_ => Error::UnexpectedError(err),
|
|
|
|
}
|
|
|
|
}
|
2024-11-20 14:52:26 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
#[derive(Clone, Debug, Hash, Eq, PartialEq)]
|
|
|
|
pub struct AssetId(String);
|
|
|
|
|
|
|
|
impl Display for AssetId {
|
|
|
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
2024-11-21 14:08:36 +00:00
|
|
|
write!(f, "AssetId({})", self.0)
|
2024-11-20 14:52:26 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
impl From<&str> for AssetId {
|
|
|
|
fn from(s: &str) -> Self {
|
|
|
|
AssetId(s.to_owned())
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2024-11-21 14:08:36 +00:00
|
|
|
impl From<String> for AssetId {
|
|
|
|
fn from(s: String) -> Self {
|
|
|
|
AssetId(s)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
pub struct AssetIter<'a>(Iter<'a, AssetId, String>);
|
|
|
|
|
2024-11-24 14:21:58 +00:00
|
|
|
impl<'a> Iterator for AssetIter<'a> {
|
2024-11-21 14:08:36 +00:00
|
|
|
type Item = (&'a AssetId, &'a String);
|
|
|
|
|
|
|
|
fn next(&mut self) -> Option<Self::Item> {
|
|
|
|
self.0.next()
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2024-11-20 14:52:26 +00:00
|
|
|
pub trait Assets {
|
2024-11-21 14:08:36 +00:00
|
|
|
fn assets<'a>(&'a self) -> AssetIter<'a>;
|
2024-11-20 14:52:26 +00:00
|
|
|
|
2024-11-24 14:21:58 +00:00
|
|
|
fn get(&self, asset_id: AssetId) -> Result<(Mime, Vec<u8>), Error>;
|
2024-11-20 14:52:26 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
pub struct FsAssets {
|
|
|
|
assets: HashMap<AssetId, String>,
|
|
|
|
}
|
|
|
|
|
|
|
|
impl FsAssets {
|
|
|
|
pub fn new() -> Self {
|
|
|
|
Self {
|
|
|
|
assets: HashMap::new(),
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
fn assets<'a>(&'a self) -> impl Iterator<Item = &'a AssetId> {
|
|
|
|
self.assets.keys()
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
impl Assets for FsAssets {
|
2024-11-21 14:08:36 +00:00
|
|
|
fn assets<'a>(&'a self) -> AssetIter<'a> {
|
|
|
|
AssetIter(self.assets.iter())
|
2024-11-20 14:52:26 +00:00
|
|
|
}
|
|
|
|
|
2024-11-24 14:21:58 +00:00
|
|
|
fn get(&self, asset_id: AssetId) -> Result<(Mime, Vec<u8>), Error> {
|
2024-11-21 23:46:05 +00:00
|
|
|
let path = match self.assets.get(&asset_id) {
|
|
|
|
Some(asset) => Ok(asset),
|
|
|
|
None => Err(Error::NotFound),
|
|
|
|
}?;
|
2024-11-24 14:21:58 +00:00
|
|
|
let mime = mime_guess::from_path(&path).first().unwrap();
|
2024-11-21 23:46:05 +00:00
|
|
|
let mut content: Vec<u8> = Vec::new();
|
|
|
|
let mut file = std::fs::File::open(&path)?;
|
|
|
|
file.read_to_end(&mut content)?;
|
2024-11-24 14:21:58 +00:00
|
|
|
Ok((mime, content))
|
2024-11-20 14:52:26 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
#[cfg(test)]
|
|
|
|
pub mod mocks {
|
|
|
|
use std::collections::HashMap;
|
|
|
|
|
|
|
|
use super::*;
|
|
|
|
|
|
|
|
pub struct MemoryAssets {
|
2024-11-24 14:21:58 +00:00
|
|
|
asset_paths: HashMap<AssetId, String>,
|
|
|
|
assets: HashMap<AssetId, Vec<u8>>,
|
2024-11-20 14:52:26 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
impl MemoryAssets {
|
2024-11-24 14:21:58 +00:00
|
|
|
pub fn new(data: Vec<(AssetId, String, Vec<u8>)>) -> Self {
|
|
|
|
let mut asset_paths = HashMap::new();
|
|
|
|
let mut assets = HashMap::new();
|
|
|
|
data.into_iter().for_each(|(asset, path, data)| {
|
|
|
|
asset_paths.insert(asset.clone(), path);
|
|
|
|
assets.insert(asset, data);
|
2024-11-20 14:52:26 +00:00
|
|
|
});
|
2024-11-24 14:21:58 +00:00
|
|
|
Self {
|
|
|
|
asset_paths,
|
|
|
|
assets,
|
|
|
|
}
|
2024-11-20 14:52:26 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
impl Assets for MemoryAssets {
|
2024-11-21 14:08:36 +00:00
|
|
|
fn assets<'a>(&'a self) -> AssetIter<'a> {
|
2024-11-24 14:21:58 +00:00
|
|
|
AssetIter(self.asset_paths.iter())
|
2024-11-20 14:52:26 +00:00
|
|
|
}
|
|
|
|
|
2024-11-24 14:21:58 +00:00
|
|
|
fn get(&self, asset_id: AssetId) -> Result<(Mime, Vec<u8>), Error> {
|
|
|
|
match (self.asset_paths.get(&asset_id), self.assets.get(&asset_id)) {
|
|
|
|
(Some(path), Some(data)) => {
|
|
|
|
let mime = mime_guess::from_path(&path).first().unwrap();
|
|
|
|
Ok((mime, data.to_vec()))
|
|
|
|
}
|
|
|
|
_ => Err(Error::NotFound),
|
|
|
|
}
|
2024-11-20 14:52:26 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|