diff --git a/web/src/configuration.rs b/web/src/configuration.rs index 558c68e..dfd952c 100644 --- a/web/src/configuration.rs +++ b/web/src/configuration.rs @@ -33,11 +33,11 @@ pub enum SubCommand { /// If serving static files, all files should be available under the /static/filename.ext /// route. pub struct DumpStatics { - #[argh(option, default = "String::from(\"./static/\")")] + #[argh(positional, default = "PathBuf::from(\"./static/\")")] /// the directory to write the static files to /// /// Defaults to ./static/ - pub destination: String, + pub destination: PathBuf, } #[derive(FromArgs, PartialEq, Debug)] diff --git a/web/src/main.rs b/web/src/main.rs index 75ff4e0..357be9c 100644 --- a/web/src/main.rs +++ b/web/src/main.rs @@ -5,6 +5,7 @@ pub mod configuration; use configuration::ConfigError; +use std::io::ErrorKind; use std::net::SocketAddr; use std::process::exit; use std::collections::HashMap; @@ -150,8 +151,38 @@ async fn main() -> std::io::Result<()> { let args: configuration::PronounsTodayArgs = argh::from_env(); match args.command { - configuration::SubCommand::DumpStatics(_subargs) => { - println!("Support for dumping statics not yet implemented"); + configuration::SubCommand::DumpStatics(subargs) => { + match statics::dump_statics( + subargs.destination.as_ref(), + statics::STATIC_ASSETS + ) { + Ok(()) => { + println!("Successfully dumped all static assets to {}. Serve this \ + directory under /static/ in order to speed up delivery of \ + static assets", subargs.destination.display()); + } + Err((e, path)) => { + match e.kind() { + ErrorKind::PermissionDenied => { + eprintln!( + "Permission denied for {}", + path.display(), + ); + }, + ErrorKind::AlreadyExists => { + eprintln!( + "Refusing to overwrite existing file {}", + path.display(), + ); + }, + _ => { + eprintln!("An unexpected IO error occurred while trying to \ + interact with {}", path.display()); + return Err(e); + } + }; + } + }; Ok(()) } configuration::SubCommand::Run(subargs) => { diff --git a/web/src/statics.rs b/web/src/statics.rs index 0b89ccb..c3b05f8 100644 --- a/web/src/statics.rs +++ b/web/src/statics.rs @@ -1,3 +1,10 @@ +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; @@ -49,6 +56,34 @@ impl StaticAsset { } } +/// 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::, (io::Error, PathBuf)>>()?; + + files.into_iter() + .map(|(mut file, bytes, path)| + file.write_all(bytes) + .map_err(|e| (e, path)) + ) + .collect::>() +} + /// Generate a [`StaticAsset`] at compiletime from a filename. #[cfg(any(feature = "embed_static_assets", feature = "ogp_images"))]