upstream-rs 2.4.0

Fetch package updates directly from the source.
Documentation
use std::path::Path;

use anyhow::{Context, Result, anyhow};

use crate::{
    models::{
        common::{enums::TrustMode, version::Version},
        provider::{Asset, Release},
        upstream::Package,
    },
    providers::provider_manager::ProviderManager,
    services::{
        packaging::{
            InstallPreview, PackageInstaller, PackagePhase, PackageProgressEvent,
            transaction_recorder::{PackageTransaction, failed_package, successful_package},
        },
        storage::{
            package_storage::PackageStorage,
            transaction_storage::{TransactionKind, UndoActionKind},
        },
        trust::TrustedSignatureKeys,
    },
    utils::static_paths::UpstreamPaths,
};

#[derive(Debug, Clone)]
pub enum PackageTransactionContext {
    Record {
        kind: TransactionKind,
        undo_kind: Option<UndoActionKind>,
    },
    CoveredByParent,
}

impl PackageTransactionContext {
    pub fn install() -> Self {
        Self::Record {
            kind: TransactionKind::Install,
            undo_kind: Some(UndoActionKind::Remove),
        }
    }

    pub fn build() -> Self {
        Self::Record {
            kind: TransactionKind::Build,
            undo_kind: Some(UndoActionKind::Remove),
        }
    }
}

pub struct ReleaseInstallRequest {
    pub package: Package,
    pub version: Option<String>,
    pub add_entry: bool,
    pub trust_mode: TrustMode,
    pub transaction_context: PackageTransactionContext,
}

pub struct SelectedAssetInstallRequest<'a> {
    pub package: Package,
    pub release: &'a Release,
    pub asset: &'a Asset,
    pub add_entry: bool,
    pub trust_mode: TrustMode,
    pub transaction_context: PackageTransactionContext,
}

pub struct LocalArtifactInstallRequest<'a> {
    pub package: Package,
    pub artifact_path: &'a Path,
    pub version: Version,
    pub add_entry: bool,
    pub transaction_context: PackageTransactionContext,
}

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

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,
            paths,
        })
    }

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

    pub async fn install_release<F, H, P>(
        &mut self,
        request: ReleaseInstallRequest,
        download_progress_callback: &mut Option<F>,
        message_callback: &mut Option<H>,
        progress_callback: &mut Option<P>,
    ) -> Result<Package>
    where
        F: FnMut(u64, u64),
        H: FnMut(&str),
        P: FnMut(PackageProgressEvent),
    {
        let package_name = request.package.name.clone();
        let transaction =
            self.start_transaction(request.transaction_context, package_name.clone())?;

        let result = match self
            .installer
            .install_release(
                &self.trusted_keys,
                request.package,
                &request.version,
                &request.add_entry,
                request.trust_mode,
                download_progress_callback,
                message_callback,
                progress_callback,
            )
            .await
        {
            Ok(installed_package) => {
                self.save_installed_package(installed_package, message_callback, progress_callback)
            }
            Err(err) => Err(err),
        };

        self.finish_transaction(transaction, &package_name, result)
    }

    pub async fn install_selected_asset<F, H, P>(
        &mut self,
        request: SelectedAssetInstallRequest<'_>,
        download_progress_callback: &mut Option<F>,
        message_callback: &mut Option<H>,
        progress_callback: &mut Option<P>,
    ) -> Result<Package>
    where
        F: FnMut(u64, u64),
        H: FnMut(&str),
        P: FnMut(PackageProgressEvent),
    {
        let package_name = request.package.name.clone();
        let transaction =
            self.start_transaction(request.transaction_context, package_name.clone())?;

        let result = match self
            .installer
            .install_selected_asset(
                &self.trusted_keys,
                request.package,
                request.release,
                request.asset,
                &request.add_entry,
                request.trust_mode,
                download_progress_callback,
                message_callback,
                progress_callback,
            )
            .await
        {
            Ok(installed_package) => {
                self.save_installed_package(installed_package, message_callback, progress_callback)
            }
            Err(err) => Err(err),
        };

        self.finish_transaction(transaction, &package_name, result)
    }

    pub async fn install_local_artifact<H, P>(
        &mut self,
        request: LocalArtifactInstallRequest<'_>,
        message_callback: &mut Option<H>,
        progress_callback: &mut Option<P>,
    ) -> Result<Package>
    where
        H: FnMut(&str),
        P: FnMut(PackageProgressEvent),
    {
        let package_name = request.package.name.clone();
        let transaction =
            self.start_transaction(request.transaction_context, package_name.clone())?;

        let result = match self
            .installer
            .install_local_artifact(
                request.package,
                request.artifact_path,
                request.version,
                &request.add_entry,
                message_callback,
                progress_callback,
            )
            .await
        {
            Ok(installed_package) => {
                self.save_installed_package(installed_package, message_callback, progress_callback)
            }
            Err(err) => Err(err),
        };

        self.finish_transaction(transaction, &package_name, result)
    }

    fn start_transaction(
        &self,
        context: PackageTransactionContext,
        package_name: String,
    ) -> Result<Option<PackageTransaction>> {
        match context {
            PackageTransactionContext::Record { kind, undo_kind } => Ok(Some(
                PackageTransaction::start(self.paths, kind, vec![package_name], undo_kind)?,
            )),
            PackageTransactionContext::CoveredByParent => Ok(None),
        }
    }

    fn finish_transaction(
        &self,
        transaction: Option<PackageTransaction>,
        package_name: &str,
        result: Result<Package>,
    ) -> Result<Package> {
        match (result, transaction) {
            (Ok(installed_package), Some(transaction)) => {
                transaction.complete(vec![successful_package(
                    package_name.to_string(),
                    None,
                    Some(installed_package.version.to_string()),
                )])?;
                Ok(installed_package)
            }
            (Err(err), Some(transaction)) => {
                let summary = crate::output::error_summary(&err);
                transaction.fail(
                    vec![failed_package(
                        package_name.to_string(),
                        None,
                        None,
                        summary.clone(),
                    )],
                    summary,
                )?;
                Err(err)
            }
            (Ok(installed_package), None) => Ok(installed_package),
            (Err(err), None) => Err(err),
        }
    }

    fn save_installed_package<H, P>(
        &mut self,
        installed_package: Package,
        message_callback: &mut Option<H>,
        progress_callback: &mut Option<P>,
    ) -> Result<Package>
    where
        H: FnMut(&str),
        P: FnMut(PackageProgressEvent),
    {
        if let Some(cb) = progress_callback.as_mut() {
            cb(PackageProgressEvent::Phase(PackagePhase::SavingMetadata));
        }

        if let Err(err) = self
            .package_storage
            .add_or_update_package(installed_package.clone())
            .context(format!(
                "Failed to save package '{}' to storage",
                installed_package.name
            ))
        {
            return self.fail_after_metadata_error(installed_package, err, message_callback);
        }

        Ok(installed_package)
    }

    fn fail_after_metadata_error<H>(
        &self,
        installed_package: Package,
        err: anyhow::Error,
        message_callback: &mut Option<H>,
    ) -> Result<Package>
    where
        H: FnMut(&str),
    {
        match self
            .installer
            .cleanup_partial_install(&installed_package, message_callback)
        {
            Ok(()) => Err(err.context(format!(
                "Rolled back partial install for '{}'",
                installed_package.name
            ))),
            Err(cleanup_err) => Err(anyhow!(
                "{}. Additionally failed to roll back partial install for '{}': {}",
                err,
                installed_package.name,
                cleanup_err
            )),
        }
    }
}