use std::collections::HashMap;
use std::fs::File;
use std::io::Write;
use std::path::PathBuf;
use std::sync::OnceLock;
use log::{debug, error};
use regex::Regex;
use super::utils::find_files_with_extension;
static DISC_PATTERN: OnceLock<Regex> = OnceLock::new();
#[derive(Debug, clap::Args)]
#[command(about = "Create playlist files for multidisc games")]
#[command(args_conflicts_with_subcommands = true)]
pub struct Args {
#[command(subcommand)]
command: Option<Commands>,
#[command(flatten)]
generate: GenerateArgs,
}
#[derive(Debug, clap::Subcommand)]
enum Commands {
#[command(about = "Generate playlist files for multidisc games")]
Generate(GenerateArgs),
}
#[derive(Debug, clap::Args)]
struct GenerateArgs {
#[arg(help = "The location to check for files")]
source: PathBuf,
}
impl Args {
pub fn dispatch(self) -> Result<(), String> {
let cmd = self.command.unwrap_or(Commands::Generate(self.generate));
match cmd {
Commands::Generate(args) => generate_m3u_playlists(args.source),
}
}
}
fn generate_m3u_playlists(source: PathBuf) -> Result<(), String> {
debug!("Generating playlists for files in {source:?}");
let re = DISC_PATTERN.get_or_init(|| {
Regex::new(r"(?<before>.+) \(Disc (?<disc>\d+)\)(?<after>.*)\.[^.]+$")
.expect("Failed to compile regex pattern")
});
let mut matches: HashMap<String, Vec<String>> = HashMap::new();
let chd_ext = ["chd"];
for file in find_files_with_extension(&source, &chd_ext)? {
let file_name = file
.file_name()
.ok_or_else(|| format!("Failed to get filename for {}", file.display()))?
.to_str()
.ok_or_else(|| format!("Failed to convert file name {} to UTF-8", file.display()))?;
let capture = re.captures(file_name);
if let Some(capture) = capture {
let before = capture
.name("before")
.ok_or_else(|| format!("Failed to find regex capture 'before' for {}", file_name))?
.as_str();
let after = capture
.name("after")
.ok_or_else(|| format!("Failed to find regex capture 'after' for {}", file_name))?
.as_str();
let full_match = capture
.get(0)
.ok_or_else(|| format!("Failed to find regex full match for {}", file_name))?
.as_str();
matches
.entry(format!("{before}{after}"))
.or_default()
.push(full_match.to_string())
}
}
for (playlist, files) in &matches {
let playlist_file = source.join(playlist).with_extension("m3u");
if playlist_file.exists() {
continue;
}
error!("Generating {playlist_file:?}");
let mut f = File::create(&playlist_file).map_err(|e| {
format!(
"Failed to create playlist {}: {}",
playlist_file.display(),
e
)
})?;
for file in files {
f.write_all(file.as_bytes()).map_err(|e| {
format!(
"Failed to write to playlist {}: {}",
playlist_file.display(),
e
)
})?;
}
}
Ok(())
}