cobble_core/minecraft/install/
libraries.rs

1use crate::error::InstallationResult;
2use crate::minecraft::install::extract_native::extract_native;
3use crate::minecraft::install::InstallationUpdate;
4use crate::minecraft::models::Library;
5use crate::minecraft::InstallOptions;
6use crate::utils::{download, download_progress_channel, Download, DownloadProgress};
7use futures::future::try_join_all;
8use futures::join;
9use std::path::Path;
10use tokio::sync::mpsc::{Receiver, Sender};
11
12/// Installs all libraries as defined in the provided version data.
13/// Also extracts native versions of said libraries.
14///
15/// This function provides updates during installation.
16// #[instrument(
17//     name = "install_libraries",
18//     level = "trace",
19//     skip_all,
20//     fields(
21//         version = &options.version_data.id,
22//         libraries_path = %options.libraries_path.display(),
23//         natives_path = %options.natives_path.display(),
24//         parallel_downloads = options.parallel_downloads,
25//         download_retries = options.download_retries,
26//         verify_downloads = options.verify_downloads,
27//     )
28// )]
29pub async fn install_libraries(
30    options: &InstallOptions,
31    update_sender: Sender<InstallationUpdate>,
32) -> InstallationResult<()> {
33    let needed_libraries = options.version_data.needed_libraries();
34
35    trace!("Building downloads for libraries");
36    let downloads = needed_libraries
37        .iter()
38        .map(|l| build_download(l, &options.libraries_path))
39        .collect::<Result<Vec<_>, _>>()?;
40
41    trace!("Preparing futures for downloading and channel translation");
42    let (tx, rx) = download_progress_channel(500);
43    let download_future = download(
44        downloads,
45        Some(tx),
46        options.parallel_downloads,
47        options.download_retries,
48        options.verify_downloads,
49    );
50    let map_future = map_progress(update_sender.clone(), rx);
51
52    trace!("Starting downloads");
53    join!(download_future, map_future).0?;
54
55    trace!("Preparing extraction of natives");
56    let extractions = needed_libraries
57        .iter()
58        .filter(|lib| lib.needs_extract())
59        .map(|lib| {
60            let excludes = lib
61                .extract
62                .as_ref()
63                .map(|e| e.exclude.clone())
64                .unwrap_or_default();
65            extract_native(
66                lib.jar_path(&options.libraries_path),
67                &options.natives_path,
68                excludes,
69                Some(update_sender.clone()),
70            )
71        });
72
73    trace!("Starting extraction");
74    try_join_all(extractions).await?;
75
76    Ok(())
77}
78
79async fn map_progress(
80    sender: Sender<InstallationUpdate>,
81    mut receiver: Receiver<DownloadProgress>,
82) {
83    while let Some(p) = receiver.recv().await {
84        let path = p
85            .file
86            .file_name()
87            .map(|s| s.to_string_lossy().to_string())
88            .unwrap_or_default();
89
90        let send_result = sender
91            .send(InstallationUpdate::Library((
92                path,
93                LibraryInstallationUpdate::Downloading(p),
94            )))
95            .await;
96
97        if send_result.is_err() {
98            debug!("Failed to translate DownloadProgress to InstallationUpdate");
99            break;
100        }
101    }
102}
103
104fn build_download(
105    library: &Library,
106    libraries_path: impl AsRef<Path>,
107) -> InstallationResult<Download> {
108    let (url, sha1, _size) = library.download_url();
109
110    let sha1 = sha1.as_deref().map(hex::decode).transpose()?;
111
112    Ok(Download {
113        url,
114        file: library.jar_path(libraries_path),
115        sha1,
116    })
117}
118
119/// Update of library installation
120#[derive(Clone, Debug)]
121pub enum LibraryInstallationUpdate {
122    /// Download status
123    Downloading(DownloadProgress),
124    /// Extracting
125    Extracting,
126}