87 lines
4.0 KiB
Swift
87 lines
4.0 KiB
Swift
import Foundation
|
|
import TOMLDecoder
|
|
|
|
public struct PlayerConfiguration: Equatable, Sendable {
|
|
public var showBufferInfo: Bool
|
|
public var automaticQuality: Bool
|
|
public var snapToEdges: Bool
|
|
|
|
public init(showBufferInfo: Bool = false, automaticQuality: Bool = false, snapToEdges: Bool = true) {
|
|
self.showBufferInfo = showBufferInfo
|
|
self.automaticQuality = automaticQuality
|
|
self.snapToEdges = snapToEdges
|
|
}
|
|
|
|
public static func fileURL(environment: [String: String], homeDirectory: URL) -> URL {
|
|
let directory: URL
|
|
if let path = environment["XDG_CONFIG_HOME"], path.hasPrefix("/") {
|
|
directory = URL(fileURLWithPath: path, isDirectory: true)
|
|
} else {
|
|
directory = homeDirectory.appendingPathComponent(".config", isDirectory: true)
|
|
}
|
|
return directory.appendingPathComponent("terebi/config.toml")
|
|
}
|
|
|
|
public static func loadForApplication(arguments: [String], environment: [String: String],
|
|
homeDirectory: URL) throws -> Self {
|
|
if arguments.contains("--no-config") { return Self() }
|
|
if let index = arguments.firstIndex(of: "--config-file") {
|
|
guard index + 1 < arguments.count, let url = URL(string: arguments[index + 1]), url.isFileURL,
|
|
url.host == nil || url.host == "" || url.host == "localhost" else {
|
|
throw ConfigurationError(message: "--config-file requires a local file URL.")
|
|
}
|
|
return try load(from: url)
|
|
}
|
|
return try load(from: fileURL(environment: environment, homeDirectory: homeDirectory))
|
|
}
|
|
|
|
public static func load(from url: URL) throws -> Self {
|
|
let data: Data
|
|
do {
|
|
data = try Data(contentsOf: url)
|
|
} catch {
|
|
let failure = error as NSError
|
|
let underlying = failure.userInfo[NSUnderlyingErrorKey] as? NSError
|
|
if failure.domain == NSCocoaErrorDomain, failure.code == CocoaError.fileReadNoSuchFile.rawValue,
|
|
underlying?.domain == NSPOSIXErrorDomain, underlying?.code == Int(ENOENT) {
|
|
return Self()
|
|
}
|
|
throw ConfigurationError(message: "\(url.path): \(error.localizedDescription)")
|
|
}
|
|
do {
|
|
let values = try TOMLDecoder(isLenient: false).decode([String: Bool].self, from: data)
|
|
for key in values.keys {
|
|
switch key {
|
|
case "show_buffer_info", "automatic_quality", "snap_to_edges": break
|
|
default: throw ConfigurationError(message: "Unknown setting '\(key)'.")
|
|
}
|
|
}
|
|
var configuration = Self()
|
|
configuration.showBufferInfo = values["show_buffer_info"] ?? configuration.showBufferInfo
|
|
configuration.automaticQuality = values["automatic_quality"] ?? configuration.automaticQuality
|
|
configuration.snapToEdges = values["snap_to_edges"] ?? configuration.snapToEdges
|
|
return configuration
|
|
} catch {
|
|
throw ConfigurationError(message: "\(url.path): \(diagnostic(for: error))")
|
|
}
|
|
}
|
|
|
|
private static func diagnostic(for error: Error) -> String {
|
|
let context: DecodingError.Context
|
|
switch error {
|
|
case DecodingError.dataCorrupted(let value), DecodingError.typeMismatch(_, let value),
|
|
DecodingError.valueNotFound(_, let value), DecodingError.keyNotFound(_, let value): context = value
|
|
case let error as ConfigurationError: return error.message
|
|
default: return String(describing: error)
|
|
}
|
|
if let underlying = context.underlyingError { return String(describing: underlying) }
|
|
let path = context.codingPath.map(\.stringValue).joined(separator: ".")
|
|
return path.isEmpty ? context.debugDescription : "\(path): \(context.debugDescription)"
|
|
}
|
|
|
|
private struct ConfigurationError: LocalizedError {
|
|
let message: String
|
|
var errorDescription: String? { message }
|
|
}
|
|
}
|