102 lines
3.3 KiB
Swift
102 lines
3.3 KiB
Swift
// SPDX-FileCopyrightText: 2026 John Burwell and contributors
|
|
// SPDX-License-Identifier: AGPL-3.0-or-later
|
|
|
|
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
|
|
)
|
|
}
|
|
}
|
|
}
|
|
}
|