fix: mod updates not being symlinked to a clusters mods folder after update

This commit is contained in:
LynithDev 2026-09-14 20:39:07 +02:00
parent db764e63f9
commit 6ceba7480f
4 changed files with 44 additions and 16 deletions

View file

@ -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");
}

View file

@ -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))]

View file

@ -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 {

View file

@ -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 {