From 6ceba7480fe19a3731b839ccea5480ddaec8e20d Mon Sep 17 00:00:00 2001 From: LynithDev <61880709+LynithDev@users.noreply.github.com> Date: Mon, 14 Sep 2026 20:39:07 +0200 Subject: [PATCH] fix: mod updates not being symlinked to a clusters mods folder after update --- .../oneclient_content/src/bundles/install.rs | 14 ++++++--- .../src/packages/store/link.rs | 30 +++++++++++++++---- .../oneclient_content/src/packages/updates.rs | 9 ++++-- .../oneclient_core/src/game/shared_dir.rs | 7 +++-- 4 files changed, 44 insertions(+), 16 deletions(-) diff --git a/packages/oneclient_content/src/bundles/install.rs b/packages/oneclient_content/src/bundles/install.rs index 9ebe909c..496fe527 100644 --- a/packages/oneclient_content/src/bundles/install.rs +++ b/packages/oneclient_content/src/bundles/install.rs @@ -749,13 +749,19 @@ pub async fn remove_artifact_from_cluster( artifact_dao::unlink_cluster_artifact(&ctx.db, cluster_id, hash).await?; // Best-effort folder cleanup failure here is not an error - if let (Some(content_type), Some(link)) = (target, link) { - try_unlink_materialized(&cluster, content_type, &link.cluster_file_name).await; - } + let deferred = match (target, link) { + (Some(content_type), Some(link)) => { + try_unlink_materialized(&cluster, content_type, &link.cluster_file_name).await + == LiveSync::Deferred + } + _ => false, + }; // The package actually lives in the cache // `evict_if_unused` drops it only once no other cluster still needs it - if let Err(err) = evict_if_unused(hash, ctx).await { + if !deferred + && let Err(err) = evict_if_unused(hash, ctx).await + { tracing::warn!(hash, error = %err, "failed to evict unused artifact from the cache"); } diff --git a/packages/oneclient_content/src/packages/store/link.rs b/packages/oneclient_content/src/packages/store/link.rs index 45f576d3..d1f1e432 100644 --- a/packages/oneclient_content/src/packages/store/link.rs +++ b/packages/oneclient_content/src/packages/store/link.rs @@ -109,20 +109,38 @@ async fn materialized_root( .map(|dir| (dir, manifest::MANIFEST_NAME)) } +async fn session_owns(cluster: &ClusterRow) -> bool { + let Ok(game_dir) = paths::cluster_game_dir(&cluster.folder_name) else { + return false; + }; + + manifest::load(&game_dir, manifest::MANIFEST_NAME) + .await + .is_some_and(|session| session.cluster_id == cluster.id) +} + #[tracing::instrument(level = "debug", skip(cluster), fields(cluster_id = cluster.id))] pub async fn try_unlink_materialized( cluster: &ClusterRow, content_type: ContentType, file_name: &str, -) -> bool { +) -> LiveSync { + if !content_type.reloads_in_game() && session_owns(cluster).await { + tracing::debug!( + file = file_name, + "file will be moved at next launch due to an active session" + ); + return LiveSync::Deferred; + } + let Some((root, manifest_name)) = materialized_root(cluster, content_type).await else { - return false; + return LiveSync::Deferred; }; let _guard = manifest::lock().await; let Some(mut loaded) = manifest::load(&root, manifest_name).await else { - return false; + return LiveSync::Skipped; }; let relative = manifest::entry_path(content_type.folder_name(), file_name); @@ -133,7 +151,7 @@ pub async fn try_unlink_materialized( }; if !ours { - return false; + return LiveSync::Skipped; } let path = root.join(&relative); @@ -143,13 +161,13 @@ pub async fn try_unlink_materialized( error = %err, "could not drop package from the game folder now; it goes at the next launch" ); - return false; + return LiveSync::Deferred; } loaded.entries.retain(|entry| entry.path != relative); manifest::save(&root, manifest_name, &loaded).await; - true + LiveSync::Applied } #[tracing::instrument(level = "debug", skip(cluster, artifact), fields(cluster_id = cluster.id, hash = %artifact.hash))] diff --git a/packages/oneclient_content/src/packages/updates.rs b/packages/oneclient_content/src/packages/updates.rs index 699a474c..e17d37e6 100644 --- a/packages/oneclient_content/src/packages/updates.rs +++ b/packages/oneclient_content/src/packages/updates.rs @@ -20,7 +20,7 @@ use oneclient_events::GroupedProgressChild; use crate::ctx::ContentCtx; use crate::error::{ContentError, ContentResult}; use crate::packages::dependencies::pick_version; -use crate::packages::store::{PackageStore, evict_if_unused, try_unlink_materialized}; +use crate::packages::store::{LiveSync, PackageStore, evict_if_unused, try_unlink_materialized}; use crate::packages::types::LinkedArtifactInfo; /// Matches the bundle installer's fan-out @@ -459,8 +459,11 @@ async fn unlink_superseded(cluster_id: i64, hash: &str, ctx: &ContentCtx) -> Con artifact_dao::unlink_cluster_artifact(&ctx.db, cluster_id, hash).await?; - if let (Some(content_type), Some(link)) = (content_type, link) { - try_unlink_materialized(&cluster, content_type, &link.cluster_file_name).await; + if let (Some(content_type), Some(link)) = (content_type, link) + && try_unlink_materialized(&cluster, content_type, &link.cluster_file_name).await + == LiveSync::Deferred + { + return Ok(()); } if let Err(err) = evict_if_unused(hash, ctx).await { diff --git a/packages/oneclient_core/src/game/shared_dir.rs b/packages/oneclient_core/src/game/shared_dir.rs index f55d1f9a..6dc76696 100644 --- a/packages/oneclient_core/src/game/shared_dir.rs +++ b/packages/oneclient_core/src/game/shared_dir.rs @@ -69,6 +69,7 @@ pub async fn materialize_content( unwind_cluster_mods(cluster, &cluster_dir).await; } + adopt_into_global(&cluster_dir, &global_root).await; adopt_into_global(game_dir, &global_root).await; ensure_global_links(game_dir, &global_root).await; @@ -268,13 +269,13 @@ fn without_global_entries(mut manifest: MaterializedManifest) -> MaterializedMan } // moves a cluster's own pack folders into the shared one before [`ensure_global_links`] replaces them with links -async fn adopt_into_global(game_dir: &Path, global_root: &Path) { - if game_dir == global_root { +async fn adopt_into_global(own_root: &Path, global_root: &Path) { + if own_root == global_root { return; } for content_type in GLOBAL_TYPES { - let own = game_dir.join(content_type.folder_name()); + let own = own_root.join(content_type.folder_name()); let shared = global_root.join(content_type.folder_name()); match polyio::symlink_metadata(&own).await {