From 47d62a5d5116764729116f897238b7901aa8befb Mon Sep 17 00:00:00 2001 From: John Burwell Date: Fri, 29 May 2026 07:17:14 -0500 Subject: [PATCH] Extract source content index builder --- .../Services/SourceContentIndex.swift | 283 +++++++++++++++++ .../Services/SourceLibrary.swift | 293 +----------------- 2 files changed, 290 insertions(+), 286 deletions(-) create mode 100644 World Manager for Minecraft/Services/SourceContentIndex.swift diff --git a/World Manager for Minecraft/Services/SourceContentIndex.swift b/World Manager for Minecraft/Services/SourceContentIndex.swift new file mode 100644 index 0000000..7cea782 --- /dev/null +++ b/World Manager for Minecraft/Services/SourceContentIndex.swift @@ -0,0 +1,283 @@ +// +// SourceContentIndex.swift +// World Manager for Minecraft +// +// Created by OpenAI Codex on 2026-05-29. +// + +import Foundation + +struct SourceContentIndex { + let rawItems: [MinecraftContentItem] + let logicalPacks: [LogicalPack] + let logicalWorlds: [LogicalWorld] + let packInstances: [PackInstance] + let worldPackRelationships: [WorldPackRelationship] + let displayItems: [MinecraftContentItem] +} + +enum SourceContentIndexer { + static func buildIndex(for source: MinecraftSource) -> SourceContentIndex { + let rawItems = source.rawItems.sorted(by: WorldScanner.sortItems) + let rawItemsByID = Dictionary(uniqueKeysWithValues: rawItems.map { ($0.id, $0) }) + + let rawPacks = rawItems.filter { + $0.contentType == .behaviorPack || $0.contentType == .resourcePack + } + let rawWorlds = rawItems.filter { $0.contentType == .world } + + let packMetadataByItemID = Dictionary(uniqueKeysWithValues: rawPacks.map { item in + (item.id, packMetadata(for: item, sourceRootURL: source.folderURL)) + }) + + var chosenRepresentativeByIdentity: [PackIdentity: MinecraftContentItem] = [:] + var allPackItemsByIdentity: [PackIdentity: [MinecraftContentItem]] = [:] + + for item in rawPacks { + let metadata = packMetadataByItemID[item.id] ?? packMetadata(for: item, sourceRootURL: source.folderURL) + let identity = metadata.identity + allPackItemsByIdentity[identity, default: []].append(item) + + guard let existing = chosenRepresentativeByIdentity[identity] else { + chosenRepresentativeByIdentity[identity] = item + continue + } + + if shouldPreferPackItem(item, over: existing) { + chosenRepresentativeByIdentity[identity] = item + } + } + + let logicalPacks = allPackItemsByIdentity.keys.sorted { + let lhs = chosenRepresentativeByIdentity[$0]?.displayName ?? "" + let rhs = chosenRepresentativeByIdentity[$1]?.displayName ?? "" + let nameOrder = lhs.localizedStandardCompare(rhs) + if nameOrder != .orderedSame { + return nameOrder == .orderedAscending + } + + return $0.id.localizedStandardCompare($1.id) == .orderedAscending + }.compactMap { identity -> LogicalPack? in + guard + let representativeItem = chosenRepresentativeByIdentity[identity], + let instances = allPackItemsByIdentity[identity] + else { + return nil + } + + let metadata = packMetadataByItemID[representativeItem.id] + + return LogicalPack( + id: identity, + contentType: identity.type, + displayName: representativeItem.displayName, + uuid: metadata?.uuid, + version: metadata?.version, + representativeItemID: representativeItem.id, + instanceItemIDs: instances.map(\.id).sorted { + $0.path.localizedStandardCompare($1.path) == .orderedAscending + }, + isSuspicious: identity.isSuspicious + ) + } + + var packInstances: [PackInstance] = [] + for logicalPack in logicalPacks { + for itemID in logicalPack.instanceItemIDs { + guard let item = rawItemsByID[itemID] else { + continue + } + + packInstances.append( + PackInstance( + id: item.id, + itemID: item.id, + sourceID: source.id, + logicalPackID: logicalPack.id, + origin: packOrigin(for: item), + hostWorldItemID: hostWorldItemID(for: item, in: rawWorlds) + ) + ) + } + } + + let logicalPacksByID = Dictionary(uniqueKeysWithValues: logicalPacks.map { ($0.id, $0) }) + var worldRelationships: [WorldPackRelationship] = [] + var logicalWorlds: [LogicalWorld] = [] + + for world in rawWorlds { + var usedPackIDs = Set() + var unresolvedReferences: [ContentPackReference] = [] + + for reference in world.packReferences { + let referenceIdentity = PackIdentity( + type: reference.type, + uuid: reference.uuid, + version: reference.version, + fallbackName: reference.name, + fallbackLocationHint: world.folderName + ) + let resolvedID = logicalPacksByID[referenceIdentity]?.id + + if let resolvedID { + usedPackIDs.insert(resolvedID) + } else { + unresolvedReferences.append(reference) + } + + worldRelationships.append( + WorldPackRelationship( + worldItemID: world.id, + logicalPackID: resolvedID, + reference: reference + ) + ) + } + + logicalWorlds.append( + LogicalWorld( + id: world.id, + itemID: world.id, + usedPackIDs: usedPackIDs.sorted { + $0.id.localizedStandardCompare($1.id) == .orderedAscending + }, + unresolvedReferences: unresolvedReferences + ) + ) + } + + let sortedLogicalWorlds = logicalWorlds.sorted { + guard + let lhs = rawItemsByID[$0.itemID], + let rhs = rawItemsByID[$1.itemID] + else { + return $0.itemID.path.localizedStandardCompare($1.itemID.path) == .orderedAscending + } + + return WorldScanner.sortItems(lhs, rhs) + } + + let sortedPackInstances = packInstances.sorted { + $0.itemID.path.localizedStandardCompare($1.itemID.path) == .orderedAscending + } + + return SourceContentIndex( + rawItems: rawItems, + logicalPacks: logicalPacks, + logicalWorlds: sortedLogicalWorlds, + packInstances: sortedPackInstances, + worldPackRelationships: worldRelationships, + displayItems: buildDisplayItems( + from: rawItems, + logicalPacks: logicalPacks, + rawItemsByID: rawItemsByID + ) + ) + } + + private static func buildDisplayItems( + from rawItems: [MinecraftContentItem], + logicalPacks: [LogicalPack], + rawItemsByID: [URL: MinecraftContentItem] + ) -> [MinecraftContentItem] { + var normalizedItemIDs = Set() + var normalizedItems: [MinecraftContentItem] = [] + + for item in rawItems where item.contentType == .world { + guard normalizedItemIDs.insert(item.id).inserted else { + continue + } + + normalizedItems.append(item) + } + + for logicalPack in logicalPacks { + guard + let item = rawItemsByID[logicalPack.representativeItemID], + normalizedItemIDs.insert(item.id).inserted + else { + continue + } + + normalizedItems.append(item) + } + + for item in rawItems where item.contentType == .skinPack || item.contentType == .worldTemplate { + guard normalizedItemIDs.insert(item.id).inserted else { + continue + } + + normalizedItems.append(item) + } + + return normalizedItems + } + + private static func shouldPreferPackItem(_ candidate: MinecraftContentItem, over existing: MinecraftContentItem) -> Bool { + let candidateEmbedded = isEmbeddedWorldPack(candidate) + let existingEmbedded = isEmbeddedWorldPack(existing) + + if candidateEmbedded != existingEmbedded { + return !candidateEmbedded + } + + if candidate.metadataLoaded != existing.metadataLoaded { + return candidate.metadataLoaded + } + + if (candidate.iconURL != nil) != (existing.iconURL != nil) { + return candidate.iconURL != nil + } + + if candidate.previewLoaded != existing.previewLoaded { + return candidate.previewLoaded + } + + if candidate.modifiedDate != existing.modifiedDate { + return (candidate.modifiedDate ?? .distantPast) > (existing.modifiedDate ?? .distantPast) + } + + return candidate.folderURL.path.localizedStandardCompare(existing.folderURL.path) == .orderedAscending + } + + private static func packOrigin(for item: MinecraftContentItem) -> PackSource { + isEmbeddedWorldPack(item) ? .embeddedInWorld : .foundInCollection + } + + private static func isEmbeddedWorldPack(_ item: MinecraftContentItem) -> Bool { + item.folderURL.pathComponents.contains(MinecraftContentType.world.collectionFolderName) + } + + private static func hostWorldItemID(for packItem: MinecraftContentItem, in rawWorlds: [MinecraftContentItem]) -> URL? { + rawWorlds.first(where: { world in + packItem.folderURL.path.hasPrefix(world.folderURL.path + "/") + })?.id + } + + private static func packMetadata(for item: MinecraftContentItem, sourceRootURL: URL) -> IndexedPackMetadata { + let uuid = item.packUUID + let version = item.packVersion + + return IndexedPackMetadata( + uuid: uuid, + version: version, + identity: PackIdentity( + type: item.contentType, + uuid: uuid, + version: version, + fallbackName: item.displayName, + fallbackLocationHint: relativePathHint(for: item, sourceRootURL: sourceRootURL) + ) + ) + } + + private static func relativePathHint(for item: MinecraftContentItem, sourceRootURL: URL) -> String { + item.folderURL.path.replacingOccurrences(of: sourceRootURL.path + "/", with: "") + } +} + +private struct IndexedPackMetadata { + let uuid: String? + let version: String? + let identity: PackIdentity +} diff --git a/World Manager for Minecraft/Services/SourceLibrary.swift b/World Manager for Minecraft/Services/SourceLibrary.swift index 325d3fe..632e499 100644 --- a/World Manager for Minecraft/Services/SourceLibrary.swift +++ b/World Manager for Minecraft/Services/SourceLibrary.swift @@ -257,151 +257,13 @@ final class SourceLibrary: ObservableObject, SourceScanSessionHosting, SourcePer func rebuildNormalizedIndex(for sourceID: URL) { updateSource(sourceID) { source in - let rawItems = source.rawItems.sorted(by: WorldScanner.sortItems) - source.rawItems = rawItems - let rawItemsByID = Dictionary(uniqueKeysWithValues: rawItems.map { ($0.id, $0) }) - - let rawPacks = rawItems.filter { - $0.contentType == .behaviorPack || $0.contentType == .resourcePack - } - let rawWorlds = rawItems.filter { $0.contentType == .world } - - let packMetadataByItemID = Dictionary(uniqueKeysWithValues: rawPacks.map { item in - (item.id, packMetadata(for: item, sourceRootURL: source.folderURL)) - }) - - var chosenRepresentativeByIdentity: [PackIdentity: MinecraftContentItem] = [:] - var allPackItemsByIdentity: [PackIdentity: [MinecraftContentItem]] = [:] - - for item in rawPacks { - let metadata = packMetadataByItemID[item.id] ?? packMetadata(for: item, sourceRootURL: source.folderURL) - let identity = metadata.identity - allPackItemsByIdentity[identity, default: []].append(item) - - guard let existing = chosenRepresentativeByIdentity[identity] else { - chosenRepresentativeByIdentity[identity] = item - continue - } - - if shouldPreferPackItem(item, over: existing) { - chosenRepresentativeByIdentity[identity] = item - } - } - - let logicalPacks = allPackItemsByIdentity.keys.sorted { - let lhs = chosenRepresentativeByIdentity[$0]?.displayName ?? "" - let rhs = chosenRepresentativeByIdentity[$1]?.displayName ?? "" - let nameOrder = lhs.localizedStandardCompare(rhs) - if nameOrder != .orderedSame { - return nameOrder == .orderedAscending - } - - return $0.id.localizedStandardCompare($1.id) == .orderedAscending - }.compactMap { identity -> LogicalPack? in - guard - let representativeItem = chosenRepresentativeByIdentity[identity], - let instances = allPackItemsByIdentity[identity] - else { - return nil - } - - let metadata = packMetadataByItemID[representativeItem.id] - - return LogicalPack( - id: identity, - contentType: identity.type, - displayName: representativeItem.displayName, - uuid: metadata?.uuid, - version: metadata?.version, - representativeItemID: representativeItem.id, - instanceItemIDs: instances.map(\.id).sorted { $0.path.localizedStandardCompare($1.path) == .orderedAscending }, - isSuspicious: identity.isSuspicious - ) - } - - var packInstances: [PackInstance] = [] - for logicalPack in logicalPacks { - for itemID in logicalPack.instanceItemIDs { - guard let item = rawItemsByID[itemID] else { - continue - } - - packInstances.append( - PackInstance( - id: item.id, - itemID: item.id, - sourceID: sourceID, - logicalPackID: logicalPack.id, - origin: packOrigin(for: item), - hostWorldItemID: hostWorldItemID(for: item, in: rawWorlds) - ) - ) - } - } - - let logicalPacksByID = Dictionary(uniqueKeysWithValues: logicalPacks.map { ($0.id, $0) }) - var worldRelationships: [WorldPackRelationship] = [] - var logicalWorlds: [LogicalWorld] = [] - - for world in rawWorlds { - var usedPackIDs = Set() - var unresolvedReferences: [ContentPackReference] = [] - - for reference in world.packReferences { - let referenceIdentity = PackIdentity( - type: reference.type, - uuid: reference.uuid, - version: reference.version, - fallbackName: reference.name, - fallbackLocationHint: world.folderName - ) - let resolvedID = logicalPacksByID[referenceIdentity]?.id - - if let resolvedID { - usedPackIDs.insert(resolvedID) - } else { - unresolvedReferences.append(reference) - } - - worldRelationships.append( - WorldPackRelationship( - worldItemID: world.id, - logicalPackID: resolvedID, - reference: reference - ) - ) - } - - logicalWorlds.append( - LogicalWorld( - id: world.id, - itemID: world.id, - usedPackIDs: usedPackIDs.sorted { $0.id.localizedStandardCompare($1.id) == .orderedAscending }, - unresolvedReferences: unresolvedReferences - ) - ) - } - - source.logicalPacks = logicalPacks - source.logicalWorlds = logicalWorlds.sorted { - guard - let lhs = source.rawItem(withID: $0.itemID), - let rhs = source.rawItem(withID: $1.itemID) - else { - return $0.itemID.path.localizedStandardCompare($1.itemID.path) == .orderedAscending - } - - return WorldScanner.sortItems(lhs, rhs) - } - source.packInstances = packInstances.sorted { - $0.itemID.path.localizedStandardCompare($1.itemID.path) == .orderedAscending - } - source.worldPackRelationships = worldRelationships - source.displayItems = buildDisplayItems( - from: rawItems, - logicalPacks: logicalPacks, - rawItemsByID: rawItemsByID - ) + let index = SourceContentIndexer.buildIndex(for: source) + source.rawItems = index.rawItems + source.logicalPacks = index.logicalPacks + source.logicalWorlds = index.logicalWorlds + source.packInstances = index.packInstances + source.worldPackRelationships = index.worldPackRelationships + source.displayItems = index.displayItems } } @@ -545,44 +407,6 @@ final class SourceLibrary: ObservableObject, SourceScanSessionHosting, SourcePer sources.sort { $0.displayName.localizedStandardCompare($1.displayName) == .orderedAscending } } - private func buildDisplayItems( - from rawItems: [MinecraftContentItem], - logicalPacks: [LogicalPack], - rawItemsByID: [URL: MinecraftContentItem] - ) -> [MinecraftContentItem] { - var normalizedItemIDs = Set() - var normalizedItems: [MinecraftContentItem] = [] - - for item in rawItems where item.contentType == .world { - guard normalizedItemIDs.insert(item.id).inserted else { - continue - } - - normalizedItems.append(item) - } - - for logicalPack in logicalPacks { - guard - let item = rawItemsByID[logicalPack.representativeItemID], - normalizedItemIDs.insert(item.id).inserted - else { - continue - } - - normalizedItems.append(item) - } - - for item in rawItems where item.contentType == .skinPack || item.contentType == .worldTemplate { - guard normalizedItemIDs.insert(item.id).inserted else { - continue - } - - normalizedItems.append(item) - } - - return normalizedItems - } - func persistSourceIfAvailable(withID sourceID: URL) { SourcePersistenceCoordinator.persistSourceIfAvailable( withID: sourceID, @@ -655,10 +479,6 @@ final class SourceLibrary: ObservableObject, SourceScanSessionHosting, SourcePer ) } - private func isLogicalPackType(_ contentType: MinecraftContentType) -> Bool { - contentType == .behaviorPack || contentType == .resourcePack - } - @discardableResult func updateAvailability(for sourceID: URL, to newAvailability: SourceAvailability) -> (previous: SourceAvailability, becameAvailable: Bool) { let previousAvailability = source(withID: sourceID)?.availability ?? .unknown @@ -699,103 +519,4 @@ final class SourceLibrary: ObservableObject, SourceScanSessionHosting, SourcePer return diagnostic.localizedCaseInsensitiveContains("showing cached results") } - - private func shouldPreferPackItem(_ candidate: MinecraftContentItem, over existing: MinecraftContentItem) -> Bool { - let candidateEmbedded = isEmbeddedWorldPack(candidate) - let existingEmbedded = isEmbeddedWorldPack(existing) - - if candidateEmbedded != existingEmbedded { - return !candidateEmbedded - } - - if candidate.metadataLoaded != existing.metadataLoaded { - return candidate.metadataLoaded - } - - if (candidate.iconURL != nil) != (existing.iconURL != nil) { - return candidate.iconURL != nil - } - - if candidate.previewLoaded != existing.previewLoaded { - return candidate.previewLoaded - } - - if candidate.modifiedDate != existing.modifiedDate { - return (candidate.modifiedDate ?? .distantPast) > (existing.modifiedDate ?? .distantPast) - } - - return candidate.folderURL.path.localizedStandardCompare(existing.folderURL.path) == .orderedAscending - } - - private func packOrigin(for item: MinecraftContentItem) -> PackSource { - isEmbeddedWorldPack(item) ? .embeddedInWorld : .foundInCollection - } - - private func isEmbeddedWorldPack(_ item: MinecraftContentItem) -> Bool { - item.folderURL.pathComponents.contains(MinecraftContentType.world.collectionFolderName) - } - - private func hostWorldItemID(for packItem: MinecraftContentItem, in rawWorlds: [MinecraftContentItem]) -> URL? { - rawWorlds.first(where: { world in - packItem.folderURL.path.hasPrefix(world.folderURL.path + "/") - })?.id - } - - private func packMetadata(for item: MinecraftContentItem, sourceRootURL: URL) -> PackMetadata { - let uuid = item.packUUID - let version = item.packVersion - - return PackMetadata( - uuid: uuid, - version: version, - identity: PackIdentity( - type: item.contentType, - uuid: uuid, - version: version, - fallbackName: item.displayName, - fallbackLocationHint: relativePathHint(for: item, sourceRootURL: sourceRootURL) - ) - ) - } - - private func relativePathHint(for item: MinecraftContentItem, sourceRootURL: URL) -> String { - item.folderURL.path.replacingOccurrences(of: sourceRootURL.path + "/", with: "") - } } - - private func shouldPreferPackItem(_ candidate: MinecraftContentItem, over existing: MinecraftContentItem) -> Bool { - let candidateEmbedded = isEmbeddedWorldPack(candidate) - let existingEmbedded = isEmbeddedWorldPack(existing) - - if candidateEmbedded != existingEmbedded { - return !candidateEmbedded - } - - if candidate.metadataLoaded != existing.metadataLoaded { - return candidate.metadataLoaded - } - - if (candidate.iconURL != nil) != (existing.iconURL != nil) { - return candidate.iconURL != nil - } - - if candidate.previewLoaded != existing.previewLoaded { - return candidate.previewLoaded - } - - if candidate.modifiedDate != existing.modifiedDate { - return (candidate.modifiedDate ?? .distantPast) > (existing.modifiedDate ?? .distantPast) - } - - return candidate.folderURL.path.localizedStandardCompare(existing.folderURL.path) == .orderedAscending - } - - private func isEmbeddedWorldPack(_ item: MinecraftContentItem) -> Bool { - item.folderURL.pathComponents.contains(MinecraftContentType.world.collectionFolderName) - } - - private func hostWorldItemID(for packItem: MinecraftContentItem, in rawWorlds: [MinecraftContentItem]) -> URL? { - rawWorlds.first(where: { world in - packItem.folderURL.path.hasPrefix(world.folderURL.path + "/") - })?.id - }