upstream-rs 2.3.0

Fetch package updates directly from the source.
Documentation
use anyhow::{Context, Result};
use console::style;
use std::time::{Duration, Instant};

use crate::{
    models::common::enums::TrustMode,
    models::upstream::Package,
    providers::provider_manager::ProviderManager,
    services::{
        packaging::{
            InstallPreview, PackageInstaller, PackageProgressEvent, PackageTransactionContext,
        },
        storage::package_storage::PackageStorage,
        trust::TrustedSignatureKeys,
    },
    utils::static_paths::UpstreamPaths,
};

const INSTALL_PROGRESS_UPDATE_INTERVAL: Duration = Duration::from_millis(100);

macro_rules! message {
    ($cb:expr, $($arg:tt)*) => {{
        if let Some(cb) = $cb.as_mut() {
            cb(&format!($($arg)*));
        }
    }};
}

pub struct InstallOperation<'a> {
    installer: PackageInstaller<'a>,
    package_storage: &'a mut PackageStorage,
    trusted_keys: TrustedSignatureKeys,
}

impl<'a> InstallOperation<'a> {
    pub fn new(
        provider_manager: &'a ProviderManager,
        package_storage: &'a mut PackageStorage,
        paths: &'a UpstreamPaths,
        trusted_keys: TrustedSignatureKeys,
    ) -> Result<Self> {
        Ok(Self {
            installer: PackageInstaller::new(provider_manager, paths)?,
            package_storage,
            trusted_keys,
        })
    }

    pub async fn install_bulk<F, G, H>(
        &mut self,
        packages: Vec<Package>,
        trust_mode: TrustMode,
        download_progress_callback: &mut Option<F>,
        overall_progress_callback: &mut Option<G>,
        message_callback: &mut Option<H>,
    ) -> Result<()>
    where
        F: FnMut(u64, u64),
        G: FnMut(u32, u32),
        H: FnMut(&str),
    {
        let total = packages.len() as u32;
        let mut completed = 0;
        let mut failures = 0;

        for package in packages {
            let package_name = package.name.clone();
            message!(message_callback, "Installing '{}' ...", package_name);

            let use_icon = &package.icon_path.is_some();
            let mut last_progress: Option<(u64, u64)> = None;
            let mut last_emit: Option<Instant> = None;
            let mut throttled_download_progress = download_progress_callback.as_mut().map(|cb| {
                |downloaded: u64, total: u64| {
                    last_progress = Some((downloaded, total));
                    let should_emit = last_emit
                        .map(|t| t.elapsed() >= INSTALL_PROGRESS_UPDATE_INTERVAL)
                        .unwrap_or(true);
                    if should_emit {
                        cb(downloaded, total);
                        last_emit = Some(Instant::now());
                    }
                }
            });

            match self
                .install_single(
                    package,
                    &None,
                    use_icon,
                    trust_mode,
                    &mut throttled_download_progress,
                    message_callback,
                )
                .await
                .context(format!("Failed to install package '{}'", package_name))
            {
                Ok(_) => {
                    message!(message_callback, "{}", style("Package installed").green());
                }
                Err(e) => {
                    message!(message_callback, "{} {}", style("Install failed:").red(), e);
                    failures += 1;
                }
            }

            if let (Some((downloaded, total)), Some(cb)) =
                (last_progress, download_progress_callback.as_mut())
            {
                cb(downloaded, total);
            }

            completed += 1;
            if let Some(cb) = overall_progress_callback.as_mut() {
                cb(completed, total);
            }
        }

        if failures > 0 {
            message!(
                message_callback,
                "{} package(s) failed to install",
                failures
            );
        }

        Ok(())
    }

    pub async fn install_single<F, H>(
        &mut self,
        package: Package,
        version: &Option<String>,
        add_entry: &bool,
        trust_mode: TrustMode,
        download_progress_callback: &mut Option<F>,
        message_callback: &mut Option<H>,
    ) -> Result<()>
    where
        F: FnMut(u64, u64),
        H: FnMut(&str),
    {
        let mut no_progress: Option<fn(PackageProgressEvent)> = None;
        self.install_single_with_progress(
            package,
            version,
            add_entry,
            trust_mode,
            download_progress_callback,
            message_callback,
            &mut no_progress,
        )
        .await
    }

    #[allow(clippy::too_many_arguments)]
    pub async fn install_single_with_context<F, H>(
        &mut self,
        package: Package,
        version: &Option<String>,
        add_entry: &bool,
        trust_mode: TrustMode,
        transaction_context: PackageTransactionContext,
        download_progress_callback: &mut Option<F>,
        message_callback: &mut Option<H>,
    ) -> Result<()>
    where
        F: FnMut(u64, u64),
        H: FnMut(&str),
    {
        let mut no_progress: Option<fn(PackageProgressEvent)> = None;
        self.install_release_with_context(
            package,
            version,
            add_entry,
            trust_mode,
            transaction_context,
            download_progress_callback,
            message_callback,
            &mut no_progress,
        )
        .await
    }

    #[allow(clippy::too_many_arguments)]
    pub async fn install_single_with_progress<F, H, P>(
        &mut self,
        package: Package,
        version: &Option<String>,
        add_entry: &bool,
        trust_mode: TrustMode,
        download_progress_callback: &mut Option<F>,
        message_callback: &mut Option<H>,
        progress_callback: &mut Option<P>,
    ) -> Result<()>
    where
        F: FnMut(u64, u64),
        H: FnMut(&str),
        P: FnMut(PackageProgressEvent),
    {
        self.install_release_with_context(
            package,
            version,
            add_entry,
            trust_mode,
            PackageTransactionContext::install(),
            download_progress_callback,
            message_callback,
            progress_callback,
        )
        .await
    }

    #[allow(clippy::too_many_arguments)]
    async fn install_release_with_context<F, H, P>(
        &mut self,
        package: Package,
        version: &Option<String>,
        add_entry: &bool,
        trust_mode: TrustMode,
        transaction_context: PackageTransactionContext,
        download_progress_callback: &mut Option<F>,
        message_callback: &mut Option<H>,
        progress_callback: &mut Option<P>,
    ) -> Result<()>
    where
        F: FnMut(u64, u64),
        H: FnMut(&str),
        P: FnMut(PackageProgressEvent),
    {
        self.installer
            .install_release_with_progress(
                self.package_storage,
                &self.trusted_keys,
                package,
                version,
                add_entry,
                trust_mode,
                transaction_context,
                download_progress_callback,
                message_callback,
                progress_callback,
            )
            .await
            .map(|_| ())
    }

    pub async fn install_local_artifact<H>(
        &mut self,
        package: Package,
        artifact_path: &std::path::Path,
        version: crate::models::common::version::Version,
        add_entry: &bool,
        transaction_context: PackageTransactionContext,
        message_callback: &mut Option<H>,
    ) -> Result<Package>
    where
        H: FnMut(&str),
    {
        self.installer
            .install_local_artifact(
                self.package_storage,
                package,
                artifact_path,
                version,
                add_entry,
                transaction_context,
                message_callback,
            )
            .await
    }

    pub async fn preview_single_install(
        &self,
        package: &Package,
        version: &Option<String>,
    ) -> Result<InstallPreview> {
        self.installer
            .preview_single_install(package, version)
            .await
    }
}