use clap::{Parser, Subcommand};
use std::path::PathBuf;
use std::str::FromStr;
use tracing::Level;
use ngdp_client::commands::keys::KeysCommands;
use ngdp_client::{
CertsCommands, ConfigCommands, DownloadCommands, InspectCommands, InstallCommands,
OutputFormat, ProductsCommands, StorageCommands, cached_client, commands,
};
#[derive(Parser)]
#[command(
name = "ngdp",
about = "NGDP client for interacting with Blizzard's content distribution system",
version,
author,
long_about = "A command-line tool for accessing NGDP (Next Generation Distribution Pipeline) services, including Ribbit for product information and TACT for content delivery."
)]
struct Cli {
#[arg(short, long, value_enum, default_value = "info")]
log_level: LogLevel,
#[arg(short, long, global = true)]
config: Option<PathBuf>,
#[arg(short = 'o', long, value_enum, global = true, default_value = "text")]
format: OutputFormat,
#[arg(long, global = true)]
no_color: bool,
#[arg(long, global = true)]
no_cache: bool,
#[arg(long, global = true)]
clear_cache: bool,
#[command(subcommand)]
command: Commands,
}
#[derive(clap::ValueEnum, Clone, Copy, Debug)]
enum LogLevel {
Trace,
Debug,
Info,
Warn,
Error,
}
impl From<LogLevel> for Level {
fn from(level: LogLevel) -> Self {
match level {
LogLevel::Trace => Level::TRACE,
LogLevel::Debug => Level::DEBUG,
LogLevel::Info => Level::INFO,
LogLevel::Warn => Level::WARN,
LogLevel::Error => Level::ERROR,
}
}
}
#[derive(Subcommand)]
enum Commands {
#[command(subcommand)]
Products(ProductsCommands),
#[command(subcommand)]
Storage(StorageCommands),
#[command(subcommand)]
Download(DownloadCommands),
#[command(subcommand)]
Install(InstallCommands),
#[command(subcommand)]
Inspect(InspectCommands),
#[command(subcommand)]
Config(ConfigCommands),
#[command(subcommand)]
Keys(KeysCommands),
#[command(subcommand)]
Certs(CertsCommands),
#[command(subcommand)]
Listfile(ngdp_client::ListfileCommands),
}
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
let cli = Cli::parse();
tracing_subscriber::fmt()
.with_max_level(Level::from(cli.log_level))
.with_target(false)
.init();
if cli.no_color {
unsafe {
std::env::set_var("NO_COLOR", "1");
}
}
if cli.no_cache {
cached_client::set_caching_enabled(false);
tracing::debug!("Caching disabled via --no-cache flag");
}
if cli.clear_cache {
tracing::info!("Clearing all cached data...");
for region in ["us", "eu", "kr", "tw", "cn", "sg"] {
if let Ok(r) = ribbit_client::Region::from_str(region) {
if let Ok(client) = cached_client::create_client(r).await {
let _ = client.clear_cache().await;
}
}
}
tracing::info!("Cache cleared successfully");
}
let result = match cli.command {
Commands::Products(cmd) => commands::products::handle(cmd, cli.format).await,
Commands::Storage(cmd) => commands::storage::handle(cmd, cli.format).await,
Commands::Download(cmd) => commands::download::handle(cmd, cli.format).await,
Commands::Install(cmd) => commands::install::handle(cmd, cli.format).await,
Commands::Inspect(cmd) => commands::inspect::handle(cmd, cli.format).await,
Commands::Config(cmd) => commands::config::handle(cmd, cli.format).await,
Commands::Certs(cmd) => commands::certs::handle(cmd, cli.format).await,
Commands::Keys(cmd) => commands::keys::handle_keys_command(cmd, cli.format).await,
Commands::Listfile(cmd) => commands::listfile::handle(cmd, cli.format).await,
};
if let Err(e) = result {
if let Some(ribbit_error) = e.downcast_ref::<ribbit_client::Error>() {
match ribbit_error {
ribbit_client::Error::ConnectionTimeout {
host,
port,
timeout_secs,
} => {
eprintln!("Error: Connection timed out after {timeout_secs} seconds");
eprintln!("Failed to connect to {host}:{port}");
eprintln!("\nPossible causes:");
eprintln!(" - The server may be unreachable from your location");
eprintln!(" - Network restrictions may be blocking the connection");
eprintln!(" - The service may be temporarily unavailable");
if host.contains("cn.version.battle.net") {
eprintln!(
"\nNote: The CN (China) region servers are typically only accessible from within China."
);
eprintln!(
"Consider using a different region (e.g., --region us, --region eu)"
);
}
std::process::exit(1);
}
ribbit_client::Error::ConnectionFailed { host, port } => {
eprintln!("Error: Failed to connect to {host}:{port}");
eprintln!("\nPlease check your internet connection and try again.");
std::process::exit(1);
}
_ => {
eprintln!("Error: {e}");
std::process::exit(1);
}
}
} else {
eprintln!("Error: {e}");
std::process::exit(1);
}
}
Ok(())
}