95 lines
2.9 KiB
Swift
95 lines
2.9 KiB
Swift
import Foundation
|
|
import Observation
|
|
|
|
@Observable
|
|
@MainActor
|
|
final class ProgressSyncManager {
|
|
private let client: ABSClient
|
|
private(set) var queuedCount: Int = 0
|
|
private(set) var lastSyncError: String?
|
|
|
|
/// Latest progress per itemId, persisted to disk.
|
|
private var queue: [String: PlaybackProgress] = [:]
|
|
|
|
private let queueFile: URL
|
|
|
|
init(client: ABSClient) {
|
|
self.client = client
|
|
let dir = AppPaths.supportDirectory
|
|
try? FileManager.default.createDirectory(at: dir, withIntermediateDirectories: true)
|
|
self.queueFile = dir.appendingPathComponent("progress-queue.json")
|
|
loadQueue()
|
|
}
|
|
|
|
func report(itemId: String, episodeId: String? = nil, currentTime: Double, duration: Double, isFinished: Bool, isOnline: Bool) async {
|
|
let progress = PlaybackProgress(
|
|
itemId: itemId,
|
|
episodeId: episodeId,
|
|
currentTime: currentTime,
|
|
duration: duration,
|
|
isFinished: isFinished,
|
|
updatedAt: Date()
|
|
)
|
|
let key = progress.syncKey
|
|
|
|
if isOnline {
|
|
do {
|
|
try await client.saveProgress(progress)
|
|
queue.removeValue(forKey: key)
|
|
persist()
|
|
lastSyncError = nil
|
|
return
|
|
} catch {
|
|
lastSyncError = error.localizedDescription
|
|
}
|
|
}
|
|
|
|
queue[key] = progress
|
|
persist()
|
|
}
|
|
|
|
func drain() async {
|
|
guard !queue.isEmpty else { return }
|
|
let snapshot = queue
|
|
for (id, progress) in snapshot {
|
|
do {
|
|
try await client.saveProgress(progress)
|
|
queue.removeValue(forKey: id)
|
|
} catch {
|
|
lastSyncError = error.localizedDescription
|
|
break
|
|
}
|
|
}
|
|
persist()
|
|
}
|
|
|
|
private func loadQueue() {
|
|
guard let data = try? Data(contentsOf: queueFile),
|
|
let decoded = try? JSONDecoder().decode([String: PlaybackProgress].self, from: data) else { return }
|
|
queue = decoded
|
|
queuedCount = decoded.count
|
|
}
|
|
|
|
private func persist() {
|
|
queuedCount = queue.count
|
|
do {
|
|
let data = try JSONEncoder().encode(queue)
|
|
try data.write(to: queueFile, options: .atomic)
|
|
} catch {
|
|
lastSyncError = "Queue konnte nicht gespeichert werden: \(error.localizedDescription)"
|
|
}
|
|
}
|
|
}
|
|
|
|
enum AppPaths {
|
|
static var supportDirectory: URL {
|
|
let base = FileManager.default.urls(for: .applicationSupportDirectory, in: .userDomainMask).first
|
|
?? URL(fileURLWithPath: NSHomeDirectory()).appendingPathComponent("Library/Application Support")
|
|
return base.appendingPathComponent("AudiobookshelfClient", isDirectory: true)
|
|
}
|
|
|
|
static var downloadsDirectory: URL {
|
|
supportDirectory.appendingPathComponent("downloads", isDirectory: true)
|
|
}
|
|
}
|