diff --git a/World Manager for Minecraft/Services/SourceLibrary.swift b/World Manager for Minecraft/Services/SourceLibrary.swift index 6f189c7..a4ff122 100644 --- a/World Manager for Minecraft/Services/SourceLibrary.swift +++ b/World Manager for Minecraft/Services/SourceLibrary.swift @@ -334,7 +334,7 @@ final class SourceLibrary: ObservableObject { let accessMethod = sourceAccessMethod let discoveryTask = Task.detached(priority: .userInitiated) { do { - _ = try await accessMethod.discoverItems(for: source, mode: mode) { item in + try await accessMethod.discoverItems(for: source, mode: mode) { item in continuation.yield(item) } continuation.finish() @@ -630,46 +630,6 @@ final class SourceLibrary: ObservableObject { } } - private func handleEnrichedItem(_ enrichedItem: MinecraftContentItem, for sourceID: URL) { - var previousItem: MinecraftContentItem? - - updateSource(sourceID) { source in - guard let index = source.rawItems.firstIndex(where: { $0.id == enrichedItem.id }) else { - return - } - - previousItem = source.rawItems[index] - source.rawItems[index] = enrichedItem - source.indexedDetailCount += 1 - - if source.indexedDetailCount < source.indexedItemCount { - source.scanStatus = "Loaded details for \(source.indexedDetailCount) of \(source.indexedItemCount) items..." - } - - handleMetadataUpdate( - for: enrichedItem, - previousItem: previousItem, - in: &source, - sourceID: sourceID - ) - } - } - - private func handleSizedItem(_ sizedItem: MinecraftContentItem, for sourceID: URL) { - updateSource(sourceID) { source in - guard let index = source.rawItems.firstIndex(where: { $0.id == sizedItem.id }) else { - return - } - - source.rawItems[index].sizeBytes = sizedItem.sizeBytes - source.rawItems[index].sizeLoaded = sizedItem.sizeLoaded - - if source.isScanning { - source.scanStatus = "Calculating sizes for \(source.rawItems.filter(\.sizeLoaded).count) of \(source.indexedItemCount) items..." - } - } - } - private func rebuildNormalizedIndex(for sourceID: URL) { updateSource(sourceID) { source in let rawItems = source.rawItems.sorted(by: WorldScanner.sortItems) @@ -921,39 +881,6 @@ final class SourceLibrary: ObservableObject { return "Failed to scan device library." } - private func handleDiscoveredItem(_ item: MinecraftContentItem, in source: inout MinecraftSource, sourceID: URL) { - guard isLogicalPackType(item.contentType) else { - return - } - - let identity = packMetadata(for: item, sourceRootURL: source.folderURL).identity - refreshLogicalPack(identity: identity, in: &source, sourceID: sourceID) - } - - private func handleMetadataUpdate( - for item: MinecraftContentItem, - previousItem: MinecraftContentItem?, - in source: inout MinecraftSource, - sourceID: URL - ) { - if isLogicalPackType(item.contentType) { - let newIdentity = packMetadata(for: item, sourceRootURL: source.folderURL).identity - let previousIdentity = previousItem.map { packMetadata(for: $0, sourceRootURL: source.folderURL).identity } - - if let previousIdentity, previousIdentity != newIdentity { - refreshLogicalPack(identity: previousIdentity, in: &source, sourceID: sourceID) - } - - refreshLogicalPack(identity: newIdentity, in: &source, sourceID: sourceID) - refreshWorldRelationships(in: &source, filteringTo: item.contentType) - return - } - - if item.contentType == .world { - refreshWorldRelationship(for: item, in: &source) - } - } - private func updateSource(_ sourceID: URL, mutate: (inout MinecraftSource) -> Void) { guard let index = sources.firstIndex(where: { $0.id == sourceID }) else { return @@ -987,135 +914,6 @@ final class SourceLibrary: ObservableObject { } } - private func refreshLogicalPack(identity: PackIdentity, in source: inout MinecraftSource, sourceID: URL) { - let matchingItems = source.rawItems.filter { item in - guard isLogicalPackType(item.contentType) else { - return false - } - - return packMetadata(for: item, sourceRootURL: source.folderURL).identity == identity - } - - source.logicalPacks.removeAll { $0.id == identity } - source.packInstances.removeAll { $0.logicalPackID == identity } - - guard !matchingItems.isEmpty else { - return - } - - let representativeItem = matchingItems.reduce(matchingItems[0]) { current, candidate in - shouldPreferPackItem(candidate, over: current) ? candidate : current - } - let representativeMetadata = packMetadata(for: representativeItem, sourceRootURL: source.folderURL) - - source.logicalPacks.append( - LogicalPack( - id: identity, - contentType: identity.type, - displayName: representativeItem.displayName, - uuid: representativeMetadata.uuid, - version: representativeMetadata.version, - representativeItemID: representativeItem.id, - instanceItemIDs: matchingItems.map(\.id).sorted { - $0.path.localizedStandardCompare($1.path) == .orderedAscending - }, - isSuspicious: identity.isSuspicious - ) - ) - source.logicalPacks.sort { - let nameOrder = $0.displayName.localizedStandardCompare($1.displayName) - if nameOrder != .orderedSame { - return nameOrder == .orderedAscending - } - - return $0.id.id.localizedStandardCompare($1.id.id) == .orderedAscending - } - - let rawWorlds = source.rawItems.filter { $0.contentType == .world } - source.packInstances.append( - contentsOf: matchingItems.map { item in - PackInstance( - id: item.id, - itemID: item.id, - sourceID: sourceID, - logicalPackID: identity, - origin: packOrigin(for: item), - hostWorldItemID: hostWorldItemID(for: item, in: rawWorlds) - ) - } - ) - source.packInstances.sort { - $0.itemID.path.localizedStandardCompare($1.itemID.path) == .orderedAscending - } - } - - private func refreshWorldRelationships(in source: inout MinecraftSource, filteringTo type: MinecraftContentType? = nil) { - let worlds = source.rawItems.filter { $0.contentType == .world } - for world in worlds { - guard type == nil || world.packReferences.contains(where: { $0.type == type }) else { - continue - } - - refreshWorldRelationship(for: world, in: &source) - } - } - - private func refreshWorldRelationship(for world: MinecraftContentItem, in source: inout MinecraftSource) { - source.worldPackRelationships.removeAll { $0.worldItemID == world.id } - source.logicalWorlds.removeAll { $0.itemID == world.id } - - let logicalPacksByID = Dictionary(uniqueKeysWithValues: source.logicalPacks.map { ($0.id, $0) }) - var usedPackIDs = Set() - var unresolvedReferences: [ContentPackReference] = [] - var relationships: [WorldPackRelationship] = [] - - 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) - } - - relationships.append( - WorldPackRelationship( - worldItemID: world.id, - logicalPackID: resolvedID, - reference: reference - ) - ) - } - - source.worldPackRelationships.append(contentsOf: relationships) - source.logicalWorlds.append( - LogicalWorld( - id: world.id, - itemID: world.id, - usedPackIDs: usedPackIDs.sorted { $0.id.localizedStandardCompare($1.id) == .orderedAscending }, - unresolvedReferences: unresolvedReferences - ) - ) - let rawItemsByID = Dictionary(uniqueKeysWithValues: source.rawItems.map { ($0.id, $0) }) - source.logicalWorlds.sort { - 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) - } - } - private func runConnectedDeviceRefreshLoop() async { while !Task.isCancelled && !isShuttingDown { if hasActiveConnectedDeviceScan { diff --git a/World Manager for Minecraft/SourceAccess/ConnectedDevice/AppleMobileDevice/AppleMobileDeviceSourceAccess.swift b/World Manager for Minecraft/SourceAccess/ConnectedDevice/AppleMobileDevice/AppleMobileDeviceSourceAccess.swift index cabac0f..29c41e6 100644 --- a/World Manager for Minecraft/SourceAccess/ConnectedDevice/AppleMobileDevice/AppleMobileDeviceSourceAccess.swift +++ b/World Manager for Minecraft/SourceAccess/ConnectedDevice/AppleMobileDevice/AppleMobileDeviceSourceAccess.swift @@ -103,7 +103,7 @@ struct AppleMobileDeviceSourceAccess: ConnectedDeviceSourceAccessMethod { for source: MinecraftSource, mode: SourceDiscoveryMode, onDiscovered: @escaping @Sendable (MinecraftContentItem) -> Void - ) async throws -> [MinecraftContentItem] { + ) async throws { _ = mode guard case .connectedDevice(_, let container) = source.origin else { throw SourceAccessError.accessFailed( @@ -141,8 +141,6 @@ struct AppleMobileDeviceSourceAccess: ConnectedDeviceSourceAccessMethod { for item in items { onDiscovered(item) } - - return items } nonisolated func enrich(_ item: MinecraftContentItem, for source: MinecraftSource) async -> MinecraftContentItem { diff --git a/World Manager for Minecraft/SourceAccess/Core/SourceAccessCoordinator.swift b/World Manager for Minecraft/SourceAccess/Core/SourceAccessCoordinator.swift index 799e4fe..9b560db 100644 --- a/World Manager for Minecraft/SourceAccess/Core/SourceAccessCoordinator.swift +++ b/World Manager for Minecraft/SourceAccess/Core/SourceAccessCoordinator.swift @@ -20,7 +20,7 @@ protocol SourceAccessMethod: Sendable { for source: MinecraftSource, mode: SourceDiscoveryMode, onDiscovered: @escaping @Sendable (MinecraftContentItem) -> Void - ) async throws -> [MinecraftContentItem] + ) async throws nonisolated func enrich(_ item: MinecraftContentItem, for source: MinecraftSource) async -> MinecraftContentItem nonisolated func loadPreviewAssets(for item: MinecraftContentItem, in source: MinecraftSource) async -> MinecraftContentItem nonisolated func loadPreviewAssets(for items: [MinecraftContentItem], in source: MinecraftSource) async -> [MinecraftContentItem] @@ -54,11 +54,10 @@ extension SourceAccessMethod { for source: MinecraftSource, mode: SourceDiscoveryMode, onDiscovered: @escaping @Sendable (MinecraftContentItem) -> Void - ) async throws -> [MinecraftContentItem] { + ) async throws { _ = source _ = mode _ = onDiscovered - return [] } nonisolated func enrich(_ item: MinecraftContentItem, for source: MinecraftSource) async -> MinecraftContentItem { @@ -157,8 +156,8 @@ struct SourceAccessCoordinator: SourceAccessMethod { for source: MinecraftSource, mode: SourceDiscoveryMode, onDiscovered: @escaping @Sendable (MinecraftContentItem) -> Void - ) async throws -> [MinecraftContentItem] { - return try await accessMethod(for: source).discoverItems( + ) async throws { + try await accessMethod(for: source).discoverItems( for: source, mode: mode, onDiscovered: onDiscovered diff --git a/World Manager for Minecraft/SourceAccess/LocalFolder/LocalFolderSourceAccess.swift b/World Manager for Minecraft/SourceAccess/LocalFolder/LocalFolderSourceAccess.swift index 7e9c9f2..a2f357a 100644 --- a/World Manager for Minecraft/SourceAccess/LocalFolder/LocalFolderSourceAccess.swift +++ b/World Manager for Minecraft/SourceAccess/LocalFolder/LocalFolderSourceAccess.swift @@ -48,7 +48,7 @@ struct LocalFolderSourceAccess: SourceAccessMethod { for source: MinecraftSource, mode: SourceDiscoveryMode, onDiscovered: @escaping @Sendable (MinecraftContentItem) -> Void - ) async throws -> [MinecraftContentItem] { + ) async throws { guard case .localFolder(let bookmarkData) = source.origin else { throw SourceAccessError.accessFailed( reason: "No local-folder access method is configured for this source type." @@ -83,15 +83,16 @@ struct LocalFolderSourceAccess: SourceAccessMethod { if case .reconcile = mode, let snapshot = source.snapshot { - return try discoverItemsByReconcilingCache( + try discoverItemsByReconcilingCache( for: source, snapshot: snapshot, resolvedURL: resolvedURL, onDiscovered: onDiscovered ) + return } - return try WorldScanner.discoverItems(in: resolvedURL, onDiscovered: onDiscovered) + _ = try WorldScanner.discoverItems(in: resolvedURL, onDiscovered: onDiscovered) } nonisolated func enrich(_ item: MinecraftContentItem, for source: MinecraftSource) async -> MinecraftContentItem { @@ -137,7 +138,7 @@ struct LocalFolderSourceAccess: SourceAccessMethod { snapshot: SourceSnapshot, resolvedURL: URL, onDiscovered: @escaping @Sendable (MinecraftContentItem) -> Void - ) throws -> [MinecraftContentItem] { + ) throws { let currentCollections = Dictionary( uniqueKeysWithValues: WorldScanner.collectionSnapshots(in: resolvedURL).map { ($0.folderName, $0) } ) @@ -182,7 +183,6 @@ struct LocalFolderSourceAccess: SourceAccessMethod { for item in reconciledItems { onDiscovered(item) } - return reconciledItems } nonisolated private func topLevelCollectionName(for item: MinecraftContentItem, sourceRootURL: URL) -> String? {