use std::path::{Component, Path, PathBuf};
use http::Uri;
use memchr::memmem;
use serde::{Deserialize, Serialize};
use topcoat_core::fnv1a;
use crate::{AssetOptions, ConstReader, ConstWriter, Source};
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
#[serde(transparent)]
pub struct Asset(u64);
impl Asset {
#[must_use]
pub const fn new(
crate_name: &str,
source_file: &str,
path: &str,
options: &AssetOptions,
) -> Self {
let mut h = fnv1a::hash(crate_name.as_bytes());
h = fnv1a::hash_continue(h, b"\0");
h = fnv1a::hash_continue(h, source_file.as_bytes());
h = fnv1a::hash_continue(h, b"\0");
h = fnv1a::hash_continue(h, path.as_bytes());
h = options.hash_into(h);
Self(h)
}
#[must_use]
pub const fn as_u64(self) -> u64 {
self.0
}
}
#[derive(Debug, Clone, PartialEq)]
pub struct RawAsset {
id: Asset,
path: String,
crate_name: String,
manifest_dir: String,
source_file: String,
options: AssetOptions,
}
pub const ENCODED_ASSET_SIZE: usize = 2048;
impl RawAsset {
#[must_use]
pub const fn encode(
id: Asset,
path: &str,
crate_name: &str,
manifest_dir: &str,
source_file: &str,
options: &AssetOptions,
) -> [u8; ENCODED_ASSET_SIZE] {
let mut out = [0u8; ENCODED_ASSET_SIZE];
let mut w = ConstWriter::new(&mut out);
w.write_bytes(&asset_prefix());
w.write_u64_le(id.0);
w.write_str(path);
w.write_str(crate_name);
w.write_str(manifest_dir);
w.write_str(source_file);
options.encode_into(&mut w);
out
}
#[must_use]
pub fn decode(buffer: &[u8]) -> Option<Self> {
let mut r = ConstReader::new(buffer);
r.skip(asset_prefix().len())?;
Some(Self {
id: Asset(r.read_u64_le()?),
path: r.read_str()?.to_owned(),
crate_name: r.read_str()?.to_owned(),
manifest_dir: r.read_str()?.to_owned(),
source_file: r.read_str()?.to_owned(),
options: AssetOptions::decode_from(&mut r)?,
})
}
#[must_use]
pub fn options(&self) -> &AssetOptions {
&self.options
}
#[must_use]
pub fn find_in_binary(binary: &[u8]) -> Vec<Self> {
let prefix = asset_prefix();
let finder = memmem::Finder::new(&prefix);
finder
.find_iter(binary)
.filter_map(|index| Self::decode(&binary[index..]))
.collect()
}
#[must_use]
pub fn id(&self) -> Asset {
self.id
}
#[must_use]
pub fn path(&self) -> &str {
&self.path
}
#[must_use]
pub fn source(&self) -> Source {
if let Ok(uri) = self.path.parse::<Uri>()
&& matches!(uri.scheme_str(), Some("http" | "https"))
{
return Source::Url(uri);
}
Source::Path(self.resolved_path())
}
#[must_use]
pub fn crate_name(&self) -> &str {
&self.crate_name
}
pub fn resolved_path(&self) -> PathBuf {
let path = Path::new(&self.path);
if path.is_absolute() {
return normalize(path);
}
let anchor = match path.components().next() {
Some(Component::CurDir | Component::ParentDir) => {
let source = anchor_source_file(&self.manifest_dir, &self.source_file);
source
.parent()
.map_or_else(|| PathBuf::from(&self.manifest_dir), Path::to_path_buf)
}
_ => PathBuf::from(&self.manifest_dir),
};
normalize(&anchor.join(path))
}
}
fn anchor_source_file(manifest_dir: &str, source_file: &str) -> PathBuf {
let source = Path::new(source_file);
if source.is_absolute() {
return source.to_path_buf();
}
let mut cur = Path::new(manifest_dir);
loop {
let candidate = cur.join(source);
if candidate.exists() {
return candidate;
}
match cur.parent() {
Some(parent) => cur = parent,
None => return Path::new(manifest_dir).join(source),
}
}
}
fn normalize(path: &Path) -> PathBuf {
let mut out = PathBuf::new();
for comp in path.components() {
match comp {
Component::CurDir => {}
Component::ParentDir => {
if !out.pop() {
out.push(comp);
}
}
other => out.push(other),
}
}
out
}
#[macro_export]
macro_rules! asset {
($path:expr $(, $($ao:tt)*)?) => {{
const PATH: &str = $path;
const CRATE_NAME: &str = env!("CARGO_CRATE_NAME");
const MANIFEST_DIR: &str = env!("CARGO_MANIFEST_DIR");
const SOURCE_FILE: &str = file!();
const OPTIONS: $crate::AssetOptions = $crate::asset_options!($($($ao)*)?);
const ID: $crate::Asset = $crate::Asset::new(CRATE_NAME, SOURCE_FILE, PATH, &OPTIONS);
#[used]
pub static ENCODED_ASSET: [u8; $crate::ENCODED_ASSET_SIZE] = $crate::RawAsset::encode(
ID,
PATH,
CRATE_NAME,
MANIFEST_DIR,
SOURCE_FILE,
&OPTIONS,
);
ID
}};
}
const PREFIX_KEY: u8 = 0xA7;
const SCRAMBLED_PREFIX: [u8; 13] = [
b'T' ^ PREFIX_KEY,
b'O' ^ PREFIX_KEY,
b'P' ^ PREFIX_KEY,
b'C' ^ PREFIX_KEY,
b'O' ^ PREFIX_KEY,
b'A' ^ PREFIX_KEY,
b'T' ^ PREFIX_KEY,
b'_' ^ PREFIX_KEY,
b'A' ^ PREFIX_KEY,
b'S' ^ PREFIX_KEY,
b'S' ^ PREFIX_KEY,
b'E' ^ PREFIX_KEY,
b'T' ^ PREFIX_KEY,
];
const fn asset_prefix() -> [u8; 13] {
let mut out = [0u8; 13];
let mut i = 0;
while i < SCRAMBLED_PREFIX.len() {
out[i] = SCRAMBLED_PREFIX[i] ^ PREFIX_KEY;
i += 1;
}
out
}