diff --git a/packages/oneclient_content/src/packages/activity.rs b/packages/oneclient_content/src/packages/activity.rs index 1a7f2e41..7cbf6259 100644 --- a/packages/oneclient_content/src/packages/activity.rs +++ b/packages/oneclient_content/src/packages/activity.rs @@ -2,19 +2,13 @@ //! of one package are legal rows //! the newest stays enabled the rest are switched off rather than unlinked -use std::collections::{HashMap, HashSet}; +use std::collections::HashMap; -use oneclient_common::domain::{ContentType, ProviderId}; -use oneclient_db::dao::applied_migration as migration_dao; -use oneclient_db::dao::artifact as artifact_dao; -use oneclient_db::dao::bundle as bundle_catalog_dao; -use oneclient_db::dao::cluster_bundle as bundle_dao; +use oneclient_common::domain::ProviderId; use crate::ctx::ContentCtx; use crate::error::ContentResult; -use crate::packages::FileIdentity; -use crate::packages::dependencies::base_game_version; -use crate::packages::store::{PackageStore, record_release}; +use crate::packages::store::PackageStore; use crate::packages::types::LinkedArtifactInfo; struct Copy { @@ -93,180 +87,6 @@ fn group_duplicates(linked: &[LinkedArtifactInfo]) -> Vec<((ProviderId, String), by_project.into_iter().collect() } -#[tracing::instrument(level = "debug", skip(ctx))] -pub async fn disable_foreign_game_versions( - cluster_id: i64, - mc_version: &str, - mc_loader: i64, - ctx: &ContentCtx, -) -> ContentResult> { - if mc_version.is_empty() { - return Ok(Vec::new()); - } - - let linked = PackageStore::list_linked_artifacts(cluster_id, ctx).await?; - - // bundle installs skip the compatibility check so launch must trust them too - let live_bundles: HashSet = - bundle_catalog_dao::list_visible_for_version_loader(&ctx.db, mc_version, mc_loader) - .await? - .into_iter() - .filter_map(|row| row.name) - .collect(); - - let from_bundle: HashSet = bundle_dao::list_bundle_tracked(&ctx.db, cluster_id) - .await? - .into_iter() - .filter(|row| { - row.bundle_name - .as_deref() - .is_some_and(|name| live_bundles.contains(name)) - }) - .map(|row| row.hash) - .collect(); - - let candidates: Vec<&LinkedArtifactInfo> = linked - .iter() - .filter(|info| { - info.enabled - && info.content_type == ContentType::Mod - && !from_bundle.contains(&info.hash) - }) - .collect(); - - refresh_stated_game_versions(cluster_id, &candidates, mc_version, ctx).await; - - let mut switched_off = Vec::new(); - - for info in candidates { - match switch_off_if_foreign(cluster_id, info, mc_version, ctx).await { - Ok(true) => switched_off.push( - info.display_name - .clone() - .unwrap_or_else(|| info.file_name.clone()), - ), - Ok(false) => {} - Err(err) => tracing::warn!( - cluster_id, - hash = %info.hash, - %err, - "could not switch off a mod built for another game version" - ), - } - } - - Ok(switched_off) -} - -const FOREIGN_VERSION_REPAIR: &str = "disable-foreign-game-version"; - -fn foreign_repair_id(cluster_id: i64, mc_version: &str, hash: &str) -> String { - format!("{FOREIGN_VERSION_REPAIR}:{cluster_id}:{mc_version}:{hash}") -} - -async fn refresh_stated_game_versions( - cluster_id: i64, - candidates: &[&LinkedArtifactInfo], - mc_version: &str, - ctx: &ContentCtx, -) { - let mut stale = Vec::new(); - - for info in candidates { - match migration_dao::is_applied( - &ctx.db, - &foreign_repair_id(cluster_id, mc_version, &info.hash), - ) - .await - { - Ok(true) => continue, - Ok(false) => {} - Err(err) => { - tracing::debug!(hash = %info.hash, %err, "could not read the repair marker"); - continue; - } - } - - match artifact_dao::list_release_game_versions(&ctx.db, &info.hash).await { - Ok(stated) if built_for_another_game_version(&stated, mc_version) => { - stale.push(FileIdentity::from_sha1(&info.hash)); - } - Ok(_) => {} - Err(err) => { - tracing::debug!(hash = %info.hash, %err, "could not read the stated game versions") - } - } - } - - if stale.is_empty() { - return; - } - - let found = match ctx.providers.lookup_versions(&stale, ctx).await { - Ok(found) => found, - // Offline or the provider is down, so the stored rows are all there is - Err(err) => { - tracing::debug!(%err, "could not refresh the stated game versions"); - return; - } - }; - - for (sha1, (provider, version)) in found { - if let Err(err) = record_release(provider, &version, &sha1, ctx).await { - tracing::debug!(hash = %sha1, %err, "could not store the refreshed game versions"); - } - } -} - -async fn switch_off_if_foreign( - cluster_id: i64, - info: &LinkedArtifactInfo, - mc_version: &str, - ctx: &ContentCtx, -) -> ContentResult { - let repair_id = foreign_repair_id(cluster_id, mc_version, &info.hash); - if migration_dao::is_applied(&ctx.db, &repair_id).await? { - return Ok(false); - } - - let stated = artifact_dao::list_release_game_versions(&ctx.db, &info.hash).await?; - if !built_for_another_game_version(&stated, mc_version) { - return Ok(false); - } - - tracing::info!( - cluster_id, - hash = %info.hash, - built_for = ?stated, - "switching off a mod built for another game version" - ); - - PackageStore::set_artifact_enabled_to(cluster_id, &info.hash, false, ctx).await?; - migration_dao::mark_applied(&ctx.db, &repair_id).await?; - - Ok(true) -} - -fn built_for_another_game_version(stated: &[String], mc_version: &str) -> bool { - !stated.is_empty() - && !stated.iter().any(|versions| { - let built_for: Vec = serde_json::from_str(versions).unwrap_or_default(); - covers_game_version(&built_for, mc_version) - }) -} - -fn covers_game_version(stated: &[String], mc_version: &str) -> bool { - let wanted = base_game_version(mc_version); - stated.is_empty() - || stated.iter().any(|version| { - let built_for = base_game_version(version); - built_for == wanted - || wanted - .strip_prefix(built_for) - .is_some_and(|rest| rest.starts_with('.')) - }) -} - /// `published_at` is RFC 3339 so string order matches time order /// undated copies lose to dated ones and an all-undated group still picks one fn newest<'a>(copies: impl IntoIterator) -> Option { @@ -350,78 +170,6 @@ mod tests { ); } - #[test] - fn only_a_stated_mismatch_switches_a_mod_off() { - let foreign = |rows: &[&str]| { - built_for_another_game_version( - &rows.iter().map(|r| (*r).to_string()).collect::>(), - "26.1.2", - ) - }; - - assert!( - !foreign(&[]), - "a jar no provider ever described is left alone" - ); - assert!( - !foreign(&["[]"]), - "an empty list is the provider saying nothing" - ); - assert!( - !foreign(&["[\"1.21.11"]), - "a list that will not parse says nothing" - ); - assert!(!foreign(&["[\"26.1.2\"]"])); - assert!( - !foreign(&["[\"1.21.11\",\"26.1.2\"]"]), - "a multi-version build stays" - ); - assert!( - !foreign(&["[\"1.21.11\"]", "[\"26.1.2\"]"]), - "one release row out of two is enough to keep it" - ); - assert!( - foreign(&["[\"1.21.11\"]"]), - "the jars the old stash dragged in have to go" - ); - assert!( - !foreign(&["[\"26.1\"]"]), - "a build for the release this cluster was migrated from stays" - ); - assert!( - foreign(&["[\"1.21.1\"]"]), - "a shorter version that is not a component prefix is still foreign" - ); - } - - #[test] - fn a_pre_release_tag_is_the_same_game_version() { - let foreign = |rows: &[&str], mc_version: &str| { - built_for_another_game_version( - &rows.iter().map(|r| (*r).to_string()).collect::>(), - mc_version, - ) - }; - - assert!( - !foreign(&["[\"26.3-rc-1\"]"], "26.3"), - "a jar built against the release candidate runs on the release" - ); - assert!(!foreign(&["[\"26.3-snapshot-7\",\"26.3-pre-2\"]"], "26.3")); - assert!( - !foreign(&["[\"26.3\"]"], "26.3-snapshot-10"), - "and a snapshot cluster keeps the release build" - ); - assert!( - !foreign(&["[\"26.3-rc-1\"]"], "26.3.1"), - "the component prefix still applies once the tag is off" - ); - assert!( - foreign(&["[\"26.2-rc-1\"]"], "26.3"), - "stripping the tag must not blur two game versions together" - ); - } - #[test] fn only_projects_with_several_copies_are_reconciled() { let groups = group_duplicates(&[ diff --git a/packages/oneclient_content/src/packages/mod.rs b/packages/oneclient_content/src/packages/mod.rs index 5a80acb5..d3ab2f7f 100644 --- a/packages/oneclient_content/src/packages/mod.rs +++ b/packages/oneclient_content/src/packages/mod.rs @@ -15,7 +15,7 @@ mod file_identity; pub mod markdown; // Re-exported so `oneclient_core::packages::ContentType` keeps working -pub use activity::{disable_foreign_game_versions, reconcile_duplicate_activity}; +pub use activity::reconcile_duplicate_activity; pub use bad_mods::{ BadMod, BadModAlternative, BadModList, ProjectIds, ResolvedAlternative, fetch_bad_mods, fetch_explanation, load_bad_mods, refresh_bad_mods, resolve_alternatives, diff --git a/packages/oneclient_core/src/game/shared_dir.rs b/packages/oneclient_core/src/game/shared_dir.rs index a9fdd24b..5816a663 100644 --- a/packages/oneclient_core/src/game/shared_dir.rs +++ b/packages/oneclient_core/src/game/shared_dir.rs @@ -168,39 +168,6 @@ pub async fn materialize_content( tracing::warn!(cluster_id = cluster.id, %err, "failed to resolve duplicate package versions"); } - match oneclient_content::packages::disable_foreign_game_versions( - cluster.id, - &cluster.mc_version, - cluster.mc_loader as i64, - &services.content(), - ) - .await - { - Ok(disabled) if !disabled.is_empty() => { - let names = removal_summary(&disabled); - let body = if disabled.len() == 1 { - format!( - "{names} is built for a different Minecraft version, so it has been switched off in {}.", - cluster.name - ) - } else { - format!( - "{names} are built for a different Minecraft version, so they have been switched off in {}.", - cluster.name - ) - }; - services - .events - .notify("Incompatible mods switched off") - .body(body) - .send(); - } - Ok(_) => {} - Err(err) => { - tracing::warn!(cluster_id = cluster.id, %err, "failed to switch off mods built for another game version"); - } - } - let active_mods_dir = if mods_in_cluster { cluster_dir.as_path() } else { diff --git a/packages/oneclient_db/src/dao/artifact.rs b/packages/oneclient_db/src/dao/artifact.rs index 8e597ebf..6e85d7da 100644 --- a/packages/oneclient_db/src/dao/artifact.rs +++ b/packages/oneclient_db/src/dao/artifact.rs @@ -236,19 +236,6 @@ pub async fn get_release_by_hash( .await } -pub async fn list_release_game_versions( - pool: &SqlitePool, - hash: &str, -) -> Result, sqlx::Error> { - let rows: Vec<(String,)> = - sqlx::query_as("SELECT mc_versions FROM provider_releases WHERE hash = ?") - .bind(hash) - .fetch_all(pool) - .await?; - - Ok(rows.into_iter().map(|(versions,)| versions).collect()) -} - pub async fn link_cluster_artifact( pool: &SqlitePool, cluster_id: i64,