diff --git a/packages/app-lib/src/api/profile/mod.rs b/packages/app-lib/src/api/profile/mod.rs index 0384d3c652..313fe59ae8 100644 --- a/packages/app-lib/src/api/profile/mod.rs +++ b/packages/app-lib/src/api/profile/mod.rs @@ -294,19 +294,13 @@ pub async fn get_optimal_jre_key( let state = State::get().await?; if let Some(profile) = get(path).await? { - let minecraft = crate::api::metadata::get_minecraft_versions().await?; - - // Fetch version info from stored profile game_version - let version = minecraft - .versions - .iter() - .find(|it| it.id == profile.game_version) - .ok_or_else(|| { - crate::ErrorKind::LauncherError(format!( - "Invalid or unknown Minecraft version: {}", - profile.game_version - )) - })?; + let (minecraft, version_index) = + crate::launcher::resolve_minecraft_manifest( + &profile.game_version, + &state, + ) + .await?; + let version = &minecraft.versions[version_index]; let loader_version = crate::launcher::get_loader_version_from_profile( &profile.game_version, @@ -352,14 +346,22 @@ pub async fn install(path: &str, force: bool) -> crate::Result<()> { if let Some(profile) = get(path).await? { let result = crate::launcher::install_minecraft(&profile, None, force).await; - if result.is_err() - && profile.install_stage != ProfileInstallStage::Installed - { - edit(path, |prof| { - prof.install_stage = ProfileInstallStage::NotInstalled; - async { Ok(()) } - }) - .await?; + if result.is_err() { + // Re-read the profile to get the current install_stage, as + // install_minecraft may have changed it (e.g. to MinecraftInstalling) + let current_stage = get(path) + .await + .ok() + .flatten() + .map(|p| p.install_stage) + .unwrap_or(ProfileInstallStage::NotInstalled); + if current_stage != ProfileInstallStage::Installed { + edit(path, |prof| { + prof.install_stage = ProfileInstallStage::NotInstalled; + async { Ok(()) } + }) + .await?; + } } result?; } else { diff --git a/packages/app-lib/src/api/worlds.rs b/packages/app-lib/src/api/worlds.rs index 4cde476e12..4edfd5aecb 100644 --- a/packages/app-lib/src/api/worlds.rs +++ b/packages/app-lib/src/api/worlds.rs @@ -901,15 +901,13 @@ pub async fn get_profile_protocol_version( return Ok(Some(*protocol_version)); } - let minecraft = crate::api::metadata::get_minecraft_versions().await?; - let version_index = minecraft - .versions - .iter() - .position(|it| it.id == profile.game_version) - .ok_or(ErrorKind::LauncherError(format!( - "Invalid game version: {}", - profile.game_version - )))?; + let state = State::get().await?; + let (minecraft, version_index) = + crate::launcher::resolve_minecraft_manifest( + &profile.game_version, + &state, + ) + .await?; let version = &minecraft.versions[version_index]; let loader_version = get_loader_version_from_profile( diff --git a/packages/app-lib/src/launcher/mod.rs b/packages/app-lib/src/launcher/mod.rs index db29817fb8..51b8a91240 100644 --- a/packages/app-lib/src/launcher/mod.rs +++ b/packages/app-lib/src/launcher/mod.rs @@ -189,6 +189,46 @@ pub async fn get_loader_version_from_profile( } } +/// Resolves the Minecraft version manifest and finds the index for the given +/// game version. If the version isn't found in the cache, forces a manifest +/// refresh to pick up newly-released versions. +pub async fn resolve_minecraft_manifest( + game_version: &str, + state: &State, +) -> crate::Result<(d::minecraft::VersionManifest, usize)> { + let minecraft = crate::api::metadata::get_minecraft_versions().await?; + + if let Some(idx) = minecraft + .versions + .iter() + .position(|it| it.id == game_version) + { + return Ok((minecraft, idx)); + } + + // Version not found in cache — force a manifest refresh in case it was + // released after the cache was populated. + let refreshed = crate::state::CachedEntry::get_minecraft_manifest( + Some(crate::state::CacheBehaviour::MustRevalidate), + &state.pool, + &state.api_semaphore, + ) + .await? + .ok_or_else(|| { + crate::ErrorKind::NoValueFor("minecraft versions".to_string()) + })?; + + let idx = refreshed + .versions + .iter() + .position(|it| it.id == game_version) + .ok_or(crate::ErrorKind::LauncherError(format!( + "Invalid game version: {game_version}" + )))?; + + Ok((refreshed, idx)) +} + #[tracing::instrument(skip(profile))] pub async fn install_minecraft( @@ -219,16 +259,8 @@ pub async fn install_minecraft( let instance_path = crate::api::profile::get_full_path(&profile.path).await?; - let minecraft = crate::api::metadata::get_minecraft_versions().await?; - - let version_index = minecraft - .versions - .iter() - .position(|it| it.id == profile.game_version) - .ok_or(crate::ErrorKind::LauncherError(format!( - "Invalid game version: {}", - profile.game_version - )))?; + let (minecraft, version_index) = + resolve_minecraft_manifest(&profile.game_version, &state).await?; let version = &minecraft.versions[version_index]; let minecraft_updated = version_index <= minecraft @@ -481,15 +513,8 @@ pub async fn launch_minecraft( let instance_path = crate::api::profile::get_full_path(&profile.path).await?; - let minecraft = crate::api::metadata::get_minecraft_versions().await?; - let version_index = minecraft - .versions - .iter() - .position(|it| it.id == profile.game_version) - .ok_or(crate::ErrorKind::LauncherError(format!( - "Invalid game version: {}", - profile.game_version - )))?; + let (minecraft, version_index) = + resolve_minecraft_manifest(&profile.game_version, &state).await?; let version = &minecraft.versions[version_index]; let minecraft_updated = version_index <= minecraft diff --git a/packages/ui/src/layouts/shared/content-tab/composables/content-filtering.ts b/packages/ui/src/layouts/shared/content-tab/composables/content-filtering.ts index 7f448e1e18..cc0c569543 100644 --- a/packages/ui/src/layouts/shared/content-tab/composables/content-filtering.ts +++ b/packages/ui/src/layouts/shared/content-tab/composables/content-filtering.ts @@ -51,11 +51,7 @@ export function useContentFilters(items: Ref, config?: ContentFil } } - if ( - config?.showUpdateFilter && - !config?.isPackLocked?.value && - items.value.some((m) => m.has_update) - ) { + if (config?.showUpdateFilter && items.value.some((m) => m.has_update)) { options.push({ id: 'updates', label: 'Updates' }) } diff --git a/packages/ui/src/layouts/shared/content-tab/layout.vue b/packages/ui/src/layouts/shared/content-tab/layout.vue index e1334224aa..fd9b45158a 100644 --- a/packages/ui/src/layouts/shared/content-tab/layout.vue +++ b/packages/ui/src/layouts/shared/content-tab/layout.vue @@ -278,7 +278,7 @@ const tableItems = computed(() => { isBulkOperating.value || item.installing === true, installing: item.installing === true, - hasUpdate: !ctx.isPackLocked.value && item.has_update, + hasUpdate: item.has_update, isClientOnly: isClientOnlyEnvironment(item.environment), overflowOptions: ctx.getOverflowOptions?.(item), } @@ -711,7 +711,7 @@ const confirmUnlinkModal = ref>() >() >