mod vars;
use env_logger::Env;
use xnde::{dump, export, DumpFormat, ExportFormat};
use clap::{value_parser, Arg, Command};
use parse_display::Display;
use std::convert::TryFrom;
use std::path::{Path, PathBuf};
#[derive(Debug, Display)]
enum Cause {
#[display("Another crate's or module's error-- cf. source.")]
Other,
#[display(
"An internal error has occurred; please consider filing a bug report to sp1ff@pobox.com."
)]
Internal,
#[display("No sub-command given.")]
NoSubCommand,
}
#[derive(Debug, Display)]
#[display("{cause} Source (if any): {source} Stack trace (if any): {trace}")]
struct Error {
#[display("XNDE error {}.")]
cause: Cause,
#[display("XNDE error caused by {:#?}.")]
source: Option<Box<dyn std::error::Error>>,
#[display("backtrace: {:#?}.")]
trace: Option<backtrace::Backtrace>,
}
impl Error {
fn new(cause: Cause) -> Error {
Error {
cause: cause,
source: None,
trace: Some(backtrace::Backtrace::new()),
}
}
}
impl std::error::Error for Error {
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
match &self.source {
Some(bx) => Some(bx.as_ref()),
None => None,
}
}
}
impl std::convert::From<xnde::Error> for Error {
fn from(err: xnde::Error) -> Self {
Error {
cause: Cause::Other,
source: Some(Box::new(err)),
trace: Some(backtrace::Backtrace::new()),
}
}
}
impl std::convert::From<log::SetLoggerError> for Error {
fn from(err: log::SetLoggerError) -> Self {
Error {
cause: Cause::Other,
source: Some(Box::new(err)),
trace: Some(backtrace::Backtrace::new()),
}
}
}
fn main() -> Result<(), Error> {
use vars::{AUTHOR, VERSION};
let matches = Command::new("xnde")
.version(VERSION)
.author(AUTHOR)
.about("xnde -- eXtricate your music library from the Nullsoft Database Engine")
.long_about(
"This is a little command-line tool for reading Winamp Music Library databases
and exporting the data into other formats. The Nullsoft Database Engine (NDE) was developed
against the Win32 API and (seemingly) ported to MacOS, but never Linux.",
)
.arg(
Arg::new("verbose")
.short('v')
.long("verbose")
.help("Produce more copious output.")
.required(false)
.num_args(0),
)
.subcommand(
Command::new("dump")
.about("dump the contents of a Winamp Music Library")
.long_about(
"Walk the contents of a single NDE table ('main', presumably) & dump its
contents to stdout. Useful for exploring & trouble-shooting.",
)
.arg(
Arg::new("format")
.long("format")
.short('f')
.help("Format in which your Muic Library shall be printed")
.num_args(1)
.default_value("display"),
)
.arg(
Arg::new("index")
.help("NDE index file (`main.idx', e.g.)")
.index(1)
.requires("data")
.required(true)
.value_parser(value_parser!(std::path::PathBuf)),
)
.arg(
Arg::new("data")
.help("corresponding NDE data file (`main.dat', e.g.)")
.index(2)
.required(true)
.value_parser(value_parser!(std::path::PathBuf)),
),
)
.subcommand(
Command::new("export")
.about("export the contents of a Winamp Music Library")
.long_about(
"Walk the contents of the NDE 'main' table. For each record therein, transform
it in-memory into a struct representing a single Music Library track (along with all its
associated metadata: playcount, rating, last played, &c). Serialize the entire collection to
one of a few formats for subsequent use.",
)
.arg(
Arg::new("output")
.short('o')
.help(
"file to which the serlialized form of your Winamp Music Library shall
be written",
)
.num_args(1)
.default_value("main.out")
.value_parser(value_parser!(PathBuf)), )
.arg(
Arg::new("format")
.long("format")
.short('f')
.help("Format to which your Music Library shall be serialized")
.num_args(1)
.default_value("sexp"), )
.arg(
Arg::new("index")
.help("NDE index file (`main.idx', e.g.)")
.index(1)
.requires("data")
.required(true)
.value_parser(value_parser!(std::path::PathBuf)),
)
.arg(
Arg::new("data")
.help("corresponding NDE data file (`main.dat', e.g.)")
.index(2)
.required(true)
.value_parser(value_parser!(std::path::PathBuf)),
),
)
.get_matches();
env_logger::init_from_env(Env::default().filter_or(
"RUST_LOG",
if matches.get_flag("verbose") {
"debug"
} else {
"info"
},
));
if let Some(subm) = matches.subcommand_matches("dump") {
let format = subm
.get_one::<String>("format")
.ok_or(Error::new(Cause::Internal))?;
let idx = subm
.get_one::<PathBuf>("index")
.ok_or(Error::new(Cause::Internal))?;
let dat = subm
.get_one::<PathBuf>("data")
.ok_or(Error::new(Cause::Internal))?;
return Ok(dump(
Path::new(idx),
Path::new(dat),
DumpFormat::try_from(format.as_str())?,
)?);
} else if let Some(subm) = matches.subcommand_matches("export") {
let format = subm
.get_one::<String>("format")
.ok_or(Error::new(Cause::Internal))?;
let output = subm
.get_one::<PathBuf>("output")
.ok_or(Error::new(Cause::Internal))?;
let idx = subm
.get_one::<PathBuf>("index")
.ok_or(Error::new(Cause::Internal))?;
let dat = subm
.get_one::<PathBuf>("data")
.ok_or(Error::new(Cause::Internal))?;
return Ok(export(
Path::new(idx),
Path::new(dat),
ExportFormat::try_from(format.as_str())?,
Path::new(output),
)?);
} else {
Err(Error::new(Cause::NoSubCommand))
}
}