wipe off incompatible mc version check entirely
This commit is contained in:
parent
c9c4c9536b
commit
e1be4c8bcc
4 changed files with 4 additions and 302 deletions
|
|
@ -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<Vec<String>> {
|
||||
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<String> =
|
||||
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<String> = 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<bool> {
|
||||
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<String> = 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<Item = &'a Copy>) -> Option<String> {
|
||||
|
|
@ -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::<Vec<_>>(),
|
||||
"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::<Vec<_>>(),
|
||||
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(&[
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -236,19 +236,6 @@ pub async fn get_release_by_hash(
|
|||
.await
|
||||
}
|
||||
|
||||
pub async fn list_release_game_versions(
|
||||
pool: &SqlitePool,
|
||||
hash: &str,
|
||||
) -> Result<Vec<String>, 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,
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue