world-manager/World Manager for Minecraft/Services/Sources/Local/SourceLocalRuntime.swift

106 lines
3.3 KiB
Swift

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