fix(content): stop removed bundle mods coming back

This commit is contained in:
LynithDev 2026-07-28 13:16:15 +02:00
parent d9e4f99add
commit 56b421b187
2 changed files with 51 additions and 6 deletions

View file

@ -68,13 +68,28 @@ pub async fn unlink_cluster_file(
content_type: ContentType,
file_name: &str,
) -> ContentResult<()> {
let path = paths::clusters_dir()?
let dir = paths::clusters_dir()?
.join(&cluster.folder_name)
.join(content_type.folder_name())
.join(file_name);
.join(content_type.folder_name());
if path.exists() {
polyio::remove_file(&path).await?;
}
// Both spellings: the database always stores the bare name, but the folder
// can still be holding a `<name>.disabled` left by an older launcher or by
// the user renaming it by hand. Removing a package has to take both.
remove_entry(&dir.join(file_name)).await?;
remove_entry(&dir.join(format!("{file_name}.disabled"))).await?;
Ok(())
}
/// Deletes `path` if anything is there, symlink or not.
///
/// `Path::exists` resolves symlinks, so a link whose artifact was evicted from
/// the cache reads as absent and the dead link survives every unlink; the
/// package then keeps showing up in the folder it was removed from.
async fn remove_entry(path: &Path) -> ContentResult<()> {
if polyio::symlink_metadata(path).await.is_err() {
return Ok(());
}
polyio::remove_file(path).await?;
Ok(())
}

View file

@ -126,6 +126,21 @@ pub async fn import_manual_content(
continue;
}
// The shared dir is rebuilt from the database on every launch, so
// anything here that the launcher already has in its artifact cache
// is a leftover from a session whose cleanup never ran — a crash, or
// a bundle update that dropped the package in between. Importing it
// would re-link the package the update just removed, and the next
// launch would put it back in the game. Drop it instead; the cache
// still holds the file if it is ever wanted again.
if is_cached_artifact(services, &path).await {
tracing::debug!(file = name, "discarding stale launcher content in shared dir");
if let Err(err) = polyio::remove_file(&path).await {
tracing::warn!(file = name, error = %err, "failed to discard stale shared content");
}
continue;
}
match PackageStore::import_local_file(&path, content_type, cluster.id, &services.content()).await {
Ok(_) => {
tracing::debug!(file = name, "registered manually-added shared content")
@ -140,6 +155,21 @@ pub async fn import_manual_content(
}
}
/// Whether `path` is a copy of something already in the launcher's artifact
/// cache, i.e. content the launcher itself put in the shared dir rather than
/// something the user dropped there.
async fn is_cached_artifact(services: &LauncherServices, path: &Path) -> bool {
let Ok(hash) = polyio::sha1_file(path).await else {
return false;
};
artifact_dao::get_artifact_by_hash(&services.db, &polyio::normalize_hash(&hash))
.await
.ok()
.flatten()
.is_some()
}
fn has_content_extension(content_type: ContentType, name: &str) -> bool {
let lower = name.to_lowercase();
match content_type {