use std::process::ExitCode;
use std::sync::Arc;
use std::time::Duration;
use clap::{Args, Parser};
use color_eyre::eyre::Context;
use console::style;
use indicatif::ProgressBar;
use url::Url;
pub mod commands;
pub mod config;
pub mod format;
pub mod serde_utils;
pub mod utils;
use crate::config::{Config, CONFIG_NAME};
use crate::utils::terminal::{COLOR_ENABLED, USER_ATTENDED};
pub type Error = color_eyre::Report;
pub type Result<T, E = Error> = std::result::Result<T, E>;
#[derive(Debug, Clone, PartialEq, Args)]
#[command(rename_all = "kebab-case")]
pub struct GlobalOpts {
#[arg(long)]
anonymous: bool,
#[arg(long)]
no_check_certificate: bool,
#[arg(long)]
origin: Option<Url>,
#[arg(long)]
max_retries: Option<usize>,
#[arg(long, value_parser(crate::serde_utils::duration::parse_duration))]
min_retry_delay: Option<Duration>,
#[arg(long, value_parser(crate::serde_utils::duration::parse_duration))]
max_retry_delay: Option<Duration>,
#[arg(long, value_parser(crate::serde_utils::duration::parse_duration))]
timeout: Option<Duration>,
#[arg(long)]
https: Option<bool>,
}
#[derive(Debug, Clone, PartialEq, Parser)]
#[command(author, version, about, long_about = None, rename_all = "kebab-case")]
pub struct Opts {
#[command(flatten)]
global: GlobalOpts,
#[command(subcommand)]
command: commands::Command,
}
#[tokio::main]
async fn main() -> ExitCode {
tracing_subscriber::fmt()
.with_env_filter(tracing_subscriber::filter::EnvFilter::from_default_env())
.with_ansi(*COLOR_ENABLED)
.init();
match try_main().await {
Ok(code) => code,
Err(err) => {
let errors: Vec<_> = err.chain().collect();
let [error, causes @ .., last] = errors.as_slice() else {
eprintln!(
"{0} `mega-cli` terminated due to an error",
style("ERROR:").red()
);
eprintln!();
eprintln!(" {0} {err}", style('×').red());
return ExitCode::FAILURE;
};
eprintln!(
"{0} `mega-cli` terminated due to an error.",
style("ERROR:").red()
);
eprintln!();
eprintln!(" {0} {error}", style('×').red());
for cause in causes {
eprintln!(" {0} {cause}", style("├─▶").red());
}
eprintln!(" {0} {last}", style("╰─▶").red());
ExitCode::FAILURE
}
}
}
async fn try_main() -> Result<ExitCode> {
color_eyre::install()?;
let opts: Opts = Opts::parse();
let config: Config = confy::load(CONFIG_NAME, None)?;
let mut mega = {
let http_client = reqwest::Client::builder()
.danger_accept_invalid_certs(opts.global.no_check_certificate)
.build()?;
match &config {
Config::V1(config) => mega::Client::builder()
.origin(opts.global.origin.unwrap_or(config.client.origin.clone()))
.timeout(opts.global.timeout.or(config.client.timeout))
.max_retries(opts.global.max_retries.unwrap_or(config.client.max_retries))
.min_retry_delay(
opts.global
.min_retry_delay
.unwrap_or(config.client.min_retry_delay),
)
.max_retry_delay(
opts.global
.max_retry_delay
.unwrap_or(config.client.max_retry_delay),
)
.https(opts.global.https.unwrap_or(config.client.https))
.build(http_client)?,
}
};
if !opts.global.anonymous {
match config {
Config::V1(ref config) => {
if let Some(session) = config.auth.session.as_deref() {
let maybe_bar = USER_ATTENDED.then(|| {
let bar = ProgressBar::new_spinner();
bar.set_style(utils::terminal::spinner_style());
bar.set_message("resuming session with MEGA...");
bar.enable_steady_tick(Duration::from_millis(75));
bar
});
mega.resume_session(session)
.await
.context("could not resume session with MEGA")?;
if let Some(bar) = maybe_bar {
bar.finish_and_clear();
}
}
}
};
}
let mut mega = Arc::new(mega);
let code = commands::handle(config, &mut mega, opts.command).await?;
Ok(code)
}