velody/packages/apple/VelodyPersistence/Sources/VelodyPersistence/RemoteTrackDownloadStateStore.swift

94 lines
3.0 KiB
Swift

import Foundation
import VelodyDomain
public protocol RemoteTrackDownloadStateStore: Actor {
func loadDownloadStates() async throws -> [RemoteTrackDownloadState]
func saveDownloadStates(_ states: [RemoteTrackDownloadState]) async throws
}
public extension RemoteTrackDownloadStateStore {
func saveDownloadState(_ state: RemoteTrackDownloadState) async throws {
var states = try await loadDownloadStates()
if let existingIndex = states.firstIndex(where: { $0.remoteTrackId == state.remoteTrackId }) {
states[existingIndex] = state
} else {
states.append(state)
}
try await saveDownloadStates(states)
}
}
public actor FileRemoteTrackDownloadStateStore: RemoteTrackDownloadStateStore {
private let fileURL: URL
private let fileManager: FileManager
private let encoder = JSONEncoder()
private let decoder = JSONDecoder()
public init(
fileURL: URL? = nil,
fileManager: FileManager = .default
) throws {
self.fileManager = fileManager
if let fileURL {
self.fileURL = fileURL
} else {
self.fileURL = try Self.defaultFileURL(fileManager: fileManager)
}
encoder.dateEncodingStrategy = .iso8601
decoder.dateDecodingStrategy = .iso8601
}
public func loadDownloadStates() async throws -> [RemoteTrackDownloadState] {
guard fileManager.fileExists(atPath: fileURL.path) else {
return []
}
let data = try Data(contentsOf: fileURL)
return try decoder.decode([RemoteTrackDownloadState].self, from: data)
}
public func saveDownloadStates(_ states: [RemoteTrackDownloadState]) async throws {
try fileManager.createDirectory(
at: fileURL.deletingLastPathComponent(),
withIntermediateDirectories: true
)
let sortedStates = states.sorted { lhs, rhs in
lhs.remoteTrackId.localizedCaseInsensitiveCompare(rhs.remoteTrackId) == .orderedAscending
}
let data = try encoder.encode(sortedStates)
try data.write(to: fileURL, options: .atomic)
}
private static func defaultFileURL(fileManager: FileManager) throws -> URL {
guard let applicationSupportURL = fileManager.urls(
for: .applicationSupportDirectory,
in: .userDomainMask
).first else {
throw CocoaError(.fileNoSuchFile)
}
return applicationSupportURL
.appendingPathComponent("Velody", isDirectory: true)
.appendingPathComponent("remote-download-states.json")
}
}
public actor InMemoryRemoteTrackDownloadStateStore: RemoteTrackDownloadStateStore {
private var states: [RemoteTrackDownloadState]
public init(states: [RemoteTrackDownloadState] = []) {
self.states = states
}
public func loadDownloadStates() async throws -> [RemoteTrackDownloadState] {
states
}
public func saveDownloadStates(_ states: [RemoteTrackDownloadState]) async throws {
self.states = states
}
}