Extract local source runtime orchestration
This commit is contained in:
parent
cab38254dd
commit
5baf25f601
@ -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 {
|
||||
|
||||
105
World Manager for Minecraft/Services/SourceLocalRuntime.swift
Normal file
105
World Manager for Minecraft/Services/SourceLocalRuntime.swift
Normal 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
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Loading…
Reference in New Issue
Block a user