huber 0.3.7

Huber package, simplify `GitHub release` package management with a live awesome list of GitHub project
use std::cmp::Ordering;
use std::collections::HashMap;

use async_trait::async_trait;
use clap::{Arg, ArgMatches, Command};
use log::warn;
use simpledi_rs::di::{DIContainer, DIContainerTrait};

use huber_common::model::config::Config;
use huber_common::model::config::ConfigPath;
use huber_common::model::release::Release;
use huber_common::progress::progress;
use huber_common::result::Result;
use huber_procmacro::process_lock;

use crate::cmd::{CommandAsyncTrait, CommandTrait};
use crate::service::package::PackageService;
use crate::service::release::{ReleaseService, ReleaseTrait};
use crate::service::{ItemOperationAsyncTrait, ItemOperationTrait};

pub(crate) const CMD_NAME: &str = "update";

#[derive(Debug)]
pub(crate) struct UpdateCmd;

unsafe impl Send for UpdateCmd {}

unsafe impl Sync for UpdateCmd {}

impl UpdateCmd {
    pub(crate) fn new() -> Self {
        Self {}
    }
}

impl<'help> CommandTrait<'help> for UpdateCmd {
    fn app(&self) -> Command<'help> {
        Command::new(CMD_NAME)
            .visible_alias("up")
            .about("Updates the installed package(s)")
            .args([
                Arg::new("name")
                    .multiple_occurrences(true)
                    .value_name("package name")
                    .help("Package name(s)")
                    .required(false)
                    .takes_value(true),
                Arg::new("dryrun")
                    .short('d')
                    .long("dryrun")
                    .help("Dry run to show available updates")
                    .required(false),
            ])
    }
}

#[async_trait]
impl CommandAsyncTrait for UpdateCmd {
    async fn run(
        &self,
        _config: &Config,
        container: &DIContainer,
        matches: &ArgMatches,
    ) -> Result<()> {
        process_lock!();

        let release_service = container.get::<ReleaseService>().unwrap();
        let pkg_service = container.get::<PackageService>().unwrap();

        let mut names: Vec<String> = vec![];
        let mut release_caches: HashMap<String, Release> = hashmap! {};

        if matches.is_present("name") {
            let _names: Vec<&str> = matches.values_of("name").unwrap().collect();
            for n in _names {
                names.push(n.to_string())
            }
        } else {
            for r in release_service.list()? {
                names.push(r.name.clone());
                release_caches.insert(r.name.clone(), r);
            }
        }

        for ref name in names {
            if !release_service.has(name)? {
                return Err(anyhow!("{} not found", name));
            }

            let pkg = pkg_service.get(name)?;

            if !release_caches.contains_key(name) {
                let r = release_service.current(&pkg)?;
                release_caches.insert(name.to_string(), r);
            };

            let release = release_caches.get(name).unwrap();

            match release_service.get_latest(&pkg).await {
                Ok(mut release_latest) => match release_latest.compare(&release)? {
                    Ordering::Greater => {
                        update(&release_service, &matches, &release_latest, &release).await?;
                    }

                    _ => {
                        let sorted_pkgs_sum = pkg_service.find_summary(name, true).await?;
                        let latest_pkg_sum = sorted_pkgs_sum.first().unwrap();

                        if latest_pkg_sum.version.as_ref().unwrap() != &release.version {
                            release_latest.version = latest_pkg_sum.version.clone().unwrap();
                            release_latest.package.version = Some(release_latest.version.clone());

                            update(&release_service, &matches, &release_latest, &release).await?;
                        } else {
                            println!("The installed {} is the latest version already", release);
                            continue;
                        }
                    }
                },

                Err(e) => {
                    warn!(
                        "{}, the latest release not found (possibly prerelease available only): {}",
                        release, e,
                    )
                }
            }
        }

        Ok(())
    }
}

async fn update(
    release_service: &ReleaseService,
    matches: &ArgMatches,
    new_release: &Release,
    installed_release: &Release,
) -> Result<()> {
    if matches.is_present("dryrun") {
        println!("{} -> {}", installed_release, new_release);
    } else {
        progress(&format!(
            "Updating {} to {}",
            installed_release, new_release
        ))?;
        release_service.update(&new_release.package).await?;
        println!("{} to {} updated", installed_release, new_release);
    }

    Ok(())
}