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
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
//! 游戏资源下载模块,所有的游戏/模组/模组中文名称等数据的获取和安装都在这里

pub mod authlib;
pub mod curseforge;
pub mod fabric;
pub mod forge;
pub mod mcmod;
pub mod modrinth;
pub mod optifine;
pub mod quiltmc;
pub mod structs;
pub mod vanilla;

use std::{fmt::Display, path::Path, str::FromStr};

use anyhow::Context;
use async_trait::async_trait;
pub use authlib::AuthlibDownloadExt;
pub use fabric::FabricDownloadExt;
pub use forge::ForgeDownloadExt;
pub use optifine::OptifineDownloadExt;
pub use quiltmc::QuiltMCDownloadExt;
use serde::{Deserialize, Serialize};
pub use vanilla::VanillaDownloadExt;

use self::structs::VersionInfo;
use crate::{path::*, prelude::*, progress::*};

/// 游戏的下载来源,支持和 BMCLAPI 同格式的自定义镜像源
///
/// 通常国内的镜像源速度是比官方快的,但是更新不如官方的及时
#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, Eq)]
pub enum DownloadSource {
    /// 全部使用原始来源下载
    Default,
    /// 全部使用 BMCLAPI 提供的镜像源下载
    ///
    /// 为了支持镜像源,在这里鼓励大家前去支持一下:<https://afdian.net/a/bangbang93>
    BMCLAPI,
    /// 全部使用 MCBBS 提供的镜像源下载
    MCBBS,
    /// 使用符合 BMCLAPI 镜像链接格式的自定义镜像源下载
    Custom(url::Url),
}

impl Default for DownloadSource {
    fn default() -> Self {
        Self::Default
    }
}

impl Display for DownloadSource {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        write!(
            f,
            "{}",
            match self {
                DownloadSource::Default => "默认(官方)下载源",
                DownloadSource::BMCLAPI => "BMCLAPI 下载源",
                DownloadSource::MCBBS => "MCBBS 下载源",
                DownloadSource::Custom(_) => "自定义",
            }
        )
    }
}

impl FromStr for DownloadSource {
    type Err = ();

    fn from_str(s: &str) -> Result<Self, Self::Err> {
        match s {
            "Offical" => Ok(Self::Default),
            "BMCLAPI" => Ok(Self::BMCLAPI),
            "MCBBS" => Ok(Self::MCBBS),
            s => {
                let url = s.parse::<url::Url>();
                if let Ok(url) = url {
                    Ok(Self::Custom(url))
                } else {
                    Ok(Self::Default)
                }
            }
        }
    }
}

/// 下载结构,用于存储下载所需的信息,并通过附带的扩展特质下载需要的东西
#[derive(Debug)]
pub struct Downloader<R> {
    /// 使用的下载源
    pub source: DownloadSource,
    /// 当前的 Minecraft 游戏目录路径
    pub(crate) minecraft_path: String,
    /// 当前的 Minecraft 依赖库目录路径
    pub(crate) minecraft_library_path: String,
    /// 当前的 Minecraft 版本文件夹目录路径
    pub(crate) minecraft_version_path: String,
    /// 当前的 Minecraft 资源文件夹目录路径
    pub(crate) minecraft_assets_path: String,
    /// 是否使用版本独立方式安装
    ///
    /// 这个会影响 Optifine 以模组形式的安装路径
    ///
    /// (会被安装在 版本/mods 文件夹里还是 .minecraft/mods 文件夹里)
    pub game_independent: bool,
    /// 是否验证已存在的文件是否正确
    pub verify_data: bool,
    /// 任意的 Java 运行时执行文件目录
    ///
    /// 在安装 Forge 时会使用
    pub java_path: String,
    /// 下载并发量
    parallel_amount: usize,
    /// 下载并发锁
    pub(crate) parallel_lock: inner_future::lock::Semaphore,
    /// 下载的进度报告对象
    pub reporter: Option<R>,
}

// let l = self.parallel_amount.acquire().await;

impl<R> Downloader<R> {
    /// 设置安装的目录,传入一个 `.minecraft` 文件夹路径作为参数
    ///
    /// 游戏将会被安装到此处
    pub fn set_minecraft_path(&mut self, dot_minecraft_path: impl AsRef<Path>) {
        let dot_minecraft_path = dot_minecraft_path.as_ref().to_path_buf();
        self.minecraft_path = dot_minecraft_path.to_string_lossy().to_string();
        self.minecraft_library_path = dot_minecraft_path
            .join("libraries")
            .to_string_lossy()
            .to_string();
        self.minecraft_version_path = dot_minecraft_path
            .join("versions")
            .to_string_lossy()
            .to_string();
        self.minecraft_assets_path = dot_minecraft_path
            .join("assets")
            .to_string_lossy()
            .to_string();
    }

    /// Builder 模式的 [`Downloader::set_minecraft_path`]
    pub fn with_minecraft_path(mut self, dot_minecraft_path: impl AsRef<Path>) -> Self {
        self.set_minecraft_path(dot_minecraft_path);
        self
    }
}

impl<R: Reporter> Clone for Downloader<R> {
    fn clone(&self) -> Self {
        Self {
            source: self.source.clone(),
            minecraft_path: self.minecraft_path.clone(),
            minecraft_library_path: self.minecraft_library_path.clone(),
            minecraft_version_path: self.minecraft_version_path.clone(),
            minecraft_assets_path: self.minecraft_assets_path.clone(),
            game_independent: self.game_independent,
            verify_data: self.verify_data,
            java_path: self.java_path.clone(),
            parallel_lock: if self.parallel_amount == 0 {
                inner_future::lock::Semaphore::new(usize::MAX)
            } else {
                inner_future::lock::Semaphore::new(self.parallel_amount)
            },
            reporter: self.reporter.clone(),
            parallel_amount: self.parallel_amount,
        }
    }
}

impl<R: Reporter> Downloader<R> {
    /// 设置一个进度报告对象,下载进度将会被上报给这个对象
    #[must_use]
    pub fn with_reporter(mut self, reporter: R) -> Self {
        self.reporter = Some(reporter);
        self
    }
    /// 设置一个下载源
    #[must_use]
    pub fn with_source(mut self, source: DownloadSource) -> Self {
        self.source = source;
        self
    }
    /// 设置一个 Java 运行时,安装 Forge 和 Optifine 时需要用到
    #[must_use]
    pub fn with_java(mut self, java_path: String) -> Self {
        self.java_path = java_path;
        self
    }
    /// 设置是否使用版本独立方式安装
    ///
    /// 这个会影响 Optifine 以模组形式的安装路径
    ///
    /// (会被安装在 版本/mods 文件夹里还是 .minecraft/mods 文件夹里)
    #[must_use]
    pub fn with_game_independent(mut self, game_independent: bool) -> Self {
        self.game_independent = game_independent;
        self
    }
    /// 设置下载时的并发量,如果为 0 则不限制
    #[must_use]
    pub fn with_parallel_amount(mut self, limit: usize) -> Self {
        self.parallel_amount = limit;
        if limit == 0 {
            self.parallel_lock = inner_future::lock::Semaphore::new(usize::MAX);
        } else {
            self.parallel_lock = inner_future::lock::Semaphore::new(limit);
        }
        self
    }
    /// 是否强制校验已下载的文件以确认是否需要重新下载
    ///
    /// 如不强制则仅检测文件是否存在
    #[must_use]
    pub fn with_verify_data(mut self) -> Self {
        self.verify_data = true;
        self
    }
}
impl<R: Reporter> Default for Downloader<R> {
    fn default() -> Self {
        Self {
            source: DownloadSource::Default,
            minecraft_path: MINECRAFT_PATH.to_owned(),
            minecraft_library_path: MINECRAFT_LIBRARIES_PATH.to_owned(),
            minecraft_version_path: MINECRAFT_VERSIONS_PATH.to_owned(),
            minecraft_assets_path: MINECRAFT_ASSETS_PATH.to_owned(),
            game_independent: false,
            verify_data: false,
            java_path: {
                #[cfg(windows)]
                {
                    "javaw.exe".into()
                }
                #[cfg(not(windows))]
                {
                    "java".into()
                }
            },
            reporter: None,
            parallel_amount: 64,
            parallel_lock: inner_future::lock::Semaphore::new(64),
        }
    }
}

/// 一个游戏安装特质,如果你并不需要单独安装其它部件,则可以单独引入这个特质来安装游戏
#[async_trait]
pub trait GameDownload<'a>:
    FabricDownloadExt + ForgeDownloadExt + VanillaDownloadExt + QuiltMCDownloadExt
{
    /// 根据参数安装一个游戏,允许安装模组加载器
    async fn download_game(
        &self,
        version_name: &str,
        vanilla: VersionInfo,
        fabric: &str,
        quiltmc: &str,
        forge: &str,
        optifine: &str,
    ) -> DynResult;
}

#[async_trait]
impl<R: Reporter> GameDownload<'_> for Downloader<R> {
    async fn download_game(
        &self,
        version_name: &str,
        vanilla: VersionInfo,
        fabric: &str,
        quiltmc: &str,
        forge: &str,
        optifine: &str,
    ) -> DynResult {
        self.reporter
            .set_message(format!("正在下载游戏 {version_name}"));

        let launcher_profiles_path =
            std::path::Path::new(&self.minecraft_path).join("launcher_profiles.json");

        if !launcher_profiles_path.exists() {
            inner_future::fs::create_dir_all(launcher_profiles_path.parent().unwrap()).await?;
            inner_future::fs::write(launcher_profiles_path, r#"{"profiles":{},"selectedProfile":null,"authenticationDatabase":{},"selectedUser":{"account":"00000111112222233333444445555566","profile":"66666555554444433333222221111100"}}"#).await?;
        }

        if !fabric.is_empty() {
            crate::prelude::inner_future::future::try_zip(
                self.install_vanilla(version_name, &vanilla),
                self.download_fabric_pre(version_name, &vanilla.id, fabric),
            )
            .await?;
            self.download_fabric_post(version_name).await?;
        } else if !quiltmc.is_empty() {
            crate::prelude::inner_future::future::try_zip(
                self.install_vanilla(version_name, &vanilla),
                self.download_quiltmc_pre(version_name, &vanilla.id, quiltmc),
            )
            .await?;
            self.download_quiltmc_post(version_name).await?;
        } else if !forge.is_empty() {
            self.install_vanilla(&vanilla.id, &vanilla).await?; // Forge 安装需要原版,如果安装器没有解析到则会从官方源下载,速度很慢
            crate::prelude::inner_future::future::try_zip(
                self.install_vanilla(version_name, &vanilla),
                self.install_forge_pre(version_name, &vanilla.id, forge),
            )
            .await?;
            self.install_forge_post(version_name, &vanilla.id, forge)
                .await?;
        } else {
            self.install_vanilla(version_name, &vanilla).await?;
        }
        if !optifine.is_empty() {
            if forge.is_empty() && fabric.is_empty() {
                self.install_vanilla(&vanilla.id, &vanilla).await?; // Optifine 安装需要原版,如果安装器没有解析到则会从官方源下载,速度很慢
            }
            let (optifine_type, optifine_patch) =
                optifine.split_at(optifine.find(' ').context("Optifine 版本字符串不合法!")?);
            self.install_optifine(
                version_name,
                &vanilla.id,
                optifine_type,
                &optifine_patch[1..],
                !forge.is_empty() || !fabric.is_empty(),
            )
            .await?;
        }

        // 这俩都需要安装器,而安装后会生成一个新的版本元数据
        // 因此需要最后扫描一遍生成出来的版本元数据依赖,再进行一次下载
        if !optifine.is_empty() || !forge.is_empty() {
            let mut version_info = crate::version::structs::VersionInfo {
                version_base: self.minecraft_version_path.to_owned(),
                version: version_name.to_owned(),
                ..Default::default()
            };

            if version_info
                .load()
                .await
                .context("无法读取安装完成后的版本元数据!")
                .is_ok()
            {
                if let Some(meta) = &mut version_info.meta {
                    meta.fix_libraries();
                    self.download_libraries(&meta.libraries).await?;
                }
            }
        }
        Ok(())
    }
}