cobble-core 1.2.0

Library for managing, installing and launching Minecraft instances and more.
Documentation
use crate::error::InstallationResult;
use crate::minecraft::install::extract_native::extract_native;
use crate::minecraft::install::InstallationUpdate;
use crate::minecraft::models::Library;
use crate::minecraft::InstallOptions;
use crate::utils::{download, download_progress_channel, Download, DownloadProgress};
use futures::future::try_join_all;
use futures::join;
use std::path::Path;
use tokio::sync::mpsc::{Receiver, Sender};

/// Installs all libraries as defined in the provided version data.
/// Also extracts native versions of said libraries.
///
/// This function provides updates during installation.
// #[instrument(
//     name = "install_libraries",
//     level = "trace",
//     skip_all,
//     fields(
//         version = &options.version_data.id,
//         libraries_path = %options.libraries_path.display(),
//         natives_path = %options.natives_path.display(),
//         parallel_downloads = options.parallel_downloads,
//         download_retries = options.download_retries,
//         verify_downloads = options.verify_downloads,
//     )
// )]
pub async fn install_libraries(
    options: &InstallOptions,
    update_sender: Sender<InstallationUpdate>,
) -> InstallationResult<()> {
    let needed_libraries = options.version_data.needed_libraries();

    trace!("Building downloads for libraries");
    let downloads = needed_libraries
        .iter()
        .map(|l| build_download(l, &options.libraries_path))
        .collect::<Result<Vec<_>, _>>()?;

    trace!("Preparing futures for downloading and channel translation");
    let (tx, rx) = download_progress_channel(500);
    let download_future = download(
        downloads,
        Some(tx),
        options.parallel_downloads,
        options.download_retries,
        options.verify_downloads,
    );
    let map_future = map_progress(update_sender.clone(), rx);

    trace!("Starting downloads");
    join!(download_future, map_future).0?;

    trace!("Preparing extraction of natives");
    let extractions = needed_libraries
        .iter()
        .filter(|lib| lib.needs_extract())
        .map(|lib| {
            let excludes = lib
                .extract
                .as_ref()
                .map(|e| e.exclude.clone())
                .unwrap_or_default();
            extract_native(
                lib.jar_path(&options.libraries_path),
                &options.natives_path,
                excludes,
                Some(update_sender.clone()),
            )
        });

    trace!("Starting extraction");
    try_join_all(extractions).await?;

    Ok(())
}

async fn map_progress(
    sender: Sender<InstallationUpdate>,
    mut receiver: Receiver<DownloadProgress>,
) {
    while let Some(p) = receiver.recv().await {
        let path = p
            .file
            .file_name()
            .map(|s| s.to_string_lossy().to_string())
            .unwrap_or_default();

        let send_result = sender
            .send(InstallationUpdate::Library((
                path,
                LibraryInstallationUpdate::Downloading(p),
            )))
            .await;

        if send_result.is_err() {
            debug!("Failed to translate DownloadProgress to InstallationUpdate");
            break;
        }
    }
}

fn build_download(
    library: &Library,
    libraries_path: impl AsRef<Path>,
) -> InstallationResult<Download> {
    let (url, sha1, _size) = library.download_url();

    let sha1 = sha1.as_deref().map(hex::decode).transpose()?;

    Ok(Download {
        url,
        file: library.jar_path(libraries_path),
        sha1,
    })
}

/// Update of library installation
#[derive(Clone, Debug)]
pub enum LibraryInstallationUpdate {
    /// Download status
    Downloading(DownloadProgress),
    /// Extracting
    Extracting,
}