monorepo/visions/server/src/asset_db.rs

157 lines
3.9 KiB
Rust

use std::{
collections::{hash_map::Iter, HashMap}, fmt::{self, Display}, fs, io::Read, path::PathBuf
};
use mime::Mime;
use serde::{Deserialize, Serialize};
use thiserror::Error;
use typeshare::typeshare;
#[derive(Debug, Error)]
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}")]
Unexpected(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::Unexpected(err),
}
}
}
#[derive(Clone, Debug, Deserialize, Eq, Hash, PartialEq, Serialize)]
#[typeshare]
pub struct AssetId(String);
impl AssetId {
pub fn as_str(&self) -> &str {
&self.0
}
}
impl Display for AssetId {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "AssetId({})", self.0)
}
}
impl From<&str> for AssetId {
fn from(s: &str) -> Self {
AssetId(s.to_owned())
}
}
impl From<String> for AssetId {
fn from(s: String) -> Self {
AssetId(s)
}
}
pub struct AssetIter<'a>(Iter<'a, AssetId, String>);
impl<'a> Iterator for AssetIter<'a> {
type Item = (&'a AssetId, &'a String);
fn next(&mut self) -> Option<Self::Item> {
self.0.next()
}
}
pub trait Assets {
fn assets(&self) -> AssetIter;
fn get(&self, asset_id: AssetId) -> Result<(Mime, Vec<u8>), Error>;
}
pub struct FsAssets {
assets: HashMap<AssetId, String>,
}
impl FsAssets {
pub fn new(path: PathBuf) -> Self {
let dir = fs::read_dir(path).unwrap();
let mut assets = HashMap::new();
for dir_ent in dir {
let path = dir_ent.unwrap().path();
let file_name = path.file_name().unwrap().to_str().unwrap();
assets.insert(AssetId::from(file_name), path.to_str().unwrap().to_owned());
}
Self {
assets,
}
}
}
impl Assets for FsAssets {
fn assets(&self) -> AssetIter {
AssetIter(self.assets.iter())
}
fn get(&self, asset_id: AssetId) -> Result<(Mime, Vec<u8>), Error> {
let path = match self.assets.get(&asset_id) {
Some(asset) => Ok(asset),
None => Err(Error::NotFound),
}?;
let mime = mime_guess::from_path(path).first().unwrap();
let mut content: Vec<u8> = Vec::new();
let mut file = std::fs::File::open(path)?;
file.read_to_end(&mut content)?;
Ok((mime, content))
}
}
#[cfg(test)]
pub mod mocks {
use std::collections::HashMap;
use super::*;
pub struct MemoryAssets {
asset_paths: HashMap<AssetId, String>,
assets: HashMap<AssetId, Vec<u8>>,
}
impl MemoryAssets {
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);
});
Self {
asset_paths,
assets,
}
}
}
impl Assets for MemoryAssets {
fn assets<'a>(&'a self) -> AssetIter<'a> {
AssetIter(self.asset_paths.iter())
}
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),
}
}
}
}