use crate::{
AssetProperties, CowStr, PuppeteerError, PuppeteerResult, StaticAssetProperties, StaticCowStr,
};
use base64ct::{Base64, Encoding};
use bytes::{BufMut, BytesMut};
use camino::{Utf8Path, Utf8PathBuf};
use file_format::FileFormat;
use smol::{fs::File, io::AsyncReadExt};
use std::borrow::Cow;
pub const BUFFER_CAPACITY: usize = 1024 * 64; pub const DEFAULT_RESOURCE_SIZE: usize = 1024 * 1024;
#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
pub struct StaticAsset {
pub name: &'static str,
pub bytes: &'static [u8],
}
impl core::fmt::Debug for StaticAsset {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("StaticAsset")
.field("name", &self.name)
.field("bytes", &self.hash())
.finish()
}
}
impl StaticAssetProperties for StaticAsset {
fn name(&self) -> &'static str {
self.name
}
fn format(&self) -> FileFormat {
FileFormat::from_bytes(self.bytes)
}
fn bytes(&self) -> &'static [u8] {
self.bytes
}
fn base64(&self) -> StaticCowStr {
let media_type = self.format().media_type().to_owned();
Cow::Borrowed("data:")
+ Cow::Owned(media_type)
+ ";base64,"
+ Cow::Owned(Base64::encode_string(self.bytes))
}
fn hash(&self) -> blake3::Hash {
blake3::hash(self.bytes)
}
}
#[derive(Clone, PartialEq, Eq, PartialOrd, Ord)]
pub struct AssetFile<'p> {
pub name: CowStr<'p>,
pub bytes: BytesMut,
}
impl<'p> AssetProperties for AssetFile<'p> {
fn name(&self) -> CowStr {
self.name.clone()
}
fn format(&self) -> FileFormat {
FileFormat::from_bytes(&self.bytes)
}
fn bytes(&self) -> &BytesMut {
&self.bytes
}
fn base64(&self) -> StaticCowStr {
let media_type = self.format().media_type().to_owned();
Cow::Borrowed("data:")
+ Cow::Owned(media_type)
+ ";base64,"
+ Cow::Owned(Base64::encode_string(&self.bytes))
}
fn hash(&self) -> blake3::Hash {
blake3::hash(&self.bytes)
}
}
impl<'p> core::fmt::Debug for AssetFile<'p> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("AssetFile")
.field("name", &self.name)
.field("bytes", &self.hash())
.finish()
}
}
#[derive(Debug)]
pub struct AssetFileLoader<'p> {
dir: &'p Utf8Path,
file_name: &'p Utf8Path,
max_resource_size: usize,
cargo_dir: bool,
}
impl<'p> AssetFileLoader<'p> {
pub fn new(file_name: &'p str) -> Self {
AssetFileLoader {
dir: Utf8Path::new(env!("CARGO_MANIFEST_DIR")),
file_name: Utf8Path::new(file_name),
max_resource_size: DEFAULT_RESOURCE_SIZE,
cargo_dir: true,
}
}
pub fn path_from_cargo_dir(mut self, root_cargo_dir: bool) -> Self {
self.cargo_dir = root_cargo_dir;
self
}
pub fn add_dir(mut self, dir: &'p str) -> Self {
self.dir = Utf8Path::new(dir);
self
}
pub fn max_resource_size(mut self, size: usize) -> Self {
self.max_resource_size = size;
self
}
pub async fn load(self) -> PuppeteerResult<AssetFile<'p>> {
let mut path = Utf8PathBuf::new();
if self.cargo_dir {
path.push(env!("CARGO_MANIFEST_DIR"));
};
path.push(self.dir);
path.push(self.file_name);
let mut file = File::open(path).await?;
let mut container = BytesMut::with_capacity(self.max_resource_size);
let mut bytes_read: usize;
let mut buffer = [0; BUFFER_CAPACITY];
loop {
if container.len() > self.max_resource_size {
return Err(PuppeteerError::MaxResourceLengthExceeded);
}
bytes_read = file.read(&mut buffer).await?;
if bytes_read == 0 {
break;
}
if buffer[..bytes_read].len() < BUFFER_CAPACITY {
container.put(&buffer[..bytes_read][..]);
break;
}
container.put(&buffer[..bytes_read][..]);
}
let file_name = self.file_name.to_path_buf();
Ok(AssetFile {
name: Cow::Owned(file_name.to_string()),
bytes: container,
})
}
}
#[cfg(test)]
mod asset_checks {
use super::*;
#[test]
fn correctness() {
smol::block_on(async {
{
let resource = AssetFileLoader::new("centauri.woff2")
.add_dir("examples/assets/fonts")
.load()
.await
.unwrap();
assert_eq!(&FileFormat::WebOpenFontFormat2, &resource.format());
assert_eq!(&file_format::Kind::Font, &resource.format().kind());
assert_eq!(&"font/woff2", &resource.format().media_type());
assert_eq!(&Some("WOFF2",), &resource.format().short_name());
assert_eq!("centauri.woff2", &resource.name());
}
{
let resource = AssetFileLoader::new("frow.min.css")
.add_dir("examples/assets")
.load()
.await
.unwrap();
assert_eq!(&FileFormat::PlainText, &resource.format());
assert_eq!(&file_format::Kind::Other, &resource.format().kind());
assert_eq!(&"text/plain", &resource.format().media_type());
assert_eq!(&Some("TXT",), &resource.format().short_name());
assert_eq!("frow.min.css", &resource.name());
}
})
}
}