1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
//! QuiltMC 下载源数据结构
use anyhow::Context;
use async_trait::async_trait;
use serde::Deserialize;

use super::Downloader;
use crate::{package::PackageName, prelude::*, version::structs::VersionMeta};

/// QuiltMC 加载器的版本元数据和其源码对照表的版本元数据
#[derive(Debug, Deserialize, PartialEq, Eq, Clone)]
pub struct LoaderMetaItem {
    /// QuiltMC 加载器的元数据
    pub loader: LoaderStruct,
    /// 源码对照表的元数据
    pub intermediary: IntermediaryStruct,
}

/// 源码对照表的信息
#[derive(Debug, Deserialize, PartialEq, Eq, Clone)]
pub struct IntermediaryStruct {
    /// 源码对照表对应的 Maven 仓库文件链接
    pub maven: String,
    /// 源码对照表的版本
    pub version: String,
}

/// QuiltMC 加载器的信息
#[derive(Debug, Deserialize, PartialEq, Eq, Clone)]
pub struct LoaderStruct {
    /// QuiltMC 加载器对应的 Maven 仓库文件链接
    pub maven: String,
    /// QuiltMC 加载器的版本
    pub version: String,
}

/// QuiltMC 模组加载器的安装特质
///
/// 可以通过引入本特质和使用 [`crate::download::Downloader`] 来安装模组加载器
#[async_trait]
pub trait QuiltMCDownloadExt: Sync {
    /// 根据原版版本号获取该版本下可用的 QuiltMC 模组加载器
    async fn get_avaliable_loaders(&self, vanilla_version: &str) -> DynResult<Vec<LoaderMetaItem>>;
    // http://resources.download.minecraft.net
    /// 下载 QuiltMC 模组加载器所需的库文件
    async fn download_library(&self, name: &str, url: String) -> DynResult;
    /// 下载 QuiltMC 模组加载器,安装时需要配合 download_quiltmc_post 来合并版本元数据
    async fn download_quiltmc_pre(
        &self,
        version_name: &str,
        version_id: &str,
        loader_version: &str,
    ) -> DynResult;
    /// 将 QuiltMC 模组加载器的版本元数据与原版版本元数据合并以完成最后安装步骤
    async fn download_quiltmc_post(&self, version_name: &str) -> DynResult;
}

#[async_trait]
impl<R: Reporter> QuiltMCDownloadExt for Downloader<R> {
    async fn get_avaliable_loaders(&self, vanilla_version: &str) -> DynResult<Vec<LoaderMetaItem>> {
        let mut result = crate::http::retry_get(format!(
            "https://meta.quiltmc.org/v3/versions/loader/{vanilla_version}"
        ))
        .await
        .map_err(|e| {
            anyhow::anyhow!(
                "获取为原版 {} 可用的 QuiltMC Loader 版本失败 {:?}",
                vanilla_version,
                e
            )
        })?;
        if result.status().is_success() {
            let result = result.body_json().await.map_err(|e| anyhow::anyhow!(e))?;
            Ok(result)
        } else {
            Ok(vec![])
        }
    }

    async fn download_library(&self, name: &str, url: String) -> DynResult {
        let url = if url.is_empty() {
            "https://maven.quiltmc.org/repository/release"
        } else {
            url.trim_end_matches('/')
        };
        let package_name = name.parse::<PackageName>().unwrap();
        let full_path = package_name.to_maven_jar_path(self.minecraft_library_path.as_str());
        let r = self.reporter.sub();
        inner_future::fs::create_dir_all(
            &full_path[..full_path.rfind('/').unwrap_or(full_path.len())],
        )
        .await
        .unwrap_or_default();
        r.set_message(format!("正在下载 QuiltMC 支持库 {name}"));
        r.add_max_progress(1.);
        if std::path::Path::new(&full_path).is_file() {
            if self.verify_data {
                let mut file = inner_future::fs::OpenOptions::new()
                    .read(true)
                    .open(&full_path)
                    .await?;
                r.set_message(format!("正在获取数据摘要以验证完整性 {name}"));
                r.add_max_progress(1.);
                let sha1 = crate::http::retry_get_string(format!(
                    "{}.sha1",
                    package_name.to_maven_jar_path(url)
                ))
                .await?;
                let current_sha1 = crate::utils::get_data_sha1(&mut file).await?;
                if sha1 == current_sha1 {
                    r.add_progress(1.);
                    return Ok(());
                }
            } else {
                r.add_progress(1.);
                return Ok(());
            }
        }
        let uris = [package_name.to_maven_jar_path(url)];
        crate::http::download(&uris, &full_path, 0)
            .await
            .map_err(|e| anyhow::anyhow!("下载 QuiltMC 依赖库失败:{:?}", e))?;
        Ok(())
    }

    async fn download_quiltmc_pre(
        &self,
        version_name: &str,
        version_id: &str,
        loader_version: &str,
    ) -> DynResult {
        let mut loader_meta_res = crate::http::retry_get(format!(
            "https://meta.quiltmc.org/v3/versions/loader/{version_id}/{loader_version}/profile/json"
        ))
        .await
        .map_err(|e| anyhow::anyhow!("获取 QuiltMC 版本元数据失败:{:?}", e))?;
        let res = loader_meta_res
            .body_bytes()
            .await
            .map_err(|e| anyhow::anyhow!(e))?;
        inner_future::fs::write(
            format!(
                "{}/{}/{}-quiltmc-loader.tmp.json",
                self.minecraft_version_path.as_str(),
                version_name,
                version_name
            ),
            &res,
        )
        .await?;
        let meta: VersionMeta =
            serde_json::from_slice(&res).context("无法解析 QuiltMC 版本元数据")?;
        let mut libraries_threads = Vec::with_capacity(meta.libraries.len());

        for lib in &meta.libraries {
            if !lib.name.is_empty() {
                let url = lib.url.as_ref().cloned().unwrap_or_default();
                libraries_threads.push(self.download_library(&lib.name, url));
            }
        }

        futures::future::try_join_all(libraries_threads).await?;

        Ok(())
    }

    async fn download_quiltmc_post(&self, version_name: &str) -> DynResult {
        // 将元数据与加载器的元数据进行合并
        tracing::trace!("合并元数据中");

        let vanilla_path = format!(
            "{}/{}/{}.json",
            self.minecraft_version_path.as_str(),
            version_name,
            version_name
        );
        let vanilla_meta = crate::prelude::inner_future::fs::read(&vanilla_path).await?;
        let loader_path = format!(
            "{}/{}/{}-quiltmc-loader.tmp.json",
            self.minecraft_version_path.as_str(),
            version_name,
            version_name
        );
        let loader_meta = crate::prelude::inner_future::fs::read(&loader_path).await?;
        inner_future::fs::remove_file(loader_path).await?;

        let mut vanilla_meta: VersionMeta =
            serde_json::from_slice(&vanilla_meta).context("无法解析原版版本元数据")?;
        let loader_meta: VersionMeta =
            serde_json::from_slice(&loader_meta).context("无法解析 QuiltMC 版本元数据")?;

        vanilla_meta += loader_meta;
        inner_future::fs::write(&vanilla_path, serde_json::to_vec(&vanilla_meta)?).await?;

        Ok(())
    }
}