Skip to content

Commit 45ecac3

Browse files
committed
download mojang static archived versions.
Signed-off-by: Rachel Powers <[email protected]>
1 parent ddefb46 commit 45ecac3

18 files changed

+7685
-40
lines changed

libmcmeta/Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ edition = "2021"
99
chrono = { version = "0.4.24", features = ["serde"] }
1010
custom_error = "1.9.2"
1111
lazy_static = "1.4.0"
12+
merge = "0.1.0"
1213
serde = { version = "1.0.160", features = ["derive"] }
1314
serde_json = "1.0.95"
1415
serde_valid = "0.15.0"

libmcmeta/src/models/forge.rs

Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
use crate::models::merge::{self, Merge};
2+
13
use serde::{Deserialize, Serialize};
24
use serde_valid::Validate;
35
use serde_with::skip_serializing_none;
@@ -302,6 +304,71 @@ pub enum ForgeInstallerManifestVersion {
302304
Modern(Box<ForgeInstallerManifest>),
303305
}
304306

307+
#[derive(Deserialize, Serialize, Clone, Debug, Validate, Merge)]
308+
pub struct ForgeFile {
309+
#[merge(strategy = merge::overwrite)]
310+
pub classifier: String,
311+
#[merge(strategy = merge::overwrite)]
312+
pub hash: String,
313+
#[merge(strategy = merge::overwrite)]
314+
pub extension: String,
315+
}
316+
317+
impl ForgeFile {
318+
fn filename(&self, long_version: &str) -> String {
319+
format!(
320+
"{}-{}-{}.{}",
321+
"forge", long_version, self.classifier, self.extension
322+
)
323+
}
324+
325+
fn url(&self, long_version: &str) -> String {
326+
format!(
327+
"https://maven.minecraftforge.net/net/minecraftforge/forge/{}/{}",
328+
long_version,
329+
self.filename(long_version),
330+
)
331+
}
332+
}
333+
334+
#[skip_serializing_none]
335+
#[derive(Deserialize, Serialize, Clone, Debug, Validate, Merge)]
336+
pub struct ForgeEntry {
337+
#[serde(rename = "longversion")]
338+
#[merge(strategy = merge::overwrite)]
339+
pub long_version: String,
340+
#[serde(rename = "mcversion")]
341+
#[merge(strategy = merge::overwrite)]
342+
pub mc_version: String,
343+
#[merge(strategy = merge::overwrite)]
344+
pub version: String,
345+
#[merge(strategy = merge::overwrite)]
346+
pub build: i64,
347+
pub branch: Option<String>,
348+
pub latest: Option<bool>,
349+
pub recommended: Option<bool>,
350+
#[merge(strategy = merge::hashmap::recurse_some)]
351+
pub files: Option<HashMap<String, ForgeFile>>,
352+
}
353+
354+
#[skip_serializing_none]
355+
#[derive(Deserialize, Serialize, Clone, Debug, Validate, Merge)]
356+
pub struct ForgeMCVersionInfo {
357+
pub latest: Option<String>,
358+
pub recommended: Option<String>,
359+
#[merge(strategy = merge::vec::append)]
360+
pub versions: Vec<String>,
361+
}
362+
363+
#[derive(Deserialize, Serialize, Clone, Debug, Validate, Merge)]
364+
pub struct DerivedForgeIndex {
365+
#[merge(strategy = merge::hashmap::recurse)]
366+
pub versions: HashMap<String, ForgeEntry>,
367+
#[serde(rename = "by_mcversion")]
368+
#[merge(strategy = merge::hashmap::recurse)]
369+
pub by_mc_version: HashMap<String, ForgeMCVersionInfo>,
370+
}
371+
305372
#[cfg(test)]
306373
mod tests {
307374
#[test]

libmcmeta/src/models/mod.rs

Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -184,3 +184,81 @@ impl<'de> Deserialize<'de> for GradleSpecifier {
184184
s.parse().map_err(serde::de::Error::custom)
185185
}
186186
}
187+
188+
pub mod validation {
189+
pub fn is_some<T>(obj: Option<T>) -> Result<(), serde_valid::validation::Error> {
190+
if !obj.is_some() {
191+
return Err(serde_valid::validation::Error::Custom(
192+
"Must be some".to_string(),
193+
));
194+
}
195+
Ok(())
196+
}
197+
}
198+
199+
pub mod merge {
200+
pub use merge::Merge;
201+
pub use merge::{bool, num, ord, vec};
202+
203+
/// generic overwrite stratagy
204+
pub fn overwrite<T>(left: &mut T, right: T) {
205+
*left = right
206+
}
207+
208+
/// Merge strategies for `Option`
209+
pub mod option {
210+
/// Overwrite `left` with `right` only if `left` is `None`.
211+
pub fn overwrite_none<T>(left: &mut Option<T>, right: Option<T>) {
212+
if left.is_none() {
213+
*left = right;
214+
}
215+
}
216+
217+
/// If both `left` and `right` are `Some`, recursively merge the two.
218+
/// Otherwise, fall back to `overwrite_none`.
219+
pub fn recurse<T: merge::Merge>(left: &mut Option<T>, right: Option<T>) {
220+
if let Some(new) = right {
221+
if let Some(original) = left {
222+
original.merge(new);
223+
} else {
224+
*left = Some(new);
225+
}
226+
}
227+
}
228+
}
229+
230+
/// Merge strategies for `HashMap`
231+
pub mod hashmap {
232+
use std::collections::HashMap;
233+
use std::hash::Hash;
234+
235+
pub fn recurse<K: Eq + Hash, V: merge::Merge>(
236+
left: &mut HashMap<K, V>,
237+
right: HashMap<K, V>,
238+
) {
239+
use std::collections::hash_map::Entry;
240+
241+
for (k, v) in right {
242+
match left.entry(k) {
243+
Entry::Occupied(mut existing) => existing.get_mut().merge(v),
244+
Entry::Vacant(empty) => {
245+
empty.insert(v);
246+
}
247+
}
248+
}
249+
}
250+
251+
pub fn recurse_some<K: Eq + Hash, V: merge::Merge>(
252+
left: &mut Option<HashMap<K, V>>,
253+
right: Option<HashMap<K, V>>,
254+
) {
255+
if let Some(new) = right {
256+
if let Some(original) = left {
257+
recurse(original, new);
258+
} else {
259+
*left = Some(new);
260+
}
261+
}
262+
}
263+
}
264+
}

libmcmeta/src/models/mojang.rs

Lines changed: 28 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -268,7 +268,7 @@ pub struct MinecraftVersion {
268268
pub asset_index: AssetIndex,
269269
pub assets: String,
270270
pub compliance_level: Option<i32>,
271-
pub downloads: VersionDownloads,
271+
pub downloads: Option<VersionDownloads>,
272272
pub id: String,
273273
pub java_version: Option<JavaVersion>,
274274
#[validate]
@@ -285,6 +285,33 @@ pub struct MinecraftVersion {
285285
pub release_type: String,
286286
}
287287

288+
#[derive(Deserialize, Serialize, Debug, Clone, Validate)]
289+
pub struct ExperimentEntry {
290+
pub id: String,
291+
pub url: String,
292+
pub wiki: Option<String>,
293+
}
294+
295+
#[derive(Deserialize, Serialize, Debug, Clone, Validate)]
296+
pub struct ExperimentIndex {
297+
pub experiments: Vec<ExperimentEntry>,
298+
}
299+
300+
#[derive(Deserialize, Serialize, Debug, Clone, Validate)]
301+
pub struct OldSnapshotEntry {
302+
pub id: String,
303+
pub url: String,
304+
pub wiki: Option<String>,
305+
pub jar: String,
306+
pub sha1: String,
307+
pub size: i32,
308+
}
309+
310+
#[derive(Deserialize, Serialize, Debug, Clone, Validate)]
311+
pub struct OldSnapshotIndex {
312+
pub old_snapshots: Vec<OldSnapshotEntry>,
313+
}
314+
288315
#[cfg(test)]
289316
mod tests {
290317

mcmeta/.env.example

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,23 @@
11
RUST_LOG=INFO
22

3+
# all the below varabile are set be default and do not all need to be provided
4+
# you only need to set the ones you want to change
5+
36
MCMETA__BIND_ADDRESS=127.0.0.1:9988
47

58
MCMETA__STORAGE_FORMAT__TYPE=json
6-
MCMETA__STORAGE_FORMAT__META_DIRECTORY=../meta
9+
MCMETA__STORAGE_FORMAT__META_DIRECTORY=./meta
10+
MCMETA__STORAGE_FORMAT__GENERATED_DIRECTORY=./generated
711

812
MCMETA__METADATA__MAX_PARALLEL_FETCH_CONNECTIONS=8
13+
MCMETA__METADATA__STATIC_DIRECTORY=./static
914

1015
MCMETA__DEBUG_LOG__ENABLE=true
1116
MCMETA__DEBUG_LOG__PATH=./logs
1217
MCMETA__DEBUG_LOG__PREFIX=mcmeta.log
13-
MCMETA__DEBUG_LOG__LEVEL=DEBUG
18+
MCMETA__DEBUG_LOG__LEVEL=DEBUG
19+
20+
MCMETA_MOJANG__MANIFEST_URL=https://piston-meta.mojang.com/mc/game/version_manifest_v2.json
21+
22+
MCMETA_FORGE__MAVEN_URL=https://files.minecraftforge.net/net/minecraftforge/forge/maven-metadata.json
23+
MCMETA_FORGE__PROMOTIONS_URL=https://files.minecraftforge.net/net/minecraftforge/forge/promotions_slim.json

mcmeta/Cargo.toml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,8 +21,10 @@ serde = { version = "1.0.160", features = ["derive"] }
2121
serde_json = "1.0.95"
2222
serde_valid = "0.15.0"
2323
serde_with = "2.3.2"
24+
tempdir = "0.3.7"
2425
thiserror = "1.0.40"
2526
tokio = { version = "1.27.0", features = ["full"] }
2627
tracing = "0.1.37"
2728
tracing-appender = "0.2.2"
2829
tracing-subscriber = { version = "0.3.16", features = ["env-filter"] }
30+
zip = "0.6.4"

mcmeta/src/app_config.rs

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,13 +4,17 @@ use serde::Deserialize;
44
#[derive(Deserialize, Debug)]
55
#[serde(rename_all = "snake_case", tag = "type")]
66
pub enum StorageFormat {
7-
Json { meta_directory: String },
7+
Json {
8+
meta_directory: String,
9+
generated_directory: String,
10+
},
811
Database,
912
}
1013

1114
#[derive(Deserialize, Debug)]
1215
pub struct MetadataConfig {
1316
pub max_parallel_fetch_connections: usize,
17+
pub static_directory: String,
1418
}
1519

1620
#[derive(Deserialize, Debug)]
@@ -35,7 +39,9 @@ impl ServerConfig {
3539
.set_default("bind_address", "127.0.0.1:8080")?
3640
.set_default("storage_format.type", "json")?
3741
.set_default("storage_format.meta_directory", "meta")?
42+
.set_default("storage_format.generated_directory", "generated")?
3843
.set_default("metadata.max_parallel_fetch_connections", 4)?
44+
.set_default("metadata.static_directory", "static")?
3945
.set_default("debug_log.enable", false)?
4046
.set_default("debug_log.path", "./logs")?
4147
.set_default("debug_log.prefix", "mcmeta.log")?

mcmeta/src/download/mojang.rs

Lines changed: 52 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,10 @@
11
use libmcmeta::models::mojang::{MinecraftVersion, MojangVersionManifest};
22
use serde::Deserialize;
33
use serde_valid::Validate;
4+
use tempdir::TempDir;
45
use tracing::debug;
56

6-
use anyhow::Result;
7+
use anyhow::{anyhow, Result};
78

89
use crate::download::errors::MetadataError;
910

@@ -62,3 +63,53 @@ pub async fn load_version_manifest(version_url: &str) -> Result<MinecraftVersion
6263
manifest.validate()?;
6364
Ok(manifest)
6465
}
66+
67+
pub async fn load_zipped_version(version_url: &str) -> Result<MinecraftVersion> {
68+
use std::io::prelude::*;
69+
70+
let client = reqwest::Client::new();
71+
72+
debug!("Fetching zipped version from {:#?}", version_url);
73+
74+
let file_response = client.get(version_url).send().await?.error_for_status()?;
75+
76+
let tmp_dir = TempDir::new("mcmeta_mojang_zip")?;
77+
let dest_path = {
78+
let fname = file_response
79+
.url()
80+
.path_segments()
81+
.and_then(|segments| segments.last())
82+
.and_then(|name| if name.is_empty() { None } else { Some(name) })
83+
.unwrap_or("tmp.zip");
84+
85+
tmp_dir.path().join(fname)
86+
};
87+
88+
{
89+
// write to file, context drop to flush and close
90+
let mut file = std::fs::File::create(&dest_path)?;
91+
let mut content = std::io::Cursor::new(file_response.bytes().await?);
92+
std::io::copy(&mut content, &mut file)?;
93+
}
94+
95+
let file = std::fs::File::open(&dest_path)?;
96+
97+
let mut archive = zip::ZipArchive::new(file)?;
98+
99+
let mut manifest: Option<MinecraftVersion> = None;
100+
for i in 0..archive.len() {
101+
let mut zfile = archive.by_index(i)?;
102+
if zfile.name().ends_with(".json") {
103+
debug!("Found {} as version json", zfile.name());
104+
let mut contents = String::new();
105+
zfile.read_to_string(&mut contents).unwrap();
106+
107+
manifest = Some(
108+
serde_json::from_str(&contents)
109+
.map_err(|err| MetadataError::from_json_err(err, &contents))?,
110+
);
111+
}
112+
}
113+
114+
Ok(manifest.ok_or(anyhow!("Unable to find version manifest"))?)
115+
}

0 commit comments

Comments
 (0)