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 childDirectoryCount: Int
let fingerprint: String let fingerprint: String
var id: String { folderName } var id: String { "\(folderName)::\(fingerprint)" }
} }
nonisolated struct SourceSnapshot: Hashable, Sendable, Codable { nonisolated struct SourceSnapshot: Hashable, Sendable, Codable {

View File

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

View File

@ -443,6 +443,60 @@ struct World_Manager_for_MinecraftTests {
#expect(snapshots.map(\.folderName).contains("a/e/f/resourcepacks")) #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 { @Test func sourceLibraryAddSourceCandidatePreservesJavaAggregateProvider() async throws {
let fileManager = FileManager.default let fileManager = FileManager.default
let workingURL = fileManager.temporaryDirectory.appendingPathComponent(UUID().uuidString, isDirectory: true) let workingURL = fileManager.temporaryDirectory.appendingPathComponent(UUID().uuidString, isDirectory: true)