kyanite 0.5.1

A modular, expandable, gallery collector.
use crate::collectors::danbooru::DanbooruCollector;
use log::{debug, error, info};

use crate::collectors::e621::E621Collector;
use crate::collectors::gelbooru::GelbooruCollector;
use crate::collectors::konachan::KonachanCollector;
use crate::collectors::rule34::Rule34Collector;
use crate::collectors::xbooru::XBooruCollector;
use crate::collectors::yandere::YandereCollector;
use crate::item::KyaniteItem;
use crate::manifest::KyaniteManifest;
use crate::params::KyaniteParams;
use crate::stats::StatsContainer;
use crate::utility::KyaniteUtility;

#[async_trait::async_trait]
pub trait KyaniteCollector {
    fn id(&self) -> &'static str;
    fn name(&self) -> &'static str;
    fn api_base(&self) -> &'static str;
    fn site_base(&self) -> &'static str;
    fn tags_argument(&self) -> &'static str;
    fn page_argument(&self) -> &'static str;
    fn starting_marker(&self) -> &'static str {
        if self.api_base().contains('?') {
            "&"
        } else {
            "?"
        }
    }
    fn api_by_page(&self, tags: String, page: u64) -> String {
        let api = format!(
            "{}{}{}={}&{}={}",
            &self.api_base(),
            &self.starting_marker(),
            &self.tags_argument(),
            tags,
            &self.page_argument(),
            page
        );
        debug!("{} API: {}", &self.name(), &api);
        api
    }
    async fn collect(&self, tags: Vec<String>) -> anyhow::Result<Vec<KyaniteItem>>;
}

pub struct CollectorCore {
    stats: StatsContainer,
    params: KyaniteParams,
    manifest: KyaniteManifest,
    collectors: Vec<Box<dyn KyaniteCollector>>,
}

impl CollectorCore {
    pub fn new(params: KyaniteParams) -> anyhow::Result<Self> {
        let stats = StatsContainer::new();
        let collectors = vec![
            DanbooruCollector::boxed(),
            E621Collector::boxed(),
            GelbooruCollector::boxed(),
            KonachanCollector::boxed(),
            Rule34Collector::boxed(),
            XBooruCollector::boxed(),
            YandereCollector::boxed(),
        ];
        let manifest = KyaniteManifest::new()?;
        Ok(Self {
            stats,
            params,
            manifest,
            collectors,
        })
    }

    pub async fn collect(&mut self) -> anyhow::Result<Vec<KyaniteItem>> {
        info!(
            "Searching for {} on {}.",
            &self.params.tags.join(", "),
            &self.params.sources.join(", ")
        );
        let mut items = Vec::new();
        for collector in &self.collectors {
            for source in &self.params.sources {
                if source == collector.id() || source == collector.name() || source == "all" {
                    let collected = match collector.collect((&self.params.tags).to_owned()).await {
                        Ok(clctd) => clctd,
                        Err(why) => {
                            error!(
                                "Failed collecting items from {}: {:#?}",
                                collector.id(),
                                why
                            );
                            Vec::new()
                        }
                    };
                    for item in collected {
                        items.push(item);
                    }
                }
            }
        }
        let trimmed = KyaniteItem::trim(items);
        let skipped = KyaniteItem::skip(trimmed)?;
        let sorted = KyaniteItem::sort(skipped);
        Ok(sorted)
    }

    pub async fn download(&mut self, items: Option<Vec<KyaniteItem>>) -> anyhow::Result<()> {
        let items = match items {
            Some(items) => items,
            None => self.collect().await?,
        };
        let total = items.len();
        for mut item in items {
            let resp = item.save(&mut self.stats, &mut self.manifest).await?;
            info!(
                "{} [{}] [{}] [{}/{}]: {}",
                resp,
                self.stats.describe(),
                KyaniteUtility::human_size(self.stats.size, 3f64, "GiB"),
                self.stats.count(),
                &total,
                item.describe()
            );
        }
        Ok(())
    }
}