From 9dcf9c3aab3e98883704686b06869f6dbbb786cf Mon Sep 17 00:00:00 2001 From: "Calum H. (IMB11)" Date: Mon, 9 Nov 2026 18:49:56 +0000 Subject: [PATCH 1/2] fix: bulk update regression duplicates --- .../commands/apply_content_update.rs | 56 ++++++++++++++++--- .../instances/commands/content_mutation.rs | 4 +- 2 files changed, 52 insertions(+), 8 deletions(-) diff --git a/packages/app-lib/src/state/instances/commands/apply_content_update.rs b/packages/app-lib/src/state/instances/commands/apply_content_update.rs index f8900150306..ea92f819d86 100644 --- a/packages/app-lib/src/state/instances/commands/apply_content_update.rs +++ b/packages/app-lib/src/state/instances/commands/apply_content_update.rs @@ -186,7 +186,23 @@ pub(crate) async fn apply_bulk_update( .await?; match download { DownloadedBulkProject::ProjectUpdate(update, downloaded) => { + let mut enabled = + content_rows::get_instance_file_by_relative_path( + instance_id, + &update.relative_path, + &state.pool, + ) + .await? + .is_none_or(|file| file.enabled); for path in &update.duplicate_paths { + enabled |= + content_rows::get_instance_file_by_relative_path( + instance_id, + path, + &state.pool, + ) + .await? + .is_some_and(|file| file.enabled); super::content_mutation::remove_project( instance_id, path, @@ -194,13 +210,6 @@ pub(crate) async fn apply_bulk_update( ) .await?; } - let enabled = content_rows::get_instance_file_by_relative_path( - instance_id, - &update.relative_path, - &state.pool, - ) - .await? - .is_none_or(|file| file.enabled); let new_path = add_downloaded_project_version_with_enabled( instance_id, downloaded, @@ -532,6 +541,14 @@ pub(crate) async fn plan_bulk_update( .insert(update.project_id.clone(), (update, Vec::new())); } } + for (update, duplicate_paths) in updates_by_project.values_mut() { + adopt_current_copy( + update, + duplicate_paths, + &installed, + &updateable_paths, + ); + } let updates_by_path = updates_by_project .values() .map(|(update, _)| { @@ -591,6 +608,31 @@ pub(crate) async fn plan_bulk_update( }) } +fn adopt_current_copy( + update: &mut ContentUpdate, + duplicate_paths: &mut Vec, + installed: &[InstalledProject], + updateable_paths: &HashSet, +) { + let Some(current) = installed.iter().find(|project| { + project.project_id.as_ref() == Some(&update.project_id) + && project.version_id.as_ref() == Some(&update.update_version_id) + && project.relative_path != update.relative_path + && !duplicate_paths.contains(&project.relative_path) + && updateable_paths.contains(&project.relative_path) + }) else { + return; + }; + + duplicate_paths.push(std::mem::replace( + &mut update.relative_path, + current.relative_path.clone(), + )); + update + .current_version_id + .clone_from(&update.update_version_id); +} + async fn bulk_updateable_project_paths( instance_id: &str, shared_instance_member: bool, diff --git a/packages/app-lib/src/state/instances/commands/content_mutation.rs b/packages/app-lib/src/state/instances/commands/content_mutation.rs index 72324cdcc18..fa5e977d942 100644 --- a/packages/app-lib/src/state/instances/commands/content_mutation.rs +++ b/packages/app-lib/src/state/instances/commands/content_mutation.rs @@ -233,7 +233,9 @@ impl<'a> InstanceContent<'a> { )); } let enabled = if request.previous_path.is_some() { - existing.as_ref().is_some_and(|file| file.enabled) + request.enabled_override.unwrap_or_else(|| { + existing.as_ref().is_some_and(|file| file.enabled) + }) } else { request.enabled_override.unwrap_or_else(|| { !request.requested_path.ends_with(".disabled") From 4d5b0edae5058b419591ead0f8e37067c69ff9bd Mon Sep 17 00:00:00 2001 From: "Calum H. (IMB11)" Date: Mon, 9 Nov 2026 18:57:37 +0000 Subject: [PATCH 2/2] fix: rev --- .../commands/apply_content_update.rs | 55 ++++++++----------- 1 file changed, 22 insertions(+), 33 deletions(-) diff --git a/packages/app-lib/src/state/instances/commands/apply_content_update.rs b/packages/app-lib/src/state/instances/commands/apply_content_update.rs index ea92f819d86..e269803d021 100644 --- a/packages/app-lib/src/state/instances/commands/apply_content_update.rs +++ b/packages/app-lib/src/state/instances/commands/apply_content_update.rs @@ -117,13 +117,6 @@ async fn apply_content_update( update: &ContentUpdate, state: &State, ) -> crate::Result { - let enabled = content_rows::get_instance_file_by_relative_path( - instance_id, - project_path, - &state.pool, - ) - .await? - .is_none_or(|file| file.enabled); let downloaded = download_project_version( instance_id, &update.update_version_id, @@ -139,7 +132,7 @@ async fn apply_content_update( instance_id, downloaded, ContentSourceKind::Local, - Some(enabled), + None, Some(project_path), state, ) @@ -549,25 +542,14 @@ pub(crate) async fn plan_bulk_update( &updateable_paths, ); } - let updates_by_path = updates_by_project + let planned_versions = updates_by_project .values() - .map(|(update, _)| { - ( - update.relative_path.clone(), - update.update_version_id.clone(), - ) + .filter(|(update, duplicate_paths)| { + is_enabled(&update.relative_path) + || duplicate_paths.iter().any(|path| is_enabled(path)) }) - .collect::>(); - let planned_versions = installed - .iter() - .filter(|project| project.enabled) - .filter(|project| updates_by_path.contains_key(&project.relative_path)) - .filter_map(|project| { - let target_version_id = updates_by_path - .get(&project.relative_path) - .or(project.version_id.as_ref())?; - - versions_by_id.get(target_version_id).cloned() + .filter_map(|(update, _)| { + versions_by_id.get(&update.update_version_id).cloned() }) .collect::>(); let planned_dependencies = @@ -623,14 +605,21 @@ fn adopt_current_copy( }) else { return; }; - - duplicate_paths.push(std::mem::replace( - &mut update.relative_path, - current.relative_path.clone(), - )); - update - .current_version_id - .clone_from(&update.update_version_id); + let selected_enabled = installed.iter().any(|project| { + project.relative_path == update.relative_path && project.enabled + }); + + if current.enabled && !selected_enabled { + duplicate_paths.push(std::mem::replace( + &mut update.relative_path, + current.relative_path.clone(), + )); + update + .current_version_id + .clone_from(&update.update_version_id); + } else { + duplicate_paths.push(current.relative_path.clone()); + } } async fn bulk_updateable_project_paths(