import Domain import Foundation import Synchronization public final class FakeSource: LibrarySource, Starring, LyricsSource { public let id: SourceID public let name: String private let catalog: FakeCatalog private let artworkDirectory: URL private struct Extras { var changes: [Change] = [] var artwork: [String: URL] = [:] var lyrics: [String: URL] = [:] } private let extras = Mutex(Extras()) public init(id: SourceID = SourceID(), name: String = "Demo Library") { self.id = id self.name = name catalog = FakeCatalog(source: id) artworkDirectory = FileManager.default.temporaryDirectory.appending(path: "FakeSourceArtwork", directoryHint: .isDirectory) try? FileManager.default.createDirectory(at: artworkDirectory, withIntermediateDirectories: true) } public var sampleTracks: [Track] { catalog.changes.compactMap { if case let .upsert(.track(track)) = $0 { return track } return nil } } public func add(_ changes: [Change], artworkFiles: [String: URL], lyricsFiles: [String: URL] = [:]) { extras.withLock { $0.changes += changes $0.artwork.merge(artworkFiles) { $1 } $0.lyrics.merge(lyricsFiles) { $1 } } } public func lyrics(for track: EntityID) async throws -> Lyrics? { guard let file = extras.withLock({ $0.lyrics[track.remote] }) else { return nil } return try Lyrics(lrc: String(contentsOf: file, encoding: .utf8)) } public func changes(since cursor: Cursor?) -> AsyncThrowingStream { let changes = cursor == nil ? catalog.changes + extras.withLock(\.changes) : [] return AsyncThrowingStream { continuation in changes.forEach { continuation.yield($0) } continuation.yield(.checkpoint(Cursor("1"))) continuation.finish() } } public func stream(for _: EntityID) async throws -> StreamRequest { throw SourceError.notFound } public func artwork(_ id: EntityID, pixelSize: Int) async throws -> ResourceRequest { if let file = extras.withLock({ $0.artwork[id.remote] }) { return ResourceRequest(url: file) } let side = pixelSize <= 320 ? 320 : 1024 let file = artworkDirectory.appending(path: "\(id.remote)-\(side).png") if !FileManager.default.fileExists(atPath: file.path) { guard let data = FakeArtwork.png(seed: id.remote, side: side) else { throw SourceError.notFound } try data.write(to: file, options: .atomic) } return ResourceRequest(url: file) } public func setFavorite(_: Bool, for _: EntityID, kind _: EntityKind) async throws {} }