nyaa 0.9.1

A tui tool for browsing and downloading torrents from nyaa.si
Documentation
use std::{
    error::Error,
    fs,
    path::PathBuf,
    time::{Duration, SystemTime},
};

use crossterm::event::{self, Event};
use tokio::sync::mpsc;

use crate::{
    app::LoadType,
    client::{Client, ClientConfig, DownloadResult},
    config::CONFIG_FILE,
    results::Results,
    source::{Item, SourceConfig, SourceResponse, SourceResults, Sources},
    theme::{Theme, THEMES_PATH},
    widget::sort::SelectedSort,
};

pub trait EventSync {
    #[allow(clippy::too_many_arguments)]
    fn load_results(
        self,
        tx_res: mpsc::Sender<Result<SourceResults, Box<dyn Error + Send + Sync>>>,
        load_type: LoadType,
        src: Sources,
        client: reqwest::Client,
        search: SearchQuery,
        config: SourceConfig,
        theme: Theme,
        date_format: Option<String>,
    ) -> impl std::future::Future<Output = ()> + std::marker::Send + 'static;
    fn download(
        self,
        tx_dl: mpsc::Sender<DownloadResult>,
        batch: bool,
        items: Vec<Item>,
        config: ClientConfig,
        rq_client: reqwest::Client,
        client: Client,
    ) -> impl std::future::Future<Output = ()> + std::marker::Send + 'static;
    fn read_event_loop(
        self,
        tx_evt: mpsc::Sender<Event>,
    ) -> impl std::future::Future<Output = ()> + std::marker::Send + 'static;
    fn watch_config_loop(
        self,
        tx_evt: mpsc::Sender<ReloadType>,
    ) -> impl std::future::Future<Output = ()> + std::marker::Send + 'static;
}

#[derive(Clone)]
pub struct AppSync {
    config_path: PathBuf,
}

impl AppSync {
    pub fn new(config_path: PathBuf) -> Self {
        Self { config_path }
    }
}

#[derive(Clone, Default)]
pub struct SearchQuery {
    pub query: String,
    pub page: usize,
    pub category: usize,
    pub filter: usize,
    pub sort: SelectedSort,
    pub user: Option<String>,
}

#[derive(Clone)]
pub enum ReloadType {
    Config,
    Theme(String),
}

fn watch(path: &PathBuf, last_modified: SystemTime) -> bool {
    if let Ok(meta) = fs::metadata(path) {
        if let Ok(time) = meta.modified() {
            if time > last_modified {
                return true;
            }
        }
    }
    false
}

impl EventSync for AppSync {
    async fn load_results(
        self,
        tx_res: mpsc::Sender<Result<SourceResults, Box<dyn Error + Send + Sync>>>,
        load_type: LoadType,
        src: Sources,
        client: reqwest::Client,
        search: SearchQuery,
        config: SourceConfig,
        theme: Theme,
        date_format: Option<String>,
    ) {
        let res = src
            .load(load_type, &client, &search, &config, date_format)
            .await;
        let fmt = match res {
            Ok(SourceResponse::Results(res)) => Ok(SourceResults::Results(Results::new(
                search.clone(),
                res.clone(),
                src.format_table(&res.items, &search, &config, &theme),
            ))),
            #[cfg(feature = "captcha")]
            Ok(SourceResponse::Captcha(c)) => Ok(SourceResults::Captcha(c)),
            Err(e) => Err(e),
        };
        let _ = tx_res.send(fmt).await;
    }

    async fn download(
        self,
        tx_dl: mpsc::Sender<DownloadResult>,
        batch: bool,
        items: Vec<Item>,
        config: ClientConfig,
        rq_client: reqwest::Client,
        client: Client,
    ) {
        let res = match batch {
            true => client.batch_download(items, config, rq_client).await,
            false => client.download(items[0].clone(), config, rq_client).await,
        };
        let _ = tx_dl.send(res).await;
    }

    async fn read_event_loop(self, tx_evt: mpsc::Sender<Event>) {
        loop {
            if let Ok(evt) = event::read() {
                let _ = tx_evt.send(evt).await;
            }
        }
    }

    async fn watch_config_loop(self, tx_cfg: mpsc::Sender<ReloadType>) {
        let config_path = self.config_path.clone();
        let config_file = config_path.join(CONFIG_FILE);
        let themes_path = config_path.join(THEMES_PATH);
        let now = SystemTime::now();

        let mut last_modified = now;
        loop {
            if watch(&config_file, last_modified) {
                last_modified = SystemTime::now();
                let _ = tx_cfg.send(ReloadType::Config).await;
            }
            let theme_files = fs::read_dir(&themes_path).ok().and_then(|v| {
                v.filter_map(Result::ok)
                    .map(|v| v.path())
                    .find(|p| watch(p, last_modified))
            });
            if let Some(theme) = theme_files {
                last_modified = SystemTime::now();
                let _ = tx_cfg
                    .send(ReloadType::Theme(
                        theme
                            .file_name()
                            .unwrap_or_default()
                            .to_string_lossy()
                            .to_string(),
                    ))
                    .await;
            }
            tokio::time::sleep(Duration::from_millis(500)).await;
        }
    }
}