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
use crate::consts;
use crate::error::{CobbleError, CobbleResult};
use crate::minecraft::models::extract::Extract;
use crate::minecraft::models::library_downloads::LibraryDownloads;
use crate::minecraft::models::natives::Natives;
use crate::minecraft::models::rule::Rule;
use crate::utils::os::Architecture;
use serde::{Deserialize, Serialize};
use std::collections::VecDeque;
use std::path::{Path, PathBuf};

/// A library need for running the game.
#[derive(Clone, Debug, Deserialize, Serialize)]
pub struct Library {
    /// Information about downloading the library.
    pub downloads: LibraryDownloads,
    /// Name of the library.
    pub name: String,
    /// Available natives.
    pub natives: Option<Natives>,
    /// Rules for this library.
    #[serde(default)]
    pub rules: Vec<Rule>,
    /// Extract options.
    pub extract: Option<Extract>,
}

impl Library {
    /// Checks if the library needs to be used on the current executing machine.
    pub fn check_use(&self) -> bool {
        for rule in &self.rules {
            if !rule.allows() {
                return false;
            }
        }

        true
    }

    /// Builds the path where the library jar file is placed.
    /// This path does not include the jar file itself.
    pub fn library_path(&self, libraries_path: impl AsRef<Path>) -> CobbleResult<PathBuf> {
        let mut split = self.split_name();
        let mut package = split.pop_front().ok_or(CobbleError::LibraryNameFormat)?;
        let name = split.pop_front().ok_or(CobbleError::LibraryNameFormat)?;
        let version = split.pop_front().ok_or(CobbleError::LibraryNameFormat)?;

        package = package.replace('.', "/");

        let mut library_path = PathBuf::from(libraries_path.as_ref());
        library_path.push(&package);
        library_path.push(&name);
        library_path.push(&version);

        Ok(library_path)
    }

    /// Build the path for the library jar file.
    /// Supports building the file name for natives.
    /// This path does include the jar file itself.
    pub fn jar_path(
        &self,
        libraries_path: impl AsRef<Path>,
        native: Option<&str>,
    ) -> CobbleResult<PathBuf> {
        let mut split = self.split_name();
        let mut package = split.pop_front().ok_or(CobbleError::LibraryNameFormat)?;
        let name = split.pop_front().ok_or(CobbleError::LibraryNameFormat)?;
        let version = split.pop_front().ok_or(CobbleError::LibraryNameFormat)?;
        let suffix = split.pop_front();

        package = package.replace('.', "/");

        let mut library_path = PathBuf::from(libraries_path.as_ref());
        library_path.push(&package);
        library_path.push(&name);
        library_path.push(&version);

        let jar_name = Self::jar_name(&name, &version, native, suffix.as_deref());

        library_path.push(jar_name);

        Ok(library_path)
    }

    /// Gets the download URL for this library.
    /// Supports download URL for native file by providing the native identifier.
    /// Returns the URL, SHA1 and file size.
    pub fn download_url(
        &self,
        native: Option<&str>,
    ) -> CobbleResult<(String, Option<String>, Option<usize>)> {
        let url: String;
        let mut sha1 = None;
        let mut size = None;

        match native {
            Some(native) => {
                match self.downloads.classifiers.get(native) {
                    // Take data from file
                    Some(file) => {
                        url = file.url.clone();
                        sha1 = Some(file.sha1.clone());
                        size = Some(file.size);
                    }
                    // Build data from name
                    None => {
                        url = self.build_url_from_name(Some(native))?;
                    }
                }
            }
            None => {
                match &self.downloads.artifact {
                    // Take data from file
                    Some(file) => {
                        url = file.url.clone();
                        sha1 = Some(file.sha1.clone());
                        size = Some(file.size);
                    }
                    // Build data from name
                    None => {
                        url = self.build_url_from_name(None)?;
                    }
                }
            }
        }

        Ok((url, sha1, size))
    }

    fn build_url_from_name(&self, native: Option<&str>) -> CobbleResult<String> {
        let mut split = self.split_name();
        let mut package = split.pop_front().ok_or(CobbleError::LibraryNameFormat)?;
        let name = split.pop_front().ok_or(CobbleError::LibraryNameFormat)?;
        let version = split.pop_front().ok_or(CobbleError::LibraryNameFormat)?;

        package = package.replace('.', "/");

        let url = format!(
            "{}/{}/{}/{}/{}",
            consts::MC_LIBRARIES_BASE_URL,
            &package,
            &name,
            &version,
            &Self::jar_name(&name, &version, native, None),
        );

        warn!("{}", &url);

        Ok(url)
    }

    /// Build the name for the library jar file.
    fn jar_name(name: &str, version: &str, native: Option<&str>, suffix: Option<&str>) -> String {
        match (suffix, native) {
            (Some(suffix), Some(native)) => {
                format!("{}-{}-{}-{}.jar", name, version, native, suffix)
            }
            (Some(suffix), None) => format!("{}-{}-{}.jar", name, version, suffix),
            (None, Some(native)) => format!("{}-{}-{}.jar", name, version, native),
            (None, None) => format!("{}-{}.jar", name, version),
        }
    }

    /// Splits the library name at its delimitor (`:`).
    fn split_name(&self) -> VecDeque<String> {
        self.name
            .split(':')
            .map(|x| x.to_string())
            .collect::<VecDeque<_>>()
    }

    /// Gets the native library if applicable.
    pub fn get_native(&self) -> Option<String> {
        let arch = Architecture::current();

        if let Some(natives) = &self.natives {
            return natives
                .get_for_current_platform()
                .map(|n| n.replace("${arch}", &arch.get_bits().to_string()));
        }

        None
    }
}