use std::process::ExitCode;
use std::time::Duration;
use anyhow::Context;
use indicatif::{ProgressBar, ProgressFinish, ProgressStyle};
use reqwest::Client;
use tokio::runtime::Runtime;
use tokio::signal;
use booru_dl::api::BatchGetter;
use booru_dl::cli::{Cli, CommandFactory, Parser};
use booru_dl::config::Config;
use booru_dl::scheduler::Scheduler;
const SPINNER_FINISH_MODE: ProgressFinish = ProgressFinish::AndClear;
const SPINNER_TICK_SECS: f32 = 0.1;
#[inline]
fn build_spinner() -> ProgressBar {
ProgressBar::new_spinner()
.with_finish(SPINNER_FINISH_MODE)
.with_style(
ProgressStyle::with_template("{spinner:.blue} {msg}")
.unwrap()
.tick_strings(&[". ", ".. ", "...", " ..", " .", " "]),
)
}
#[inline]
fn build_client(timeout: u64) -> reqwest::Result<Client> {
let client_builder = Client::builder();
let client_builder = if timeout > 0 {
client_builder.timeout(Duration::from_secs(timeout))
} else {
client_builder
};
client_builder.build()
}
#[inline]
async fn async_main(config: Config) -> anyhow::Result<()> {
let client = build_client(config.timeout).context("failed to build reqwest client")?;
let getter = BatchGetter::build(&client, &config.tags, config.num_imgs.get())
.expect("wrong config parser, please raise an issue on GitHub");
let spinner = build_spinner();
spinner.set_message("Fetching image data from Gelbooru API...");
spinner.enable_steady_tick(Duration::from_secs_f32(SPINNER_TICK_SECS));
let api_post_data = getter.run().await.context("failed to get data from API")?;
spinner.finish_with_message("Image data fetched successfully!");
if api_post_data.is_empty() {
println!(
"There is no image found with the given tags: {}",
config.tags
);
return Ok(());
}
let scheduler = Scheduler::build(client, config.download_dir, api_post_data)
.await
.context("Unable to ensure the existence of the download directory")?;
scheduler.launch().await;
Ok(())
}
fn main() -> anyhow::Result<ExitCode> {
let cli = Cli::parse();
let config = match cli.config {
Some(config) => config,
None => match Cli::get_config_from_editor(&mut Cli::command()) {
Ok(config) => config,
Err(err) => {
let _ = err.print();
return Ok(ExitCode::from(u8::try_from(err.exit_code()).unwrap()));
}
},
};
let runtime = Runtime::new().context("failed to build tokio runtime")?;
runtime.block_on(async {
tokio::select! {
result = async_main(config) => {result},
result = signal::ctrl_c() => {
result.expect("failed to listen for ctrl-c signal");
println!("Ctrl-C received, exiting...");
Ok(())
},
}
})?;
Ok(ExitCode::SUCCESS)
}