// // JellyfinClientConfiguration.swift // // Copyright 2026 Brendan Szymanski // // This program is free software: you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by // the Free Software Foundation, either version 3 of the License, or // (at your option) any later version. // // This program is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License for more details. // // You should have received a copy of the GNU General Public License // along with this program. If not, see . // // SPDX-License-Identifier: GPL-3.0-or-later // import Foundation import LuminateCore /// Everything a ``JellyfinClient`` needs to reach one server as one client identity. /// /// The identity fields end up in the `Authorization` header that ``AuthenticationMiddleware`` /// builds. They are not cosmetic: the server lists ``deviceName`` in its active-devices UI and /// keys the session by ``deviceID``. package struct JellyfinClientConfiguration: Sendable, Hashable { /// The base URL of the Jellyfin server, or `nil` before a server is chosen. package var serverURL: URL? /// The application name reported to the server. package var clientName: String /// The human-readable device name shown in the server's active-devices list. package var deviceName: String /// The stable per-machine device identifier the server keys its session by. package var deviceID: String /// The application version reported to the server. package var version: String /// The access token, or `nil` before sign-in. package var accessToken: String? /// Creates a configuration, defaulting the client identity to Luminate's. /// /// - Parameters: /// - serverURL: The base URL of the Jellyfin server, or `nil` before a server is chosen. /// - clientName: The application name reported to the server. /// - deviceName: The device name shown by the server; defaults to the machine's host name. /// - deviceID: The device identifier; defaults to ``defaultDeviceID()``. /// - version: The application version reported to the server. /// - accessToken: An access token to start authenticated with, if one was persisted. package init( serverURL: URL?, clientName: String = AppInfo.name, deviceName: String = ProcessInfo.processInfo.hostName, deviceID: String = JellyfinClientConfiguration.defaultDeviceID(), version: String = AppInfo.version, accessToken: String? = nil ) { self.serverURL = serverURL self.clientName = clientName self.deviceName = deviceName self.deviceID = deviceID self.version = version self.accessToken = accessToken } /// Derives a device identifier that survives application restarts. /// /// Jellyfin opens a new server-side session for every unfamiliar device identifier, so a value /// regenerated each launch would litter the server's device list. The machine ID is stable, /// already present on every systemd and D-Bus system, and needs no persistence layer of /// Luminate's own. /// /// - Returns: The contents of `/etc/machine-id`, else `/var/lib/dbus/machine-id`, else a fresh /// UUID string when neither file is readable or both are empty. package static func defaultDeviceID() -> String { let candidates = ["/etc/machine-id", "/var/lib/dbus/machine-id"] for candidate in candidates { guard let contents = try? String(contentsOf: URL(filePath: candidate), encoding: .utf8) else { continue } let trimmed = contents.trimmingCharacters(in: .whitespacesAndNewlines) if !trimmed.isEmpty { return trimmed } } return UUID().uuidString } }