108 lines
3.1 KiB
Rust
108 lines
3.1 KiB
Rust
use std::path::PathBuf;
|
|
use std::fs::OpenOptions;
|
|
use std::fs::File;
|
|
use std::fs::create_dir_all;
|
|
use std::io;
|
|
use std::io::Write;
|
|
use std::path::Path;
|
|
use actix_web::http::StatusCode;
|
|
use actix_web::HttpResponse;
|
|
use actix_web::HttpRequest;
|
|
use actix_web::web::resource;
|
|
use actix_web::Resource;
|
|
|
|
const DEFAULT_CACHE: &str = "max-age=86400";
|
|
|
|
/// Represents a single static asset
|
|
///
|
|
/// Each asset is embeded into the binary, and can be served under the
|
|
/// `/static/{filename}` route. All static assets should be embeded in this way, and can
|
|
/// be referenced from this module.
|
|
pub struct StaticAsset {
|
|
|
|
/// The filename of this asset relative to `:/web/assets/` and the `/static/` route
|
|
pub filename: &'static str,
|
|
|
|
/// The content of the file
|
|
pub bytes: &'static [u8],
|
|
|
|
}
|
|
|
|
impl StaticAsset {
|
|
/// Generate a actix resource for serving this asset
|
|
///
|
|
/// The resource will handle requests at `/static/{filename}`. Caching headers are
|
|
/// applied to allow the content to be considered fresh up to one day, and are
|
|
/// validated against the crate version after that.
|
|
pub fn generate_resource(&self) -> Resource {
|
|
let bytes = self.bytes;
|
|
resource(format!("/static/{}", self.filename))
|
|
.to(move|req: HttpRequest| async move {
|
|
let mut response = HttpResponse::Ok();
|
|
response
|
|
.header("Etag", env!("CARGO_PKG_VERSION"))
|
|
.header("Cache-Control", DEFAULT_CACHE);
|
|
|
|
let req_etag = req.headers().get("If-None-Match");
|
|
match req_etag {
|
|
Some(etag) if etag == env!("CARGO_PKG_VERSION") => {
|
|
response.status(StatusCode::from_u16(304).unwrap()).finish()
|
|
}
|
|
_ => {
|
|
response.body(bytes)
|
|
}
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
/// Attempt to dump several static assets to a directory
|
|
///
|
|
/// Creates the destination directory and all of the necessary parent directories, if
|
|
/// necessary. Will refuse to overwrite existing files.
|
|
pub fn dump_statics(destination: &Path, assets: &[StaticAsset]) -> Result<(), (io::Error, PathBuf)> {
|
|
create_dir_all(destination)
|
|
.map_err(|e| (e, destination.into()))?;
|
|
|
|
let files = assets.iter()
|
|
.map(|asset| {
|
|
let path = destination.join(asset.filename);
|
|
OpenOptions::new()
|
|
.write(true)
|
|
.create_new(true)
|
|
.open(&path)
|
|
.map_err(|e| (e, path.clone()))
|
|
.map(|file| (file, asset.bytes, path))
|
|
})
|
|
.collect::<Result<Vec<(File, &'static [u8], PathBuf)>, (io::Error, PathBuf)>>()?;
|
|
|
|
files.into_iter()
|
|
.map(|(mut file, bytes, path)|
|
|
file.write_all(bytes)
|
|
.map_err(|e| (e, path))
|
|
)
|
|
.collect::<Result<(), (io::Error, PathBuf)>>()
|
|
}
|
|
|
|
|
|
/// Generate a [`StaticAsset`] at compiletime from a filename.
|
|
#[cfg(any(feature = "embed_static_assets", feature = "ogp_images"))]
|
|
macro_rules! static_asset {
|
|
( $filename: expr ) => {
|
|
StaticAsset {
|
|
filename: $filename,
|
|
bytes: include_bytes!(concat!("../assets/", $filename)),
|
|
}
|
|
}
|
|
}
|
|
|
|
/// The decorative font to be used for pronouns displayed at a very large size
|
|
#[cfg(any(feature = "embed_static_assets", feature = "ogp_images"))]
|
|
pub const FONT: StaticAsset = static_asset!("font.otf");
|
|
|
|
/// A list of static assets which should be served by the server
|
|
pub const STATIC_ASSETS: &[StaticAsset] = &[
|
|
#[cfg(any(feature = "embed_static_assets"))]
|
|
FONT,
|
|
];
|