import Foundation import VelodyDomain public protocol RemoteLibraryStore: Actor { func loadRemoteTracks() async throws -> [RemoteTrack] func replaceRemoteTracks(_ tracks: [RemoteTrack]) async throws } public actor FileRemoteLibraryStore: RemoteLibraryStore { 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) } } public func loadRemoteTracks() async throws -> [RemoteTrack] { guard fileManager.fileExists(atPath: fileURL.path) else { return [] } let data = try Data(contentsOf: fileURL) return try decoder.decode([RemoteTrack].self, from: data) } public func replaceRemoteTracks(_ tracks: [RemoteTrack]) async throws { try fileManager.createDirectory( at: fileURL.deletingLastPathComponent(), withIntermediateDirectories: true ) let data = try encoder.encode(tracks) 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-library.json") } } public actor InMemoryRemoteLibraryStore: RemoteLibraryStore { private var tracks: [RemoteTrack] public init(tracks: [RemoteTrack] = []) { self.tracks = tracks } public func loadRemoteTracks() async throws -> [RemoteTrack] { tracks } public func replaceRemoteTracks(_ tracks: [RemoteTrack]) async throws { self.tracks = tracks } }