From 5baf25f6016fc5efd07e79903f33b762d73ce48d Mon Sep 17 00:00:00 2001 From: John Burwell Date: Fri, 29 May 2026 07:12:00 -0500 Subject: [PATCH] Extract local source runtime orchestration --- .../Services/SourceLibrary.swift | 78 ++----------- .../Services/SourceLocalRuntime.swift | 105 ++++++++++++++++++ 2 files changed, 113 insertions(+), 70 deletions(-) create mode 100644 World Manager for Minecraft/Services/SourceLocalRuntime.swift diff --git a/World Manager for Minecraft/Services/SourceLibrary.swift b/World Manager for Minecraft/Services/SourceLibrary.swift index a550910..325d3fe 100644 --- a/World Manager for Minecraft/Services/SourceLibrary.swift +++ b/World Manager for Minecraft/Services/SourceLibrary.swift @@ -10,7 +10,7 @@ import Foundation import OSLog @MainActor -final class SourceLibrary: ObservableObject, SourceScanSessionHosting, SourcePersistenceHosting, ConnectedDeviceRuntimeHosting { +final class SourceLibrary: ObservableObject, SourceScanSessionHosting, SourcePersistenceHosting, ConnectedDeviceRuntimeHosting, LocalSourceRuntimeHosting { private static let enrichmentWorkerCount = 4 private static let sizeWorkerCount = 2 private static let minimumVisibleScanDuration: TimeInterval = 0.8 @@ -226,7 +226,7 @@ final class SourceLibrary: ObservableObject, SourceScanSessionHosting, SourcePer scanTasks[sourceID] = task } - private var hasActiveScan: Bool { + var hasActiveScan: Bool { sources.contains(where: \.isScanning) } @@ -477,77 +477,15 @@ final class SourceLibrary: ObservableObject, SourceScanSessionHosting, SourcePer } private func runLocalSourceRefreshLoop() async { - while !Task.isCancelled && !isShuttingDown { - if hasActiveScan { - do { - try await Task.sleep(for: .seconds(Self.localSourceRefreshInterval)) - } catch { - return - } - continue - } - - await refreshLocalSources() - - do { - try await Task.sleep(for: .seconds(Self.localSourceRefreshInterval)) - } catch { - return - } - } + await LocalSourceRuntime.runRefreshLoop( + on: self, + refreshInterval: Self.localSourceRefreshInterval, + accessMethod: sourceAccessMethod + ) } func refreshLocalSources() async { - guard !hasActiveScan else { - return - } - - let localSourceIDs = sources - .filter { $0.origin.kind == .localFolder } - .map(\.id) - - for sourceID in localSourceIDs { - guard !Task.isCancelled, !isShuttingDown else { - return - } - - guard let currentSource = source(withID: sourceID) else { - continue - } - - let availability = await sourceAccessMethod.availability(for: currentSource) - let transition = updateAvailability(for: sourceID, to: availability) - - guard let refreshedSource = source(withID: sourceID) else { - continue - } - - if transition.becameAvailable { - queueAutomaticSync( - for: sourceID, - reason: refreshedSource.hasCachedContent - ? "Folder available. Refreshing cached library..." - : "Folder available. Scanning Minecraft library..." - ) - continue - } - - guard refreshedSource.availability == SourceAvailability.available, !refreshedSource.isScanning else { - continue - } - - if SourceRestoration.needsReconcile( - refreshedSource, - currentCollectionSnapshots: currentCollectionSnapshots(for:) - ) { - queueAutomaticSync( - for: sourceID, - reason: refreshedSource.hasCachedContent - ? "Detected changes. Refreshing cached library..." - : "Scanning Minecraft library..." - ) - } - } + await LocalSourceRuntime.refreshSources(on: self, using: sourceAccessMethod) } func refreshConnectedDevices() async { diff --git a/World Manager for Minecraft/Services/SourceLocalRuntime.swift b/World Manager for Minecraft/Services/SourceLocalRuntime.swift new file mode 100644 index 0000000..551c3df --- /dev/null +++ b/World Manager for Minecraft/Services/SourceLocalRuntime.swift @@ -0,0 +1,105 @@ +// +// SourceLocalRuntime.swift +// World Manager for Minecraft +// +// Created by OpenAI Codex on 2026-05-29. +// + +import Foundation + +@MainActor +protocol LocalSourceRuntimeHosting: AnyObject { + var sources: [MinecraftSource] { get } + var isShuttingDown: Bool { get } + var hasActiveScan: Bool { get } + + func source(withID sourceID: URL) -> MinecraftSource? + func updateAvailability(for sourceID: URL, to newAvailability: SourceAvailability) -> (previous: SourceAvailability, becameAvailable: Bool) + func queueAutomaticSync(for sourceID: URL, reason: String, debounce: TimeInterval?) + func currentCollectionSnapshots(for sourceURL: URL) -> [CollectionSnapshot] +} + +enum LocalSourceRuntime { + static func runRefreshLoop( + on host: LocalSourceRuntimeHosting, + refreshInterval: TimeInterval, + accessMethod: SourceAccessMethod + ) async { + while !Task.isCancelled && !host.isShuttingDown { + if host.hasActiveScan { + do { + try await Task.sleep(for: .seconds(refreshInterval)) + } catch { + return + } + continue + } + + await refreshSources(on: host, using: accessMethod) + + do { + try await Task.sleep(for: .seconds(refreshInterval)) + } catch { + return + } + } + } + + static func refreshSources( + on host: LocalSourceRuntimeHosting, + using accessMethod: SourceAccessMethod + ) async { + guard !host.hasActiveScan else { + return + } + + let localSourceIDs = host.sources + .filter { $0.origin.kind == .localFolder } + .map(\.id) + + for sourceID in localSourceIDs { + guard !Task.isCancelled, !host.isShuttingDown else { + return + } + + guard let currentSource = host.source(withID: sourceID) else { + continue + } + + let availability = await accessMethod.availability(for: currentSource) + let transition = host.updateAvailability(for: sourceID, to: availability) + + guard let refreshedSource = host.source(withID: sourceID) else { + continue + } + + if transition.becameAvailable { + host.queueAutomaticSync( + for: sourceID, + reason: refreshedSource.hasCachedContent + ? "Folder available. Refreshing cached library..." + : "Folder available. Scanning Minecraft library...", + debounce: nil + ) + continue + } + + guard refreshedSource.availability == .available, !refreshedSource.isScanning else { + continue + } + + if SourceRestoration.needsReconcile( + refreshedSource, + currentCollectionSnapshots: host.currentCollectionSnapshots(for:) + ) { + host.queueAutomaticSync( + for: sourceID, + reason: refreshedSource.hasCachedContent + ? "Detected changes. Refreshing cached library..." + : "Scanning Minecraft library...", + debounce: nil + ) + } + } + } +}