rogue_tremolo 0.1.1

Client agnostic torrent synchronization
Documentation
use crate::get_config;
use crate::options::{Client, Software};
use crate::Database;
use crate::Torrent;
use colored::Colorize;
use deluge_api::get_torrents::FilterOptions as DelugeFilterOptions;
use deluge_api::{DelugeClientFactory, DelugeClientOptions};
use flat_db::Hash;
use log::{info, warn};
use qbittorrent_api::get_torrents::FilterOptions as QBittorentFilterOptions;
use qbittorrent_api::Status::Success;
use qbittorrent_api::{QBittorrentClientFactory, QBittorrentClientOptions};
use rogue_logging::Error;
use std::collections::BTreeMap;
use std::process::ExitCode;

pub async fn pull_command(client_id: String, category: Option<String>) -> Result<ExitCode, Error> {
    let options = get_config()?;
    let client = Client::get(client_id, &options)?;
    let torrents = match client.software {
        Software::Deluge => get_deluge_torrents(&client, category).await?,
        Software::QBittorrent => get_qbit_torrents(&client, category).await?,
    };
    let db = Database::new(&options, &client)?;
    db.metadata.set_many(torrents.clone(), true).await?;
    let files = torrents
        .into_iter()
        .filter_map(|(hash, torrent)| {
            let path = client.torrents.join(format!("{}.torrent", hash.to_hex()));
            if path.exists() {
                Some((hash, path))
            } else {
                warn!("Skipping: {}", torrent.name);
                warn!("File does not exist:\n{}", path.display());
                None
            }
        })
        .collect();
    db.files.set_many(files).await?;
    Ok(ExitCode::SUCCESS)
}

async fn get_deluge_torrents(
    client: &Client,
    category: Option<String>,
) -> Result<BTreeMap<Hash<20>, Torrent>, Error> {
    let client_options = DelugeClientOptions {
        host: client.host.clone(),
        password: client.password.clone(),
        user_agent: None,
        rate_limit_duration: None,
        rate_limit_count: None,
    };
    let factory = DelugeClientFactory {
        options: client_options,
    };
    let mut client = factory.create();
    let response = client.login().await?;
    if !response.get_result("pull torrents")? {
        return Err(Error {
            action: "login".to_owned(),
            domain: Some("Deluge API".to_owned()),
            ..Error::default()
        });
    }
    let filters = if let Some(category) = category {
        DelugeFilterOptions {
            label: Some(vec![category]),
            ..DelugeFilterOptions::default()
        }
    } else {
        DelugeFilterOptions::default()
    };
    let response = client.get_torrents(filters).await?;
    let torrents = response.get_result("get_torrents")?;
    info!("{} {} torrents", "Pulled".bold(), torrents.len());
    let torrents = torrents
        .into_iter()
        .map(|(key, torrent)| {
            let hash = Hash::from_string(&key).expect("hash should be valid");
            let torrent = Torrent::from_deluge(&torrent, hash);
            (hash, torrent)
        })
        .collect();
    Ok(torrents)
}

async fn get_qbit_torrents(
    client: &Client,
    category: Option<String>,
) -> Result<BTreeMap<Hash<20>, Torrent>, Error> {
    let client_options = QBittorrentClientOptions {
        host: client.host.clone(),
        username: client.username.clone().unwrap_or_default(),
        password: client.password.clone(),
        user_agent: None,
        rate_limit_duration: None,
        rate_limit_count: None,
    };
    let factory = QBittorrentClientFactory {
        options: client_options,
    };
    let mut client = factory.create();
    let response = client.login().await?;
    if response != Success {
        return Err(Error {
            action: "login".to_owned(),
            domain: Some("qBittorrent API".to_owned()),
            ..Error::default()
        });
    }
    let filters = if let Some(category) = category {
        QBittorentFilterOptions {
            category: Some(category),
            ..QBittorentFilterOptions::default()
        }
    } else {
        QBittorentFilterOptions::default()
    };
    let response = client.get_torrents(filters).await?;
    let torrents = response.get_result("get_torrents")?;
    info!("{} {} torrents", "Pulled".bold(), torrents.len());
    let torrents = torrents
        .into_iter()
        .map(|torrent| {
            let hash = Hash::from_string(&torrent.hash).expect("hash should be valid");
            let torrent = Torrent::from_qbittorrent(&torrent, hash);
            (hash, torrent)
        })
        .collect();
    Ok(torrents)
}