Extract local source runtime orchestration

This commit is contained in:
John Burwell 2026-05-29 07:12:00 -05:00
parent cab38254dd
commit 5baf25f601
2 changed files with 113 additions and 70 deletions

View File

@ -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 {

View File

@ -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
)
}
}
}
}