You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
drop.two/Sources/DropTwo/Model/ConversionCoordinator.swift

103 lines
3.5 KiB

import DropTwoCore
import Foundation
/// Treibt die Verarbeitung der Dateiliste über `JobQueue` an und hält den
/// UI-Status pro Datei nach. Kennt keine Views — nur `@Published`-Zustand,
/// den SwiftUI beobachtet.
@MainActor
final class ConversionCoordinator: ObservableObject {
@Published private(set) var items: [DropItem] = []
@Published private(set) var isRunning = false
private var runningTask: Task<Void, Never>?
var overallProgress: Double {
guard !items.isEmpty else { return 0 }
let sum = items.reduce(0.0) { partial, item in
switch item.status {
case .waiting:
return partial
case .running(let progress):
return partial + progress
case .skipped, .rejected, .succeeded, .failed:
return partial + 1
}
}
return sum / Double(items.count)
}
var problemItems: [DropItem] {
items.filter { $0.status.needsAttention }
}
/// README: "Dateiauswahl vorsortiert nach zuletzt geändert" — neueste zuerst.
func addFiles(_ urls: [URL]) {
let existing = Set(items.map(\.sourceURL))
let newURLs = urls
.filter { !existing.contains($0) }
.sorted { modificationDate(for: $0) > modificationDate(for: $1) }
items.append(contentsOf: newURLs.map(DropItem.init))
}
func removeAll() {
guard !isRunning else { return }
items.removeAll()
}
func start(targetFolder: URL) {
guard !isRunning, !items.isEmpty else { return }
isRunning = true
for index in items.indices {
items[index].status = .waiting
}
let jobs = items.map { JobQueue.Job(id: $0.id, sourceURL: $0.sourceURL) }
runningTask = Task {
let stream = JobQueue.run(
jobs: jobs,
outputRootDirectory: targetFolder,
progress: { [weak self] job, value in
Task { @MainActor in
self?.updateProgress(jobID: job.id, value: value)
}
}
)
for await result in stream {
apply(result)
}
isRunning = false
}
}
private func updateProgress(jobID: UUID, value: Double) {
guard let index = items.firstIndex(where: { $0.id == jobID }) else { return }
items[index].status = .running(progress: value)
}
private func apply(_ result: JobQueue.JobResult) {
guard let index = items.firstIndex(where: { $0.id == result.job.id }) else { return }
switch result.outcome {
case .skipped(_, let reason):
items[index].status = .skipped(reason)
case .rejected(_, let reason):
items[index].status = .rejected(reason)
case .succeeded(let success):
items[index].status = .succeeded(croppedDestination: success.croppedDestination)
case .failed(_, let error):
items[index].status = .failed(message: Self.message(for: error))
}
}
private static func message(for error: Error) -> String {
if let localizedError = error as? LocalizedError, let description = localizedError.errorDescription {
return description
}
return error.localizedDescription
}
private func modificationDate(for url: URL) -> Date {
(try? url.resourceValues(forKeys: [.contentModificationDateKey]).contentModificationDate) ?? .distantPast
}
}

Powered by TurnKey Linux.