Handle duplicate Java collection snapshots

This commit is contained in:
John Burwell 2026-06-02 19:22:32 -05:00
parent 9ec6b905bc
commit f5dfec00a3
3 changed files with 62 additions and 10 deletions

View File

@ -152,7 +152,7 @@ nonisolated struct CollectionSnapshot: Identifiable, Hashable, Sendable, Codable
let childDirectoryCount: Int
let fingerprint: String
var id: String { folderName }
var id: String { "\(folderName)::\(fingerprint)" }
}
nonisolated struct SourceSnapshot: Hashable, Sendable, Codable {

View File

@ -214,20 +214,18 @@ enum SourceRestoration {
_ currentCollections: [CollectionSnapshot],
persistedCollections: [CollectionSnapshot]
) -> Bool {
let currentCollectionsByName = Dictionary(
uniqueKeysWithValues: currentCollections.map { ($0.folderName, $0) }
)
let persistedCollectionsByName = Dictionary(
uniqueKeysWithValues: persistedCollections.map { ($0.folderName, $0) }
)
let currentCollectionsByName = Dictionary(grouping: currentCollections, by: \.folderName)
.mapValues { $0.map(\.fingerprint).sorted() }
let persistedCollectionsByName = Dictionary(grouping: persistedCollections, by: \.folderName)
.mapValues { $0.map(\.fingerprint).sorted() }
if currentCollectionsByName.count != persistedCollectionsByName.count {
return true
}
for (folderName, persistedCollection) in persistedCollectionsByName {
guard let currentCollection = currentCollectionsByName[folderName],
currentCollection.fingerprint == persistedCollection.fingerprint else {
for (folderName, persistedFingerprints) in persistedCollectionsByName {
guard let currentFingerprints = currentCollectionsByName[folderName],
currentFingerprints == persistedFingerprints else {
return true
}
}

View File

@ -443,6 +443,60 @@ struct World_Manager_for_MinecraftTests {
#expect(snapshots.map(\.folderName).contains("a/e/f/resourcepacks"))
}
@Test func sourceRestorationComparesDuplicateCollectionNamesWithoutCrashing() async throws {
let fileManager = FileManager.default
let workingURL = fileManager.temporaryDirectory.appendingPathComponent(UUID().uuidString, isDirectory: true)
defer { try? fileManager.removeItem(at: workingURL) }
try fileManager.createDirectory(at: workingURL, withIntermediateDirectories: true)
let collectionSnapshots = [
CollectionSnapshot(
folderName: "mods",
modifiedDate: Date(timeIntervalSince1970: 100),
childDirectoryCount: 1,
fingerprint: "a/b/c/mods::1::100"
),
CollectionSnapshot(
folderName: "mods",
modifiedDate: Date(timeIntervalSince1970: 200),
childDirectoryCount: 2,
fingerprint: "x/y/z/mods::2::200"
)
]
var source = MinecraftSource(
folderURL: workingURL,
origin: .javaLocalFolder(bookmarkData: nil),
accessDescriptor: SourceAccessDescriptor(
accessorIdentifier: JavaLocalFolderSourceAccess().accessorIdentifier,
kind: .localFolder,
refreshStrategy: .eagerFullScan
),
availability: .available
)
source.edition = .java
source.snapshot = SourceSnapshot(
sourceID: workingURL,
rootModifiedDate: nil,
collectionSnapshots: collectionSnapshots,
itemSnapshots: []
)
#expect(SourceRestoration.needsReconcile(source) { _, _ in collectionSnapshots } == false)
#expect(
SourceRestoration.needsReconcile(source) { _, _ in
[
collectionSnapshots[0],
CollectionSnapshot(
folderName: "mods",
modifiedDate: Date(timeIntervalSince1970: 300),
childDirectoryCount: 3,
fingerprint: "x/y/z/mods::3::300"
)
]
} == true
)
}
@Test func sourceLibraryAddSourceCandidatePreservesJavaAggregateProvider() async throws {
let fileManager = FileManager.default
let workingURL = fileManager.temporaryDirectory.appendingPathComponent(UUID().uuidString, isDirectory: true)