From 3a30b943690a0932e577c999666d2eee09d1bd6f Mon Sep 17 00:00:00 2001 From: John Burwell Date: Thu, 28 May 2026 22:41:11 -0500 Subject: [PATCH] Extract connected device source helpers --- .../Services/SourceConnectedDevices.swift | 220 +++++++++++++++++ .../Services/SourceLibrary.swift | 228 ++---------------- 2 files changed, 244 insertions(+), 204 deletions(-) create mode 100644 World Manager for Minecraft/Services/SourceConnectedDevices.swift diff --git a/World Manager for Minecraft/Services/SourceConnectedDevices.swift b/World Manager for Minecraft/Services/SourceConnectedDevices.swift new file mode 100644 index 0000000..a8fe90d --- /dev/null +++ b/World Manager for Minecraft/Services/SourceConnectedDevices.swift @@ -0,0 +1,220 @@ +// +// SourceConnectedDevices.swift +// World Manager for Minecraft +// +// Created by OpenAI Codex on 2026-05-28. +// + +import Foundation + +struct ConnectedDeviceSidebarEntry: Identifiable, Hashable { + let device: ConnectedDevice + let containers: [DeviceAppContainer] + let matchedSourceID: URL? + let discoveryErrorDescription: String? + + var id: String { device.id } + + var minecraftContainer: DeviceAppContainer? { + containers.first(where: { $0.appID == "com.mojang.minecraftpe" }) + ?? containers.first(where: { $0.minecraftFolderRelativePath != nil }) + } + + var hasMinecraftContainer: Bool { + minecraftContainer != nil + } +} + +struct CachedConnectedDeviceDiscovery { + let device: ConnectedDevice + let containers: [DeviceAppContainer] + let discoveryErrorDescription: String? + let refreshedAt: Date +} + +struct ConnectedDeviceRefreshContext { + let existingSources: [MinecraftSource] + let sourceFactory: ConnectedDeviceSourceFactory + + func knownSourceIDs(for device: ConnectedDevice) -> [URL] { + existingSources.compactMap { source -> URL? in + guard case .connectedDevice(let expectedDevice, _) = source.origin else { + return nil + } + + return expectedDevice.udid == device.udid ? source.id : nil + } + } + + func hasKnownSource(for device: ConnectedDevice) -> Bool { + !knownSourceIDs(for: device).isEmpty + } + + func matchingSourceID( + for device: ConnectedDevice, + containers: [DeviceAppContainer] + ) -> URL? { + for container in containers { + let sourceID = sourceFactory.makeSourceIdentifier( + device: device, + container: container + ) + if existingSources.contains(where: { $0.id == sourceID }) { + return sourceID + } + } + + return nil + } +} + +enum ConnectedDeviceDiscoveryCachePolicy { + private static let usbCacheTTL: TimeInterval = 60.0 + private static let networkCacheTTL: TimeInterval = 180.0 + + static func cachedDiscovery( + for device: ConnectedDevice, + cache: [String: CachedConnectedDeviceDiscovery], + isActivelyScanning: Bool, + now: Date = Date() + ) -> CachedConnectedDeviceDiscovery? { + guard let cachedDiscovery = cache[device.udid] else { + return nil + } + + if isActivelyScanning { + return cachedDiscovery + } + + let age = now.timeIntervalSince(cachedDiscovery.refreshedAt) + guard age <= discoveryCacheTTL(for: device) else { + return nil + } + + guard cachedDiscovery.device.connection == device.connection, + cachedDiscovery.device.trustState == device.trustState, + cachedDiscovery.device.name == device.name else { + return nil + } + + return cachedDiscovery + } + + static func cacheDiscovery( + for device: ConnectedDevice, + containers: [DeviceAppContainer], + discoveryErrorDescription: String?, + now: Date = Date() + ) -> CachedConnectedDeviceDiscovery { + CachedConnectedDeviceDiscovery( + device: device, + containers: containers, + discoveryErrorDescription: discoveryErrorDescription, + refreshedAt: now + ) + } + + private static func discoveryCacheTTL(for device: ConnectedDevice) -> TimeInterval { + switch device.connection { + case .usb: + return usbCacheTTL + case .network: + return networkCacheTTL + } + } +} + +enum ConnectedDeviceSourcePolicy { + static func availability(for device: ConnectedDevice, hasMinecraftContainer: Bool) -> SourceAvailability { + guard hasMinecraftContainer else { + return .unavailable + } + + switch device.trustState { + case .trusted: + return .available + case .locked, .untrusted: + return .limited + case .unavailable: + return .disconnected + } + } + + static func preferredDeviceName( + currentName: String, + fallbackDeviceName: String, + fallbackDisplayName: String + ) -> String { + if let sanitizedCurrentName = sanitizedDeviceName(currentName) { + return sanitizedCurrentName + } + + if let sanitizedFallbackName = sanitizedDeviceName(fallbackDeviceName) { + return sanitizedFallbackName + } + + if let displayNamePrefix = fallbackDisplayName.components(separatedBy: " • ").first, + let sanitizedDisplayName = sanitizedDeviceName(displayNamePrefix) { + return sanitizedDisplayName + } + + return "Unknown Device" + } + + static func sanitizedDeviceName(_ candidate: String) -> String? { + let trimmedCandidate = candidate.trimmingCharacters(in: .whitespacesAndNewlines) + guard !trimmedCandidate.isEmpty else { + return nil + } + + let normalizedCandidate = trimmedCandidate.lowercased() + guard normalizedCandidate != "unknown device", + normalizedCandidate != "unknown device..." else { + return nil + } + + return trimmedCandidate + } + + static func shouldRefreshSource(_ source: MinecraftSource) -> Bool { + guard !source.isScanning else { + return false + } + + return hasRefreshDebt(source) + } + + static func shouldRefreshSourceOnReconnect(_ source: MinecraftSource, now: Date = Date()) -> Bool { + guard !source.isScanning else { + return false + } + + if hasRefreshDebt(source) { + return true + } + + guard let lastScanDate = source.lastScanDate else { + return true + } + + let reconnectGracePeriod: TimeInterval = 5 * 60 + if now.timeIntervalSince(lastScanDate) < reconnectGracePeriod { + return false + } + + return shouldRefreshSource(source) + } + + static func hasRefreshDebt(_ source: MinecraftSource) -> Bool { + guard source.origin.kind == .connectedDevice else { + return false + } + + guard !source.rawItems.isEmpty else { + return true + } + + let itemCount = source.rawItems.count + return source.previewLoadedCount < itemCount || source.sizeLoadedCount < itemCount + } +} diff --git a/World Manager for Minecraft/Services/SourceLibrary.swift b/World Manager for Minecraft/Services/SourceLibrary.swift index 053c80c..d8cb14b 100644 --- a/World Manager for Minecraft/Services/SourceLibrary.swift +++ b/World Manager for Minecraft/Services/SourceLibrary.swift @@ -9,31 +9,6 @@ import Combine import Foundation import OSLog -struct ConnectedDeviceSidebarEntry: Identifiable, Hashable { - let device: ConnectedDevice - let containers: [DeviceAppContainer] - let matchedSourceID: URL? - let discoveryErrorDescription: String? - - var id: String { device.id } - - var minecraftContainer: DeviceAppContainer? { - containers.first(where: { $0.appID == "com.mojang.minecraftpe" }) - ?? containers.first(where: { $0.minecraftFolderRelativePath != nil }) - } - - var hasMinecraftContainer: Bool { - minecraftContainer != nil - } -} - -private struct CachedConnectedDeviceDiscovery { - let device: ConnectedDevice - let containers: [DeviceAppContainer] - let discoveryErrorDescription: String? - let refreshedAt: Date -} - @MainActor final class SourceLibrary: ObservableObject { private static let enrichmentWorkerCount = 4 @@ -45,8 +20,6 @@ final class SourceLibrary: ObservableObject { private static let connectedDeviceRefreshIntervalWhileScanning: TimeInterval = 5.0 private static let usbConnectedDeviceAutoRefreshInterval: TimeInterval = 45.0 private static let networkConnectedDeviceAutoRefreshInterval: TimeInterval = 120.0 - private static let usbConnectedDeviceDiscoveryCacheTTL: TimeInterval = 60.0 - private static let networkConnectedDeviceDiscoveryCacheTTL: TimeInterval = 180.0 private static let performanceLogger = Logger( subsystem: Bundle.main.bundleIdentifier ?? "WorldManagerForMinecraft", category: "ConnectedDevicePerformance" @@ -955,6 +928,10 @@ final class SourceLibrary: ObservableObject { var entries: [ConnectedDeviceSidebarEntry] = [] var matchedSourceIDs = Set() + let refreshContext = ConnectedDeviceRefreshContext( + existingSources: sources, + sourceFactory: connectedDeviceSourceFactory + ) let activeScanningDeviceUDIDs = Set( sources.compactMap { source -> String? in guard source.isScanning, case .connectedDevice(let device, _) = source.origin else { @@ -968,7 +945,7 @@ final class SourceLibrary: ObservableObject { cachedDeviceDiscoveryByUDID = cachedDeviceDiscoveryByUDID.filter { currentDeviceUDIDs.contains($0.key) } for device in devices { - let knownSourceIDs = knownConnectedDeviceSourceIDs(for: device) + let knownSourceIDs = refreshContext.knownSourceIDs(for: device) if !knownSourceIDs.isEmpty { matchedSourceIDs.formUnion(knownSourceIDs) let cachedContainers = cachedDeviceDiscoveryByUDID[device.udid]?.containers ?? [] @@ -985,7 +962,11 @@ final class SourceLibrary: ObservableObject { let containers: [DeviceAppContainer] let discoveryErrorDescription: String? - if let cachedDiscovery = cachedDiscovery(for: device, isActivelyScanning: activeScanningDeviceUDIDs.contains(device.udid)) { + if let cachedDiscovery = ConnectedDeviceDiscoveryCachePolicy.cachedDiscovery( + for: device, + cache: cachedDeviceDiscoveryByUDID, + isActivelyScanning: activeScanningDeviceUDIDs.contains(device.udid) + ) { containers = cachedDiscovery.containers discoveryErrorDescription = cachedDiscovery.discoveryErrorDescription } else { @@ -1023,10 +1004,7 @@ final class SourceLibrary: ObservableObject { } } - let matchedSourceID = matchingConnectedDeviceSourceID( - device: device, - containers: containers - ) + let matchedSourceID = refreshContext.matchingSourceID(for: device, containers: containers) if let matchedSourceID { matchedSourceIDs.insert(matchedSourceID) @@ -1039,7 +1017,7 @@ final class SourceLibrary: ObservableObject { let shouldDisplayEntry = matchedSourceID == nil - && !hasKnownConnectedDeviceSource(for: device) + && !refreshContext.hasKnownSource(for: device) && (!containers.isEmpty || device.trustState != .trusted) if shouldDisplayEntry { @@ -1075,88 +1053,27 @@ final class SourceLibrary: ObservableObject { lastMatchedConnectedSourceIDs = matchedSourceIDs } - private func cachedDiscovery(for device: ConnectedDevice, isActivelyScanning: Bool) -> CachedConnectedDeviceDiscovery? { - guard let cachedDiscovery = cachedDeviceDiscoveryByUDID[device.udid] else { - return nil - } - - if isActivelyScanning { - return cachedDiscovery - } - - let age = Date().timeIntervalSince(cachedDiscovery.refreshedAt) - guard age <= discoveryCacheTTL(for: device) else { - return nil - } - - guard cachedDiscovery.device.connection == device.connection, - cachedDiscovery.device.trustState == device.trustState, - cachedDiscovery.device.name == device.name else { - return nil - } - - return cachedDiscovery - } - - private func discoveryCacheTTL(for device: ConnectedDevice) -> TimeInterval { - switch device.connection { - case .usb: - return Self.usbConnectedDeviceDiscoveryCacheTTL - case .network: - return Self.networkConnectedDeviceDiscoveryCacheTTL - } - } - private func cacheDeviceDiscovery( device: ConnectedDevice, containers: [DeviceAppContainer], discoveryErrorDescription: String? ) { - cachedDeviceDiscoveryByUDID[device.udid] = CachedConnectedDeviceDiscovery( - device: device, + cachedDeviceDiscoveryByUDID[device.udid] = ConnectedDeviceDiscoveryCachePolicy.cacheDiscovery( + for: device, containers: containers, - discoveryErrorDescription: discoveryErrorDescription, - refreshedAt: Date() + discoveryErrorDescription: discoveryErrorDescription ) } - private func matchingConnectedDeviceSourceID( - device: ConnectedDevice, - containers: [DeviceAppContainer] - ) -> URL? { - for container in containers { - let sourceID = connectedDeviceSourceFactory.makeSourceIdentifier( - device: device, - container: container - ) - if sources.contains(where: { $0.id == sourceID }) { - return sourceID - } - } - - return nil - } - - private func knownConnectedDeviceSourceIDs(for device: ConnectedDevice) -> [URL] { - sources.compactMap { source -> URL? in - guard case .connectedDevice(let expectedDevice, _) = source.origin else { - return nil - } - - return expectedDevice.udid == device.udid ? source.id : nil - } - } - - private func hasKnownConnectedDeviceSource(for device: ConnectedDevice) -> Bool { - !knownConnectedDeviceSourceIDs(for: device).isEmpty - } - private func refreshMatchedConnectedDeviceSource( sourceID: URL, device: ConnectedDevice, containers: [DeviceAppContainer] ) { - let nextAvailability = availability(for: device, hasMinecraftContainer: true) + let nextAvailability = ConnectedDeviceSourcePolicy.availability( + for: device, + hasMinecraftContainer: true + ) updateSource(sourceID) { source in guard case .connectedDevice(let previousDevice, let previousContainer) = source.origin else { return @@ -1167,7 +1084,7 @@ final class SourceLibrary: ObservableObject { }) ?? previousContainer var resolvedDevice = device - resolvedDevice.name = preferredConnectedDeviceName( + resolvedDevice.name = ConnectedDeviceSourcePolicy.preferredDeviceName( currentName: device.name, fallbackDeviceName: previousDevice.name, fallbackDisplayName: source.displayName @@ -1187,7 +1104,7 @@ final class SourceLibrary: ObservableObject { } if transition.becameAvailable { - if shouldRefreshConnectedDeviceOnReconnect(source, device: device) { + if ConnectedDeviceSourcePolicy.shouldRefreshSourceOnReconnect(source) { queueAutomaticSync( for: sourceID, reason: source.hasCachedContent @@ -1198,7 +1115,7 @@ final class SourceLibrary: ObservableObject { return } - if shouldRefreshConnectedDeviceSource(source, device: device) { + if ConnectedDeviceSourcePolicy.shouldRefreshSource(source) { queueAutomaticSync(for: sourceID, reason: "Refreshing device library...") } } @@ -1215,57 +1132,6 @@ final class SourceLibrary: ObservableObject { } } - private func availability(for device: ConnectedDevice, hasMinecraftContainer: Bool) -> SourceAvailability { - guard hasMinecraftContainer else { - return .unavailable - } - - switch device.trustState { - case .trusted: - return .available - case .locked, .untrusted: - return .limited - case .unavailable: - return .disconnected - } - } - - private func preferredConnectedDeviceName( - currentName: String, - fallbackDeviceName: String, - fallbackDisplayName: String - ) -> String { - if let sanitizedCurrentName = sanitizedConnectedDeviceName(currentName) { - return sanitizedCurrentName - } - - if let sanitizedFallbackName = sanitizedConnectedDeviceName(fallbackDeviceName) { - return sanitizedFallbackName - } - - if let displayNamePrefix = fallbackDisplayName.components(separatedBy: " • ").first, - let sanitizedDisplayName = sanitizedConnectedDeviceName(displayNamePrefix) { - return sanitizedDisplayName - } - - return "Unknown Device" - } - - private func sanitizedConnectedDeviceName(_ candidate: String) -> String? { - let trimmedCandidate = candidate.trimmingCharacters(in: .whitespacesAndNewlines) - guard !trimmedCandidate.isEmpty else { - return nil - } - - let normalizedCandidate = trimmedCandidate.lowercased() - guard normalizedCandidate != "unknown device", - normalizedCandidate != "unknown device..." else { - return nil - } - - return trimmedCandidate - } - private func restorePersistedSources() async { defer { isRestoringPersistedSources = false @@ -1289,14 +1155,14 @@ final class SourceLibrary: ObservableObject { ) if case .connectedDevice(let device, let container) = source.origin { var repairedDevice = device - repairedDevice.name = preferredConnectedDeviceName( + repairedDevice.name = ConnectedDeviceSourcePolicy.preferredDeviceName( currentName: device.name, fallbackDeviceName: "", fallbackDisplayName: record.displayName ) source.origin = .connectedDevice(device: repairedDevice, container: container) let persistedDeviceName = record.displayName.components(separatedBy: " • ").first ?? record.displayName - if sanitizedConnectedDeviceName(persistedDeviceName) == nil { + if ConnectedDeviceSourcePolicy.sanitizedDeviceName(persistedDeviceName) == nil { source.displayName = connectedDeviceSourceFactory.displayName( for: repairedDevice, container: container @@ -1654,52 +1520,6 @@ final class SourceLibrary: ObservableObject { return diagnostic.localizedCaseInsensitiveContains("showing cached results") } - private func shouldRefreshConnectedDeviceSource(_ source: MinecraftSource, device: ConnectedDevice) -> Bool { - guard !source.isScanning else { - return false - } - - if connectedDeviceSourceHasRefreshDebt(source) { - return true - } - _ = device - return false - } - - private func shouldRefreshConnectedDeviceOnReconnect(_ source: MinecraftSource, device: ConnectedDevice) -> Bool { - guard !source.isScanning else { - return false - } - - if connectedDeviceSourceHasRefreshDebt(source) { - return true - } - - guard let lastScanDate = source.lastScanDate else { - return true - } - - let reconnectGracePeriod: TimeInterval = 5 * 60 - if Date().timeIntervalSince(lastScanDate) < reconnectGracePeriod { - return false - } - - return shouldRefreshConnectedDeviceSource(source, device: device) - } - - private func connectedDeviceSourceHasRefreshDebt(_ source: MinecraftSource) -> Bool { - guard source.origin.kind == .connectedDevice else { - return false - } - - guard !source.rawItems.isEmpty else { - return true - } - - let itemCount = source.rawItems.count - return source.previewLoadedCount < itemCount || source.sizeLoadedCount < itemCount - } - private func shouldPreferPackItem(_ candidate: MinecraftContentItem, over existing: MinecraftContentItem) -> Bool { let candidateEmbedded = isEmbeddedWorldPack(candidate) let existingEmbedded = isEmbeddedWorldPack(existing)