use std::{error::Error, path::PathBuf}; use clap::{arg, command, Parser, Subcommand}; use env_logger::Env; use mp32rss::{de::hms_duration, s3::Access, MP32RSS}; use tokio::{fs::File, io::AsyncReadExt}; #[derive(Parser, Debug)] #[command(version, about)] struct Args { #[arg(short, long)] bucket: String, #[command(subcommand)] command: Command, } #[derive(Subcommand, Debug)] enum Command { /// Scan the bucket for new files and regenerate the feed. Refresh, /// Rerender the feed. Render, /// Edit the metadata for an entry. Edit(EditArgs), /// Delete an entry and remove the file from the bucket. Delete { /// The key to the file in the bucket. filename: String, }, /// List the templates. Templates, /// Add or replace a template and regenerate the feed. AddTemplate(AddTemplateArgs), /// Remove a template and the files generated from it. RemoveTemplate { /// The template name. name: String, }, } #[derive(clap::Args, Debug)] struct EditArgs { /// The key to the file in the bucket. filename: String, /// The entry title. #[arg(long)] title: Option, /// The entry album. #[arg(long)] album: Option, /// The entry artist. #[arg(long)] artist: Option, /// The file size in bytes. #[arg(long)] size: Option, /// The track length in h:m:s. #[arg(long)] duration: Option, /// True if the entry should not be published. #[arg(long)] hidden: Option, } #[derive(clap::Args, Debug)] struct AddTemplateArgs { /// True if the template should be rendered once for all entries. #[arg(long)] index: bool, #[arg(long)] partial: bool, #[arg(long)] content_type: Option, #[arg(long)] filter: Option, /// The template filename. filename: PathBuf, /// The rendered filename. name: String, } #[tokio::main] async fn main() -> Result<(), Box> { env_logger::Builder::from_env(Env::default().default_filter_or("info")).init(); let args = Args::parse(); let config = aws_config::load_from_env().await; let client = aws_sdk_s3::Client::new(&config); let access = Access::new(client, args.bucket); let mut feed = MP32RSS::open(access).await?; match args.command { Command::Refresh => feed.refresh().await, Command::Render => feed.render(|_| true).await, Command::Edit(edit_args) => update_entry(&mut feed, edit_args).await, Command::Delete { filename } => feed.delete(&filename).await, Command::Templates => list_templates(&feed), Command::AddTemplate(add_template_args) => add_template(&mut feed, add_template_args).await, Command::RemoveTemplate { name } => feed.delete_template(&name).await, } } async fn update_entry(feed: &mut MP32RSS, args: EditArgs) -> Result<(), Box> { if let Some(entry) = feed.get_mut_entry(&args.filename) { if let Some(v) = args.title { entry.title = Some(v); } if let Some(v) = args.album { entry.album = Some(v); } if let Some(v) = args.artist { entry.artist = Some(v); } if let Some(v) = args.size { entry.size = v; } if let Some(v) = args.duration { entry.duration = hms_duration::from_string(v)?; } if let Some(v) = args.hidden { entry.hidden = v; } } feed.render(|e| e.filename == args.filename).await } fn list_templates(feed: &MP32RSS) -> Result<(), Box> { println!( "{:<20} {:<5} {:<7} content-type", "name", "index", "partial" ); println!("{:-<20} {:-<5} {:-<7} {:-<12}", "", "", "", ""); for template in feed.templates() { println!( "{:<20} {:<5} {:<7} {}", template.name, if template.index { "index" } else { "" }, if template.partial { "partial" } else { "" }, template.content_type.as_deref().unwrap_or("text/html") ); } Ok(()) } async fn add_template(feed: &mut MP32RSS, args: AddTemplateArgs) -> Result<(), Box> { let mut file = File::open(&args.filename).await?; let mut data = String::new(); file.read_to_string(&mut data).await?; feed.add_template( args.name, data, args.index, args.partial, args.content_type, args.filter, ) .await }