#![cfg(feature = "cli")]
use clap::Parser;
use osynic_serializer::commands::{
diff_sets, diff_songs, serialize_by_folder, serialize_by_osu_db,
};
use osynic_serializer::functions::check::{
check_osu_dir, check_sets_type, check_songs_type, get_osu_dir,
};
use osynic_serializer::functions::parse::parse_song_id_list_with_mapper;
use osynic_serializer::functions::storage::marked_save_to;
use osynic_serializer::types::{Beatmapsets, SongWithMapper, SongsWithMapper};
use std::path::{Path, PathBuf};
#[derive(Debug, Clone, Copy, clap::ValueEnum)]
enum Algorithm {
Osudb,
Folder,
}
#[derive(Debug, Clone, Copy, clap::ValueEnum)]
enum JsonType {
Songs,
Sets,
}
#[derive(Parser, Debug)]
#[command(author, version, about, long_about = None)]
struct CliArgs {
#[arg(short, long)]
algorithm: Option<Algorithm>,
#[arg(short = 't', long)]
json_type: Option<JsonType>,
#[arg(short, long)]
path: Option<PathBuf>,
#[arg(short, long)]
diff: Option<PathBuf>,
#[arg(short, long)]
output: Option<PathBuf>,
}
fn main() -> Result<(), Box<dyn std::error::Error>> {
let mut args = CliArgs::parse();
println!("🎵 OsynicSerializer - Interactive Mode\n");
if args.json_type.is_none() {
args.json_type = Some(prompt_json_type()?);
}
if args.algorithm.is_none() {
args.algorithm = Some(prompt_algorithm()?);
}
if args.path.is_none() {
args.path = Some(prompt_path()?);
}
let osu_dir = args.path.clone().ok_or("osu! path not found")?;
validate_diff_file(&args.diff, &args.json_type.unwrap())?;
let songs = (match args.algorithm.unwrap() {
Algorithm::Osudb => serialize_by_osu_db(osu_dir.to_str().unwrap_or_default()),
Algorithm::Folder => serialize_by_folder(osu_dir.to_str().unwrap_or_default()),
})?;
if args.output.is_none() {
args.output = Some(prompt_output_path()?);
}
let is_diff = args.diff.is_some();
let json_type = args.json_type.unwrap();
match json_type {
JsonType::Sets => {
let sets = Beatmapsets {
beatmapset_ids: parse_song_id_list_with_mapper(&songs.songs),
};
let result_data = process_diff_sets(sets, args.diff)?;
println!(
"\n✅ Total beatmapsets after diff: {}",
result_data.beatmapset_ids.len()
);
save_sets_data(
is_diff,
&args.output.unwrap(),
&result_data,
args.algorithm.unwrap(),
)?;
}
JsonType::Songs => {
let result_data = process_diff_songs(songs, args.diff)?;
println!("\n✅ Total songs after diff: {}", result_data.songs.len());
save_songs_data(
is_diff,
&args.output.unwrap(),
&result_data,
args.algorithm.unwrap(),
)?;
}
}
Ok(())
}
fn prompt_json_type() -> Result<JsonType, Box<dyn std::error::Error>> {
use inquire::Select;
let options = vec!["songs", "sets"];
let ans = Select::new("📋 Select output JSON type:", options).prompt()?;
Ok(match ans {
"songs" => JsonType::Songs,
"sets" => JsonType::Sets,
_ => JsonType::Songs,
})
}
fn prompt_algorithm() -> Result<Algorithm, Box<dyn std::error::Error>> {
use inquire::Select;
let options = vec!["osudb (Database - Recommended)", "folder (Direct scan)"];
let ans = Select::new("🔧 Select serialization algorithm:", options).prompt()?;
Ok(match ans {
"osudb (Database - Recommended)" => Algorithm::Osudb,
"folder (Direct scan)" => Algorithm::Folder,
_ => Algorithm::Osudb,
})
}
fn prompt_path() -> Result<PathBuf, Box<dyn std::error::Error>> {
use inquire::Confirm;
let osu_path_detected = check_osu_dir();
if osu_path_detected {
let detected_path = get_osu_dir();
println!("\n📁 Detected osu! installation at: {}", detected_path);
let use_detected = Confirm::new("Use this path?").with_default(true).prompt()?;
if use_detected {
return Ok(PathBuf::from(detected_path));
}
}
use inquire::Text;
let path = Text::new("📁 Enter osu! installation path:").prompt()?;
Ok(PathBuf::from(path))
}
fn prompt_output_path() -> Result<PathBuf, Box<dyn std::error::Error>> {
use inquire::Text;
let default_path = "./output";
let path = Text::new("📁 Enter output directory path:")
.with_default(default_path)
.prompt()?;
Ok(PathBuf::from(path))
}
fn validate_diff_file(
diff_path: &Option<PathBuf>,
json_type: &JsonType,
) -> Result<(), Box<dyn std::error::Error>> {
if let Some(path) = diff_path {
let content = std::fs::read_to_string(path)?;
let is_valid = match json_type {
JsonType::Songs => check_songs_type(&content),
JsonType::Sets => check_sets_type(&content),
};
if !is_valid {
return Err("Invalid diff file".into());
}
}
Ok(())
}
fn process_diff_sets(
base_data: Beatmapsets,
diff_path: Option<PathBuf>,
) -> Result<Beatmapsets, Box<dyn std::error::Error>> {
match diff_path {
Some(path) => {
let diff_content = std::fs::read_to_string(path)?;
let diff_data: Beatmapsets = serde_json::from_str(&diff_content)?;
Ok(diff_sets(&base_data, &diff_data))
}
None => Ok(base_data),
}
}
fn process_diff_songs(
base_data: SongsWithMapper,
diff_path: Option<PathBuf>,
) -> Result<SongsWithMapper, Box<dyn std::error::Error>> {
match diff_path {
Some(path) => {
let diff_content = std::fs::read_to_string(path)?;
let diff_data: Vec<SongWithMapper> = serde_json::from_str(&diff_content)?;
let diff_data = SongsWithMapper { songs: diff_data };
Ok(diff_songs(&base_data, &diff_data))
}
None => Ok(base_data),
}
}
fn save_sets_data(
is_diff: bool,
output_dir: &Path,
data: &Beatmapsets,
algorithm: Algorithm,
) -> Result<(), Box<dyn std::error::Error>> {
let diff_mark = if is_diff { "diff_" } else { "" };
let filename = format!(
"{}{}_{}.json",
diff_mark,
match algorithm {
Algorithm::Osudb => "sets_dm",
Algorithm::Folder => "sets_m",
},
chrono::Local::now().format("%y%m%d%H%M%S")
);
let json = serde_json::to_string_pretty(data)?;
marked_save_to(output_dir.to_str().unwrap_or_default(), &filename, &json)?;
println!("📄 Output file: {}/{}", output_dir.display(), filename);
Ok(())
}
fn save_songs_data(
is_diff: bool,
output_dir: &Path,
data: &SongsWithMapper,
algorithm: Algorithm,
) -> Result<(), Box<dyn std::error::Error>> {
let diff_mark = if is_diff { "diff_" } else { "" };
let filename = format!(
"{}{}_{}.json",
diff_mark,
match algorithm {
Algorithm::Osudb => "songs_dm",
Algorithm::Folder => "songs_m",
},
chrono::Local::now().format("%y%m%d%H%M%S")
);
let json = serde_json::to_string_pretty(&data.songs)?;
marked_save_to(output_dir.to_str().unwrap_or_default(), &filename, &json)?;
println!("📄 Output file: {}/{}", output_dir.display(), filename);
Ok(())
}