fav_cli 0.0.1-alpha3

Fav's CLI, helping persist the remote source. Repo: https://github.com/kingwingfly/fav
Documentation
use fav_core::ops::{ResOpsExt, SetOpsExt};
use fav_core::prelude::*;
use fav_core::status::SetStatusExt;
use fav_core::visual::{TableRes, TableSet, TableSets};
use fav_utils::bili::{Bili, BiliRes, BiliSet, BiliSets};
use tracing::{info, warn};

pub(super) fn init() -> FavCoreResult<()> {
    #[cfg(not(test))]
    std::fs::create_dir_all(".fav")?;
    Bili::default().write()?;
    BiliSets::default().write()?;
    Ok(())
}

pub(super) async fn login() -> FavCoreResult<()> {
    let mut bili = Bili::read()?;
    bili.login().await?;
    bili.write()
}

pub(super) async fn logout() -> FavCoreResult<()> {
    let mut bili = Bili::read()?;
    bili.logout().await
}

pub fn status(id: String) -> FavCoreResult<()> {
    let mut sets = BiliSets::read()?;
    let id_ = Id::from(&id);
    if let Some(s) = try_find_set(&mut sets, &id_) {
        s.table();
    } else if let Some(r) = try_find_res(&mut sets, &id_) {
        r.table();
    } else {
        return Err(FavCoreError::IdNotUsable(id));
    }
    Ok(())
}

pub fn status_all(sets: bool, res: bool, track: bool) -> FavCoreResult<()> {
    let mut sets_ = BiliSets::read()?;
    if sets {
        let sub = sets_.subset(|s| s.check_status(StatusFlags::TRACK) | !track);
        sub.table();
    }
    if res {
        for set in sets_.iter_mut() {
            let sub = set.subset(|r| r.check_status(StatusFlags::TRACK) | !track);
            sub.table();
        }
    }
    Ok(())
}

pub(super) async fn fetch() -> FavCoreResult<()> {
    let bili = Bili::read()?;
    let mut sets = BiliSets::read()?;
    bili.fetch_sets(&mut sets).await?;
    let mut sub = sets.subset(|s| s.check_status(StatusFlags::TRACK));
    bili.batch_fetch_set(&mut sub).await?;
    for set in sub.iter_mut() {
        let mut sub = set.subset(|r| {
            r.check_status(StatusFlags::TRACK)
                & !r.check_status(StatusFlags::FETCHED)
                & !r.check_status(StatusFlags::EXPIRED)
        });
        bili.batch_fetch_res(&mut sub).await?;
    }
    sets.write()
}

pub(super) fn track(id: String) -> FavCoreResult<()> {
    let mut sets = BiliSets::read()?;
    let id_ = Id::from(&id);
    if let Some(s) = try_find_set(&mut sets, &id_) {
        s.on_status(StatusFlags::TRACK);
        s.on_res_status(StatusFlags::TRACK);
    } else if let Some(r) = try_find_res(&mut sets, &id_) {
        r.on_status(StatusFlags::TRACK);
    } else {
        return Err(FavCoreError::IdNotUsable(id));
    }
    sets.write()
}

pub(super) fn untrack(id: String) -> FavCoreResult<()> {
    let mut sets = BiliSets::read()?;
    let id_ = Id::from(&id);
    if let Some(s) = try_find_set(&mut sets, &id_) {
        s.off_status(StatusFlags::TRACK);
        s.off_res_status(StatusFlags::TRACK);
    } else if let Some(r) = try_find_res(&mut sets, &id_) {
        r.off_status(StatusFlags::TRACK);
    } else {
        return Err(FavCoreError::IdNotUsable(id));
    }
    sets.write()
}

pub(super) async fn pull_all() -> FavCoreResult<()> {
    fetch().await?;
    let bili = Bili::read()?;
    let mut sets = BiliSets::read()?;
    let mut sub = sets.subset(|s| s.check_status(StatusFlags::TRACK));
    for set in sub.iter_mut() {
        let mut sub = set.subset(|r| {
            !r.check_status(StatusFlags::SAVED)
                & !r.check_status(StatusFlags::EXPIRED)
                & r.check_status(StatusFlags::TRACK | StatusFlags::FETCHED)
        });
        bili.batch_pull_res(&mut sub).await?;
    }
    sets.write()
}

pub(super) async fn pull(id: String) -> FavCoreResult<()> {
    fetch().await?;
    let bili = Bili::read()?;
    let mut sets = BiliSets::read()?;
    let id_ = Id::from(&id);
    if let Some(s) = try_find_set(&mut sets, &id_) {
        let mut sub = s.subset(|r| {
            !r.check_status(StatusFlags::SAVED)
                & !r.check_status(StatusFlags::EXPIRED)
                & r.check_status(StatusFlags::TRACK | StatusFlags::FETCHED)
        });
        bili.batch_pull_res(&mut sub).await?;
    } else if let Some(r) = try_find_res(&mut sets, &id_) {
        if !r.check_status(StatusFlags::EXPIRED)
            & r.check_status(StatusFlags::TRACK | StatusFlags::FETCHED)
        {
            bili.pull_res(r).await?;
        }
    } else {
        return Err(FavCoreError::IdNotUsable(id));
    }
    sets.write()
}

pub(crate) async fn daemon(interval: u64) {
    if interval < 15 {
        warn!("Interval would better to be greater than 15 minutes.");
    }
    pull_all().await.ok();
    loop {
        let next_ts_local = (chrono::Utc::now() + chrono::Duration::minutes(interval as i64))
            .with_timezone(&chrono::Local)
            .format("%Y-%m-%d %H:%M:%S")
            .to_string();
        info!(
            "Next job will be {} minutes later at {}.\n",
            interval, next_ts_local
        );
        tokio::select! {
            _ = tokio::time::sleep(tokio::time::Duration::from_secs(interval * 60)) => {
                pull_all().await.ok();
            }
            _ = tokio::signal::ctrl_c() => {
                info!("Received Ctrl-C, exiting.");
                break;
            }
        }
    }
}

fn try_find_set<'a>(sets: &'a mut BiliSets, id: &Id) -> Option<&'a mut BiliSet> {
    sets.iter_mut().find(|s| s.id() == *id)
}

fn try_find_res<'a>(sets: &'a mut BiliSets, id: &Id) -> Option<&'a mut BiliRes> {
    for set in sets.iter_mut() {
        if let Some(r) = set.iter_mut().find(|r| r.id() == *id) {
            return Some(r);
        }
    }
    None
}