Skip to content

Commit 4c0fcf0

Browse files
authored
fix(app/s-inst): preserve multiple shared versions of a project (#7770)
* fix(app): preserve multiple shared versions of a project * fix: fmt * fix: ci
1 parent aff89e5 commit 4c0fcf0

3 files changed

Lines changed: 117 additions & 69 deletions

File tree

‎packages/app-lib/src/api/instance/shared/diff.rs‎

Lines changed: 70 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ use super::client::*;
22
use super::publish::*;
33
use super::types::*;
44
use super::*;
5+
use std::collections::BTreeMap;
56

67
struct SharedContentSnapshot {
78
version_ids: Vec<String>,
@@ -36,6 +37,7 @@ pub(super) async fn shared_instance_update_diffs(
3637
&before,
3738
&after,
3839
&HashSet::new(),
40+
&HashSet::new(),
3941
&BTreeSet::new(),
4042
CommonExternalFilePolicy::AssumeUpdated,
4143
state,
@@ -53,22 +55,8 @@ pub(super) async fn shared_instance_publish_diffs(
5355
let after_configuration = local_configuration(metadata);
5456
let modpack_unlinked = before_configuration.modpack_version_id.is_some()
5557
&& after_configuration.modpack_version_id.is_none();
56-
let disabled_versions = async {
57-
if snapshot.disabled_version_ids.is_empty() {
58-
Ok(HashMap::new())
59-
} else {
60-
shared_versions_by_project(
61-
&snapshot.disabled_version_ids,
62-
true,
63-
state,
64-
)
65-
.await
66-
}
67-
};
68-
let ((version_ids, external_files), disabled_versions) = tokio::try_join!(
69-
remote_publish_content(version, modpack_unlinked, state),
70-
disabled_versions,
71-
)?;
58+
let (version_ids, external_files) =
59+
remote_publish_content(version, modpack_unlinked, state).await?;
7260
let before = SharedContentSnapshot {
7361
version_ids,
7462
external_files,
@@ -93,14 +81,11 @@ pub(super) async fn shared_instance_publish_diffs(
9381
.collect::<crate::Result<_>>()?,
9482
configuration: after_configuration,
9583
};
96-
let mut removed_disabled_project_ids =
97-
snapshot.disabled_project_ids.clone();
98-
removed_disabled_project_ids.extend(disabled_versions.into_keys());
99-
10084
shared_content_diffs(
10185
&before,
10286
&after,
103-
&removed_disabled_project_ids,
87+
&snapshot.disabled_project_ids,
88+
&snapshot.disabled_version_ids.iter().cloned().collect(),
10489
&snapshot.disabled_external_files,
10590
CommonExternalFilePolicy::AssumeUnchanged,
10691
state,
@@ -295,30 +280,24 @@ async fn shared_content_diffs(
295280
before: &SharedContentSnapshot,
296281
after: &SharedContentSnapshot,
297282
removed_disabled_project_ids: &HashSet<String>,
283+
removed_disabled_version_ids: &HashSet<String>,
298284
removed_disabled_external_files: &BTreeSet<ExternalFileKey>,
299285
common_external_files: CommonExternalFilePolicy,
300286
state: &State,
301287
) -> crate::Result<Vec<SharedInstanceUpdateDiff>> {
302288
let (before_versions, after_versions) = tokio::try_join!(
303-
shared_versions_by_project(&before.version_ids, true, state),
304-
shared_versions_by_project(&after.version_ids, false, state),
289+
shared_versions_by_id(&before.version_ids, true, state),
290+
shared_versions_by_id(&after.version_ids, false, state),
305291
)?;
306-
let to_snapshot =
307-
|source: &SharedContentSnapshot,
308-
versions: &HashMap<String, crate::state::Version>| {
309-
ContentSetSnapshot {
310-
projects: versions
311-
.iter()
312-
.map(|(project_id, version)| {
313-
(project_id.clone(), version.id.clone())
314-
})
315-
.collect(),
316-
external_files: source.external_files.clone(),
317-
}
318-
};
319-
let diff = diff_content_sets(
320-
&to_snapshot(before, &before_versions),
321-
&to_snapshot(after, &after_versions),
292+
let mut diff = diff_content_sets(
293+
&ContentSetSnapshot {
294+
projects: BTreeMap::default(),
295+
external_files: before.external_files.clone(),
296+
},
297+
&ContentSetSnapshot {
298+
projects: BTreeMap::default(),
299+
external_files: after.external_files.clone(),
300+
},
322301
&ContentSetDiffOptions {
323302
common_external_files,
324303
},
@@ -327,6 +306,52 @@ async fn shared_content_diffs(
327306
&before.configuration,
328307
&after.configuration,
329308
));
309+
let project_ids = before_versions
310+
.values()
311+
.chain(after_versions.values())
312+
.map(|version| version.project_id.clone())
313+
.collect::<BTreeSet<_>>();
314+
for project_id in project_ids {
315+
let removed = before_versions
316+
.values()
317+
.filter(|version| {
318+
version.project_id == project_id
319+
&& !after_versions.contains_key(&version.id)
320+
})
321+
.map(|version| version.id.clone())
322+
.collect::<BTreeSet<_>>();
323+
let added = after_versions
324+
.values()
325+
.filter(|version| {
326+
version.project_id == project_id
327+
&& !before_versions.contains_key(&version.id)
328+
})
329+
.map(|version| version.id.clone())
330+
.collect::<BTreeSet<_>>();
331+
// Only pair an unambiguous replacement; preserve every other version change.
332+
if removed.len() == 1 && added.len() == 1 {
333+
diff.content.push(ContentSetDiffEntry::Project {
334+
project_id,
335+
change: Change::Updated {
336+
before: removed.into_iter().next().unwrap(),
337+
after: added.into_iter().next().unwrap(),
338+
},
339+
});
340+
} else {
341+
for before in removed {
342+
diff.content.push(ContentSetDiffEntry::Project {
343+
project_id: project_id.clone(),
344+
change: Change::Removed { before },
345+
});
346+
}
347+
for after in added {
348+
diff.content.push(ContentSetDiffEntry::Project {
349+
project_id: project_id.clone(),
350+
change: Change::Added { after },
351+
});
352+
}
353+
}
354+
}
330355
let resolved_before_ids = before_versions
331356
.values()
332357
.map(|version| version.id.as_str())
@@ -367,7 +392,10 @@ async fn shared_content_diffs(
367392
match entry {
368393
ContentSetDiffEntry::Project { project_id, change } => {
369394
let disabled = change.kind() == ContentSetDiffKind::Removed
370-
&& removed_disabled_project_ids.contains(&project_id);
395+
&& (removed_disabled_project_ids.contains(&project_id)
396+
|| change.before().is_some_and(|id| {
397+
removed_disabled_version_ids.contains(id)
398+
}));
371399
content_diffs.push(SharedInstanceUpdateDiff {
372400
type_: shared_update_diff_type(change.kind()),
373401
project_name: Some(
@@ -378,13 +406,13 @@ async fn shared_content_diffs(
378406
),
379407
current_version_name: change.before().map(|id| {
380408
before_versions
381-
.get(&project_id)
409+
.get(id)
382410
.map(|version| version.version_number.clone())
383411
.unwrap_or_else(|| id.clone())
384412
}),
385413
new_version_name: change.after().map(|id| {
386414
after_versions
387-
.get(&project_id)
415+
.get(id)
388416
.map(|version| version.version_number.clone())
389417
.unwrap_or_else(|| id.clone())
390418
}),

‎packages/app-lib/src/api/instance/shared/publish.rs‎

Lines changed: 15 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -237,13 +237,17 @@ async fn extend_shared_modpack_dependencies(
237237
let dependency_ids =
238238
modpack_dependency_version_ids(modpack_id, state).await?;
239239
let (explicit, inherited) = tokio::try_join!(
240-
shared_versions_by_project(version_ids, false, state),
241-
shared_versions_by_project(&dependency_ids, false, state),
240+
shared_versions_by_id(version_ids, false, state),
241+
shared_versions_by_id(&dependency_ids, false, state),
242242
)?;
243243
version_ids.extend(
244244
inherited
245245
.into_values()
246-
.filter(|version| !explicit.contains_key(&version.project_id))
246+
.filter(|version| {
247+
!explicit
248+
.values()
249+
.any(|installed| installed.project_id == version.project_id)
250+
})
247251
.map(|version| version.id),
248252
);
249253
Ok(())
@@ -460,7 +464,9 @@ pub(super) async fn collect_publish_snapshot(
460464
continue;
461465
}
462466

463-
if let Some(project) = item.project.as_ref() {
467+
if item.version.is_none()
468+
&& let Some(project) = item.project.as_ref()
469+
{
464470
disabled_project_ids.insert(project.id.clone());
465471
}
466472

@@ -491,7 +497,7 @@ pub(super) async fn collect_publish_snapshot(
491497
})
492498
}
493499

494-
pub(super) async fn shared_versions_by_project(
500+
pub(super) async fn shared_versions_by_id(
495501
version_ids: &[String],
496502
allow_missing: bool,
497503
state: &State,
@@ -520,15 +526,10 @@ pub(super) async fn shared_versions_by_project(
520526
))
521527
.into());
522528
}
523-
let mut snapshot = ContentSetSnapshot::default();
524-
let mut by_project = HashMap::new();
525-
for version in versions {
526-
snapshot
527-
.insert_project(version.project_id.clone(), version.id.clone())
528-
.map_err(|error| crate::ErrorKind::InputError(error.to_string()))?;
529-
by_project.insert(version.project_id.clone(), version);
530-
}
531-
Ok(by_project)
529+
Ok(versions
530+
.into_iter()
531+
.map(|version| (version.id.clone(), version))
532+
.collect())
532533
}
533534

534535
pub(super) async fn shared_project_names(

‎packages/app-lib/src/install/shared_instance.rs‎

Lines changed: 32 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -155,7 +155,7 @@ impl SharedInstanceApplyPlan {
155155
.projects
156156
.values()
157157
.filter(|current| {
158-
!desired.projects.contains_key(&current.project_id)
158+
!desired.projects.contains_key(&current.version_id)
159159
})
160160
.cloned()
161161
.collect();
@@ -172,16 +172,35 @@ impl SharedInstanceApplyPlan {
172172
..Default::default()
173173
};
174174

175-
for desired in desired.projects.into_values() {
176-
match current.projects.get(&desired.project_id) {
177-
Some(current) if current.version_id != desired.version_id => {
178-
plan.project_updates.push(SharedInstanceProjectUpdate {
179-
current: current.clone(),
180-
desired,
181-
});
182-
}
183-
None => plan.project_additions.push(desired),
184-
Some(_) => {}
175+
let mut additions = desired
176+
.projects
177+
.into_values()
178+
.filter(|desired| {
179+
!current.projects.contains_key(&desired.version_id)
180+
})
181+
.collect::<Vec<_>>();
182+
while let Some(desired) = additions.pop() {
183+
let replacements = plan
184+
.project_removals
185+
.iter()
186+
.enumerate()
187+
.filter(|(_, current)| current.project_id == desired.project_id)
188+
.map(|(index, _)| index)
189+
.collect::<Vec<_>>();
190+
let unambiguous = !additions
191+
.iter()
192+
.any(|other| other.project_id == desired.project_id)
193+
&& !plan
194+
.project_additions
195+
.iter()
196+
.any(|other| other.project_id == desired.project_id);
197+
if replacements.len() == 1 && unambiguous {
198+
plan.project_updates.push(SharedInstanceProjectUpdate {
199+
current: plan.project_removals.remove(replacements[0]),
200+
desired,
201+
});
202+
} else {
203+
plan.project_additions.push(desired);
185204
}
186205
}
187206

@@ -474,7 +493,7 @@ async fn current_shared_instance_content(
474493
};
475494

476495
content.projects.insert(
477-
project_id.clone(),
496+
version_id.clone(),
478497
CurrentSharedInstanceProject {
479498
project_id,
480499
version_id,
@@ -509,7 +528,7 @@ async fn desired_shared_instance_content(
509528
))
510529
})?;
511530
content.projects.insert(
512-
version.project_id.clone(),
531+
version.id.clone(),
513532
DesiredSharedInstanceProject {
514533
project_id: version.project_id.clone(),
515534
version_id: version.id.clone(),

0 commit comments

Comments
 (0)