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
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.
    pub fn download_url(&self, native: Option<&str>) -> CobbleResult<String> {
        let url = match native {
            Some(native) => {
                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('.', "/");

                format!(
                    "{}/{}/{}/{}/{}",
                    consts::MC_LIBRARIES_BASE_URL,
                    &package,
                    &name,
                    &version,
                    &Self::jar_name(&name, &version, Some(native), None),
                )
            }
            None => self.downloads.artifact.url.clone(),
        };

        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
    }
}