qbt-clean 0.120.0

Automated rules-based cleaning of qBittorrent torrents.
use anyhow::Context as _;

mod args; pub(crate) use args::*;
mod args_config; pub(crate) use args_config::*;
mod cmd_clean; pub(crate) use cmd_clean::*;
mod cmd_group; pub(crate) use cmd_group::*;
mod cmp; pub(crate) use cmp::*;
mod config; pub(crate) use config::*;
mod float; pub(crate) use float::*;
mod fmt; pub(crate) use fmt::*;
mod global; pub(crate) use global::*;
mod group; pub(crate) use group::*;
mod group_torrent; pub(crate) use group_torrent::*;
mod info_hash; pub(crate) use info_hash::*;
mod match_; pub(crate) use match_::*;
mod parse; pub(crate) use parse::*;
mod pin_reason; pub(crate) use pin_reason::*;
mod requirement; pub(crate) use requirement::*;
mod rule; pub(crate) use rule::*;
mod rule_result; pub(crate) use rule_result::*;
mod serde_util; pub(crate) use serde_util::*;
mod torrent; pub(crate) use torrent::*;
mod torrent_state; pub(crate) use torrent_state::*;
mod tracker; pub(crate) use tracker::*;
mod units; pub(crate) use units::*;

#[cfg(test)] mod requirement_test;

#[tokio::main]
async fn main() -> anyhow::Result<()> {
	let args: crate::Args = clap::Parser::parse();
	let mut config = args.config.read()?;

	if config.age_d_max_weight == 0.0
		&& config.age_d_min_weight == 0.0
		&& config.copies_weight == 0.0
		&& config.last_activity_d_weight == 0.0
		&& config.no_scored_torrents_weight == 0.0
		&& config.seeder_count_weight == 0.0
		&& config.size_weight == 0.0
	{
		eprintln!("WARN: All weights are zero, setting seeder-count-weight to 1.0");
		config.seeder_count_weight = 1.0;
	}

	let http = reqwest::Client::builder()
		.user_agent("qbt-clean/0")
		.connect_timeout(std::time::Duration::from_secs(60))
		.default_headers((&config.qbt_headers).try_into()?)
		.timeout(std::time::Duration::from_secs(600))
		.cookie_store(true)
		.build().unwrap();

	let mut qbt = config.qbt_url.clone();
	qbt.set_username("").map_err(|()| anyhow::Error::msg("Invalid qbt_url"))?;
	qbt.set_password(None).map_err(|()| anyhow::Error::msg("Invalid qbt_url"))?;

	http.post(qbt.join("/api/v2/auth/login")?)
		.form(&[
			("username", config.qbt_url.username()),
			("password", config.qbt_url.password().unwrap_or("")),
		])
		.send().await.context("Sending login request")?
		.error_for_status().context("Login request status")?;

	eprintln!("Logged in.");

	let global = crate::Global {
		config,
		http,
		now: std::time::SystemTime::now(),
		qbt,
	};

	let main_result = async {
		match args.command {
			crate::Command::Clean => cmd_clean(&global, false).await,
			crate::Command::Group(a) => cmd_group(a, &global).await,
			crate::Command::Simulate => cmd_clean(&global, true).await,
		}
	}.await;

	let r = global.http.post(global.qbt.join("/api/v2/auth/logout")?)
		.send().await?
		.error_for_status();
	if let Err(err) = r {
		eprintln!("Warning: Faled to log out: {}", err);
	}

	main_result
}