From cf85f478f062b784a305208d5178faf98a95223d Mon Sep 17 00:00:00 2001 From: LynithDev <61880709+LynithDev@users.noreply.github.com> Date: Tue, 16 Jul 2024 11:47:34 +0200 Subject: [PATCH] fix: Loader metadata fetching + Fetch metadata when previous attemps in cache failed --- crates/core/src/api/cluster/create.rs | 2 +- crates/core/src/api/cluster/mod.rs | 2 +- crates/core/src/api/metadata.rs | 10 +- crates/core/src/game/mod.rs | 25 ++-- crates/core/src/store/metadata.rs | 177 ++++++++++++++++++-------- crates/core/src/store/mod.rs | 3 +- 6 files changed, 146 insertions(+), 73 deletions(-) diff --git a/crates/core/src/api/cluster/create.rs b/crates/core/src/api/cluster/create.rs index 718e4730..b761eb30 100644 --- a/crates/core/src/api/cluster/create.rs +++ b/crates/core/src/api/cluster/create.rs @@ -226,7 +226,7 @@ pub(crate) async fn get_loader_version( _ => return Err(CreateClusterError::MissingManifest(loader.to_string()).into()), }; - let loader_meta = loader_meta.to_owned(); + let loader_meta = loader_meta.to_owned().ok_or(anyhow::anyhow!("couldn't get game versions"))?; let loaders = &loader_meta .game_versions diff --git a/crates/core/src/api/cluster/mod.rs b/crates/core/src/api/cluster/mod.rs index 7644c49e..5d5b62e9 100644 --- a/crates/core/src/api/cluster/mod.rs +++ b/crates/core/src/api/cluster/mod.rs @@ -313,7 +313,7 @@ pub async fn get_optimal_java_version(path: &ClusterPath) -> crate::Result crate::Result { let state = State::get().await?; - let meta = state.metadata.read().await.minecraft.clone(); + let meta = state.metadata.read().await.minecraft.clone().ok_or(anyhow::anyhow!("couldn't get minecraft metadata"))?; Ok(meta) } @@ -17,7 +17,7 @@ pub async fn get_minecraft_versions() -> crate::Result { #[tracing::instrument] pub async fn get_fabric_versions() -> crate::Result { let state = State::get().await?; - let meta = state.metadata.read().await.fabric.clone(); + let meta = state.metadata.read().await.fabric.clone().ok_or(anyhow::anyhow!("couldn't get fabric metadata"))?; Ok(meta) } @@ -26,7 +26,7 @@ pub async fn get_fabric_versions() -> crate::Result { #[tracing::instrument] pub async fn get_quilt_versions() -> crate::Result { let state = State::get().await?; - let meta = state.metadata.read().await.quilt.clone(); + let meta = state.metadata.read().await.quilt.clone().ok_or(anyhow::anyhow!("couldn't get quilt metadata"))?; Ok(meta) } @@ -35,7 +35,7 @@ pub async fn get_quilt_versions() -> crate::Result { #[tracing::instrument] pub async fn get_forge_versions() -> crate::Result { let state = State::get().await?; - let meta = state.metadata.read().await.forge.clone(); + let meta = state.metadata.read().await.forge.clone().ok_or(anyhow::anyhow!("couldn't get forge metadata"))?; Ok(meta) } @@ -44,7 +44,7 @@ pub async fn get_forge_versions() -> crate::Result { #[tracing::instrument] pub async fn get_neoforge_versions() -> crate::Result { let state = State::get().await?; - let meta = state.metadata.read().await.neoforge.clone(); + let meta = state.metadata.read().await.neoforge.clone().ok_or(anyhow::anyhow!("couldn't get neoforge metadata"))?; Ok(meta) } diff --git a/crates/core/src/game/mod.rs b/crates/core/src/game/mod.rs index 1e1c51e0..8de032f8 100644 --- a/crates/core/src/game/mod.rs +++ b/crates/core/src/game/mod.rs @@ -72,9 +72,13 @@ pub async fn install_minecraft( let metadata = state.metadata.read().await; let version_manifest = metadata.minecraft.to_owned(); + let versions = version_manifest + .ok_or(anyhow::anyhow!( + "couldn't get minecraft manifest" + ))? + .versions; - let version_idx = version_manifest - .versions + let version_idx = versions .iter() .position(|g| g.id == cluster.meta.mc_version) .ok_or(anyhow::anyhow!( @@ -82,10 +86,9 @@ pub async fn install_minecraft( cluster.meta.mc_version ))?; - let version = &version_manifest.versions[version_idx]; + let version = &versions[version_idx]; let updated = version_idx - <= version_manifest - .versions + <= versions .iter() .position(|g| g.id == "22w16a") .unwrap_or(0); // LWJGL patching @@ -274,19 +277,21 @@ pub async fn launch_minecraft( let instance_path = &io::canonicalize(instance_path)?; let version_manifest = metadata.minecraft.to_owned(); + let versions = version_manifest + .ok_or(anyhow::anyhow!("couldn't get minecraft manifest"))? + .versions; - let version_index = version_manifest - .versions + let version_index = versions .iter() .position(|it| it.id == cluster.meta.mc_version) .ok_or(anyhow::anyhow!( "invalid game version {}", cluster.meta.mc_version ))?; - let version = &version_manifest.versions[version_index]; + + let version = &versions[version_index]; let updated = version_index - <= version_manifest - .versions + <= versions .iter() .position(|x| x.id == "22w16a") .unwrap_or(0); diff --git a/crates/core/src/store/metadata.rs b/crates/core/src/store/metadata.rs index fb20d234..29d6acc5 100644 --- a/crates/core/src/store/metadata.rs +++ b/crates/core/src/store/metadata.rs @@ -17,15 +17,15 @@ const METADATA_URL: &str = "https://meta.polyfrost.org"; #[derive(Serialize, Deserialize, Debug, Clone)] pub struct Metadata { /// The [`MinecraftManifest`] associated with core Minecraft versions. - pub minecraft: MinecraftManifest, + pub minecraft: Option, /// The [`ModdedManifest`] associated with core Fabric versions. - pub fabric: ModdedManifest, + pub fabric: Option, /// The [`ModdedManifest`] associated with core Quilt versions. - pub quilt: ModdedManifest, + pub quilt: Option, /// The [`ModdedManifest`] associated with core NeoForge versions. - pub neoforge: ModdedManifest, + pub neoforge: Option, /// The [`ModdedManifest`] associated with core Forge versions. - pub forge: ModdedManifest, + pub forge: Option, // /// The [`ModdedManifest`] associated with core Legacy Fabric versions. // pub legacy_fabric: Option, } @@ -33,34 +33,65 @@ pub struct Metadata { impl Metadata { /// Get the formatted manifest for a specific [`Metadata`] type. fn get_manifest(name: &str, version: usize) -> String { - format!("{METADATA_URL}/{name}/{version}/manifest.json") + format!("{METADATA_URL}/{name}/v{version}/manifest.json") } + /// Fetch all available metadata types that are currently [`None`] and get a new [`Metadata`] structure. + /// Useful for when reading from cache and a certain object not existing + pub async fn fetch_errored(&mut self, semaphore: &FetchSemaphore) -> crate::Result { + let mut did_change = false; + if self.minecraft.is_none() { + self.minecraft = Self::fetch_minecraft(semaphore).await; + + if self.minecraft.is_some() { + did_change = true; + } + } + + if self.fabric.is_none() { + self.fabric = Self::fetch_fabric(semaphore).await; + + if self.fabric.is_some() { + did_change = true; + } + } + + if self.quilt.is_none() { + self.quilt = Self::fetch_quilt(semaphore).await; + + if self.quilt.is_some() { + did_change = true; + } + } + + if self.neoforge.is_none() { + self.neoforge = Self::fetch_neoforge(semaphore).await; + + if self.neoforge.is_some() { + did_change = true; + } + } + + if self.forge.is_none() { + self.forge = Self::fetch_forge(semaphore).await; + + if self.forge.is_some() { + did_change = true; + } + } + + Ok(did_change) + } + /// Fetch all available metadata types and get a new [`Metadata`] structure. - pub async fn fetch() -> crate::Result { - let semaphore = &State::get().await?.fetch_semaphore; - let (minecraft, fabric, quilt, neoforge, forge) = tokio::try_join! { - async { - let url = Self::get_manifest("minecraft", interpulse::api::minecraft::CURRENT_FORMAT_VERSION); - fetch_version_manifest(Some(&url), semaphore).await - }, - async { - let url = Self::get_manifest("fabric", interpulse::api::modded::CURRENT_FABRIC_FORMAT_VERSION); - fetch_modded_manifest(&url, semaphore).await - }, - async { - let url = Self::get_manifest("quilt", interpulse::api::modded::CURRENT_QUILT_FORMAT_VERSION); - fetch_modded_manifest(&url, semaphore).await - }, - async { - let url = Self::get_manifest("neo", interpulse::api::modded::CURRENT_NEOFORGE_FORMAT_VERSION); - fetch_modded_manifest(&url, semaphore).await - }, - async { - let url = Self::get_manifest("forge", interpulse::api::modded::CURRENT_FORGE_FORMAT_VERSION); - fetch_modded_manifest(&url, semaphore).await - }, - }?; + pub async fn fetch(semaphore: &FetchSemaphore) -> crate::Result { + let (minecraft, fabric, quilt, neoforge, forge) = tokio::join! { + Self::fetch_minecraft(semaphore), + Self::fetch_fabric(semaphore), + Self::fetch_quilt(semaphore), + Self::fetch_neoforge(semaphore), + Self::fetch_forge(semaphore) + }; Ok(Self { minecraft, @@ -71,36 +102,53 @@ impl Metadata { }) } + async fn fetch_minecraft(semaphore: &FetchSemaphore) -> Option { + let url = Self::get_manifest("minecraft", interpulse::api::minecraft::CURRENT_FORMAT_VERSION); + fetch_version_manifest(Some(&url), semaphore).await.ok() + } + + async fn fetch_fabric(semaphore: &FetchSemaphore) -> Option { + let url = Self::get_manifest("fabric", interpulse::api::modded::CURRENT_FABRIC_FORMAT_VERSION); + fetch_modded_manifest(&url, semaphore).await.ok() + } + + async fn fetch_quilt(semaphore: &FetchSemaphore) -> Option { + let url = Self::get_manifest("quilt", interpulse::api::modded::CURRENT_QUILT_FORMAT_VERSION); + fetch_modded_manifest(&url, semaphore).await.ok() + } + + async fn fetch_neoforge(semaphore: &FetchSemaphore) -> Option { + let url = Self::get_manifest("neoforge", interpulse::api::modded::CURRENT_NEOFORGE_FORMAT_VERSION); + fetch_modded_manifest(&url, semaphore).await.ok() + } + + async fn fetch_forge(semaphore: &FetchSemaphore) -> Option { + let url = Self::get_manifest("forge", interpulse::api::modded::CURRENT_FORGE_FORMAT_VERSION); + fetch_modded_manifest(&url, semaphore).await.ok() + } + /// Initialize the core Metadata manager. - #[tracing::instrument(skip(io_semaphore))] + #[tracing::instrument(skip(io_semaphore, fetch_semaphore))] #[onelauncher_debug::debugger] pub async fn initialize( dirs: &Directories, online: bool, io_semaphore: &IoSemaphore, + fetch_semaphore: &FetchSemaphore, ) -> crate::Result { let mut metadata = None; let path = dirs.caches_dir().await.join("metadata.json"); let backup = dirs.caches_dir().await.join("metadata.json.bak"); + let mut should_write = false; + + if let Ok(mut metadata_json) = read_json::(&path, io_semaphore).await { + should_write = metadata_json.fetch_errored(fetch_semaphore).await?; - if let Ok(metadata_json) = read_json::(&path, io_semaphore).await { metadata = Some(metadata_json); } else if online { let res = async { - let fetch_data = Self::fetch().await?; - - write( - &path, - &serde_json::to_vec(&fetch_data).unwrap_or_default(), - io_semaphore, - ) - .await?; - write( - &backup, - &serde_json::to_vec(&fetch_data).unwrap_or_default(), - io_semaphore, - ) - .await?; + let fetch_data = Self::fetch(fetch_semaphore).await?; + should_write = true; metadata = Some(fetch_data); Ok::<(), crate::Error>(()) @@ -118,6 +166,19 @@ impl Metadata { copy(&backup, &path).await?; } + if should_write { + write( + &path, + &serde_json::to_vec(&metadata).unwrap_or_default(), + io_semaphore, + ).await?; + write( + &backup, + &serde_json::to_vec(&metadata).unwrap_or_default(), + io_semaphore, + ).await?; + } + if let Some(meta) = metadata { Ok(meta) } else { @@ -128,8 +189,8 @@ impl Metadata { /// Update and backup all available metadata. pub async fn update() { let res = async { - let fetch_data = Metadata::fetch().await?; - let state = State::get().await?; + let state = State::get().await?; + let fetch_data = Metadata::fetch(&state.fetch_semaphore).await?; let path = state.directories.caches_dir().await.join("metadata.json"); let backup = state @@ -169,13 +230,13 @@ async fn fetch_version_manifest( url: Option<&str>, semaphore: &FetchSemaphore, ) -> crate::Result { + let url = url.unwrap_or(interpulse::api::minecraft::VERSION_MANIFEST_URL); Ok(serde_json::from_slice( - &fetch( - url.unwrap_or(interpulse::api::minecraft::VERSION_MANIFEST_URL), - None, - semaphore, - ) - .await?, + &fetch(url, None, semaphore).await + .map_err(|err| { + tracing::error!("couldn't fetch version manifest at '{}'", url); + err + })?, )?) } @@ -183,5 +244,11 @@ async fn fetch_modded_manifest( url: &str, semaphore: &FetchSemaphore, ) -> crate::Result { - Ok(serde_json::from_slice(&fetch(url, None, semaphore).await?)?) + Ok(serde_json::from_slice( + &fetch(url, None, semaphore).await + .map_err(|err| { + tracing::error!("couldn't fetch modded manifest at '{}'", url); + err + })? + )?) } diff --git a/crates/core/src/store/mod.rs b/crates/core/src/store/mod.rs index 21936f3e..3992502e 100644 --- a/crates/core/src/store/mod.rs +++ b/crates/core/src/store/mod.rs @@ -131,7 +131,8 @@ impl State { let is_offline = !http::check_internet_connection(3).await; - let metadata_in = Metadata::initialize(&directories, !is_offline, &io_semaphore); + // TODO: Make this run in the background, this delays launcher startup by a couple seconds + let metadata_in = Metadata::initialize(&directories, !is_offline, &io_semaphore, &fetch_semaphore); let clusters_in = Clusters::initialize(&directories, &mut watcher); let users_in = MinecraftState::initialize(&directories, &io_semaphore); let (metadata, clusters, users) = crate::ingress_join! {