use anyhow::Context;
use mcvm_shared::output::{MCVMOutput, MessageContents, MessageLevel};
use mcvm_shared::{translate, util::DefaultExt, UpdateDepth};
use reqwest::Client;
use serde::{Deserialize, Serialize};
use crate::io::files::{self, paths::Paths};
use crate::io::update::UpdateManager;
use crate::io::{json_from_file, json_to_file};
use crate::net::download::ProgressiveDownload;
use crate::util::versions::VersionName;
#[derive(Deserialize, Serialize, Debug, Clone)]
pub struct VersionManifest {
pub latest: LatestVersions,
pub versions: Vec<VersionEntry>,
}
#[derive(Deserialize, Serialize, Debug, Clone)]
pub struct VersionEntry {
pub id: String,
#[serde(rename = "type")]
#[serde(default)]
pub ty: VersionType,
pub url: String,
#[serde(default)]
#[serde(skip_serializing_if = "DefaultExt::is_default")]
pub is_zipped: bool,
#[serde(default)]
#[serde(skip_serializing_if = "DefaultExt::is_default")]
pub source: Option<String>,
}
#[derive(Deserialize, Serialize, Debug, Clone, Copy, Default)]
#[serde(rename_all = "snake_case")]
pub enum VersionType {
#[default]
Release,
Snapshot,
OldAlpha,
OldBeta,
}
#[derive(Deserialize, Serialize, Debug, Clone)]
pub struct LatestVersions {
pub release: VersionName,
pub snapshot: VersionName,
}
pub async fn get(
paths: &Paths,
manager: &UpdateManager,
client: &Client,
o: &mut impl MCVMOutput,
) -> anyhow::Result<VersionManifest> {
let manifest = get_contents(paths, manager, client, false, o).await;
let manifest = match manifest {
Ok(manifest) => manifest,
Err(err) => {
o.display(
MessageContents::Error("Failed to obtain version manifest".into()),
MessageLevel::Important,
);
o.display(
MessageContents::Error(format!("{}", err)),
MessageLevel::Important,
);
o.display(
MessageContents::StartProcess("Redownloading".into()),
MessageLevel::Important,
);
get_contents(paths, manager, client, true, o)
.await
.context("Failed to download manifest contents")?
}
};
Ok(manifest)
}
pub async fn get_with_output(
paths: &Paths,
manager: &UpdateManager,
client: &Client,
o: &mut impl MCVMOutput,
) -> anyhow::Result<VersionManifest> {
o.start_process();
o.display(
MessageContents::StartProcess("Obtaining version manifest".into()),
MessageLevel::Important,
);
let manifest = get(paths, manager, client, o)
.await
.context("Failed to get version manifest")?;
o.display(
MessageContents::Success("Version manifest obtained".into()),
MessageLevel::Important,
);
o.end_process();
Ok(manifest)
}
async fn get_contents(
paths: &Paths,
manager: &UpdateManager,
client: &Client,
force: bool,
o: &mut impl MCVMOutput,
) -> anyhow::Result<VersionManifest> {
let mut path = paths.internal.join("versions");
files::create_dir(&path)?;
path.push("manifest.json");
if manager.update_depth < UpdateDepth::Full && !force && path.exists() {
return json_from_file(path).context("Failed to read manifest contents from file");
}
let mut download = ProgressiveDownload::bytes(
"https://piston-meta.mojang.com/mc/game/version_manifest_v2.json",
client,
)
.await?;
while !download.is_finished() {
download.poll_download().await?;
o.display(
MessageContents::Associated(
Box::new(download.get_progress()),
Box::new(MessageContents::Simple(translate!(
o,
StartDownloadingVersionManifest
))),
),
MessageLevel::Important,
);
}
let manifest = download.finish_json()?;
json_to_file(path, &manifest).context("Failed to write manifest to a file")?;
Ok(manifest)
}
pub fn make_version_list(version_manifest: &VersionManifest) -> Vec<String> {
let mut out = Vec::new();
for entry in &version_manifest.versions {
out.push(entry.id.clone());
}
out.reverse();
out
}
pub struct VersionManifestAndList {
pub manifest: VersionManifest,
pub list: Vec<String>,
}
impl VersionManifestAndList {
pub fn new(manifest: VersionManifest) -> Self {
let list = make_version_list(&manifest);
Self { manifest, list }
}
pub fn set(&mut self, manifest: VersionManifest) -> anyhow::Result<()> {
self.list = make_version_list(&manifest);
self.manifest = manifest;
Ok(())
}
}