Files
bearwave-mac/Sources/BearWaveCore/Stores/PersistenceStore.swift
2026-06-25 09:21:42 +02:00

131 lines
4.4 KiB
Swift

import Foundation
public struct PersistenceStore: Sendable {
let applicationSupportDirectory: URL
let cacheDirectory: URL
let linuxConfigDirectory: URL
public init(
applicationSupportDirectory: URL = FileManager.default.urls(for: .applicationSupportDirectory, in: .userDomainMask)[0].appendingPathComponent("BearWave"),
cacheDirectory: URL = FileManager.default.urls(for: .cachesDirectory, in: .userDomainMask)[0].appendingPathComponent("BearWave"),
linuxConfigDirectory: URL = FileManager.default.homeDirectoryForCurrentUser.appendingPathComponent(".config/bearwave")
) {
self.applicationSupportDirectory = applicationSupportDirectory
self.cacheDirectory = cacheDirectory
self.linuxConfigDirectory = linuxConfigDirectory
}
var apiCacheDirectory: URL {
cacheDirectory.appendingPathComponent("api_cache")
}
func bootstrapImportIfNeeded() {
if !FileManager.default.fileExists(atPath: favoritesURL.path) {
copyIfPresent(from: linuxConfigDirectory.appendingPathComponent("favorites.json"), to: favoritesURL)
}
if !FileManager.default.fileExists(atPath: stateURL.path) {
copyIfPresent(from: linuxConfigDirectory.appendingPathComponent("state.json"), to: stateURL)
}
}
func loadFavorites() -> [RadioStation] {
load([RadioStation].self, from: favoritesURL) ?? []
}
func saveFavorites(_ stations: [RadioStation]) {
save(stations, to: favoritesURL)
}
func loadManualStations() -> [RadioStation] {
load([RadioStation].self, from: manualURL) ?? []
}
func saveManualStations(_ stations: [RadioStation]) {
save(stations, to: manualURL)
}
func loadGroups() -> [StationGroup] {
load([StationGroup].self, from: groupsURL) ?? []
}
func saveGroups(_ groups: [StationGroup]) {
save(groups, to: groupsURL)
}
func loadPlaybackState() -> PlaybackState {
load(PlaybackState.self, from: stateURL) ?? PlaybackState()
}
func savePlaybackState(_ state: PlaybackState) {
save(state, to: stateURL)
}
private var favoritesURL: URL {
applicationSupportDirectory.appendingPathComponent("favorites.json")
}
private var manualURL: URL {
applicationSupportDirectory.appendingPathComponent("manual-stations.json")
}
private var stateURL: URL {
applicationSupportDirectory.appendingPathComponent("state.json")
}
private var groupsURL: URL {
applicationSupportDirectory.appendingPathComponent("groups.json")
}
private func load<T: Decodable>(_ type: T.Type, from url: URL) -> T? {
guard let data = try? Data(contentsOf: url) else { return nil }
return try? JSONDecoder().decode(type, from: data)
}
private func save<T: Encodable>(_ value: T, to url: URL) {
do {
try FileManager.default.createDirectory(at: applicationSupportDirectory, withIntermediateDirectories: true)
let data = try JSONEncoder.prettyBearWave.encode(value)
try data.write(to: url, options: .atomic)
} catch {
assertionFailure("Failed to save \(url.lastPathComponent): \(error)")
}
}
private func copyIfPresent(from source: URL, to destination: URL) {
guard FileManager.default.fileExists(atPath: source.path) else { return }
do {
try FileManager.default.createDirectory(at: destination.deletingLastPathComponent(), withIntermediateDirectories: true)
try FileManager.default.copyItem(at: source, to: destination)
} catch {
assertionFailure("Failed to import \(source.lastPathComponent): \(error)")
}
}
}
struct PlaybackState: Codable, Equatable, Sendable {
var lastStationName: String
var lastStationURL: String
var volume: Double
var recentStations: [RadioStation]
init(
lastStationName: String = "",
lastStationURL: String = "",
volume: Double = 0.55,
recentStations: [RadioStation] = []
) {
self.lastStationName = lastStationName
self.lastStationURL = lastStationURL
self.volume = volume
self.recentStations = recentStations
}
}
extension JSONEncoder {
static var prettyBearWave: JSONEncoder {
let encoder = JSONEncoder()
encoder.outputFormatting = [.prettyPrinted, .sortedKeys]
return encoder
}
}