diff --git a/Shared/Coordinators/MainCoordinator/iOSMainCoordinator.swift b/Shared/Coordinators/MainCoordinator/iOSMainCoordinator.swift index 9fd3089a..94f630c5 100644 --- a/Shared/Coordinators/MainCoordinator/iOSMainCoordinator.swift +++ b/Shared/Coordinators/MainCoordinator/iOSMainCoordinator.swift @@ -8,6 +8,7 @@ import Combine import Defaults +import Factory import Foundation import Nuke import Stinsen @@ -15,6 +16,10 @@ import SwiftUI import WidgetKit final class MainCoordinator: NavigationCoordinatable { + + @Injected(LogManager.service) + private var logger + var stack: NavigationStack @Root @@ -60,13 +65,13 @@ final class MainCoordinator: NavigationCoordinatable { @objc func didSignIn() { - LogManager.log.info("Received `didSignIn` from SwiftfinNotificationCenter.") + logger.info("Received `didSignIn` from SwiftfinNotificationCenter.") root(\.mainTab) } @objc func didSignOut() { - LogManager.log.info("Received `didSignOut` from SwiftfinNotificationCenter.") + logger.info("Received `didSignOut` from SwiftfinNotificationCenter.") root(\.serverList) } diff --git a/Shared/Coordinators/MainCoordinator/tvOSMainCoordinator.swift b/Shared/Coordinators/MainCoordinator/tvOSMainCoordinator.swift index 7cca3e82..8adc9bb7 100644 --- a/Shared/Coordinators/MainCoordinator/tvOSMainCoordinator.swift +++ b/Shared/Coordinators/MainCoordinator/tvOSMainCoordinator.swift @@ -6,12 +6,17 @@ // Copyright (c) 2022 Jellyfin & Jellyfin Contributors // +import Factory import Foundation import Nuke import Stinsen import SwiftUI final class MainCoordinator: NavigationCoordinatable { + + @Injected(LogManager.service) + private var logger + var stack = NavigationStack(initial: \MainCoordinator.mainTab) @Root @@ -40,13 +45,13 @@ final class MainCoordinator: NavigationCoordinatable { @objc func didSignIn() { - LogManager.log.info("Received `didSignIn` from NSNotificationCenter.") + logger.info("Received `didSignIn` from NSNotificationCenter.") root(\.mainTab) } @objc func didSignOut() { - LogManager.log.info("Received `didSignOut` from NSNotificationCenter.") + logger.info("Received `didSignOut` from NSNotificationCenter.") root(\.serverList) } diff --git a/Shared/Extensions/JellyfinAPIExtensions/BaseItemDto+VideoPlayerViewModel.swift b/Shared/Extensions/JellyfinAPIExtensions/BaseItemDto+VideoPlayerViewModel.swift index a58b0d8c..e096c68f 100644 --- a/Shared/Extensions/JellyfinAPIExtensions/BaseItemDto+VideoPlayerViewModel.swift +++ b/Shared/Extensions/JellyfinAPIExtensions/BaseItemDto+VideoPlayerViewModel.swift @@ -14,7 +14,7 @@ import UIKit extension BaseItemDto { func createVideoPlayerViewModel() -> AnyPublisher<[VideoPlayerViewModel], Error> { - LogManager.log.debug("Creating video player view model for item: \(id ?? "")") + LogManager.service().debug("Creating video player view model for item: \(id ?? "")") let builder = DeviceProfileBuilder() // TODO: fix bitrate settings @@ -182,7 +182,7 @@ extension BaseItemDto { func createLiveTVVideoPlayerViewModel() -> AnyPublisher<[VideoPlayerViewModel], Error> { - LogManager.log.debug("Creating liveTV video player view model for item: \(id ?? "")") + LogManager.service().debug("Creating liveTV video player view model for item: \(id ?? "")") let builder = DeviceProfileBuilder() // TODO: fix bitrate settings diff --git a/Shared/Extensions/URLExtensions.swift b/Shared/Extensions/URLExtensions.swift index 56abb3b6..5462e041 100644 --- a/Shared/Extensions/URLExtensions.swift +++ b/Shared/Extensions/URLExtensions.swift @@ -22,4 +22,8 @@ public extension URL { return items } + + static var documents: URL { + FileManager.default.urls(for: .documentDirectory, in: .userDomainMask)[0] + } } diff --git a/Shared/ServerDiscovery/ServerDiscovery.swift b/Shared/ServerDiscovery/ServerDiscovery.swift index 26ba7486..7be69649 100644 --- a/Shared/ServerDiscovery/ServerDiscovery.swift +++ b/Shared/ServerDiscovery/ServerDiscovery.swift @@ -6,10 +6,15 @@ // Copyright (c) 2022 Jellyfin & Jellyfin Contributors // +import Factory import Foundation import UDPBroadcast public class ServerDiscovery { + + @Injected(LogManager.service) + private var logger + public struct ServerLookupResponse: Codable, Hashable, Identifiable { public func hash(into hasher: inout Hasher) { @@ -56,7 +61,7 @@ public class ServerDiscovery { func receiveHandler(_ ipAddress: String, _ port: Int, _ data: Data) { do { let response = try JSONDecoder().decode(ServerLookupResponse.self, from: data) - LogManager.log.debug("Received JellyfinServer from \"\(response.name)\"", tag: "ServerDiscovery") + logger.debug("Received JellyfinServer from \"\(response.name)\"", tag: "ServerDiscovery") completion(response) } catch { completion(nil) @@ -64,15 +69,15 @@ public class ServerDiscovery { } func errorHandler(error: UDPBroadcastConnection.ConnectionError) { - LogManager.log.error("Error handling response: \(error.localizedDescription)", tag: "ServerDiscovery") + logger.error("Error handling response: \(error.localizedDescription)", tag: "ServerDiscovery") } do { self.connection = try! UDPBroadcastConnection(port: 7359, handler: receiveHandler, errorHandler: errorHandler) try self.connection?.sendBroadcast("Who is JellyfinServer?") - LogManager.log.debug("Discovery broadcast sent", tag: "ServerDiscovery") + logger.debug("Discovery broadcast sent", tag: "ServerDiscovery") } catch { - LogManager.log.error("Error sending discovery broadcast", tag: "ServerDiscovery") + logger.error("Error sending discovery broadcast", tag: "ServerDiscovery") } } } diff --git a/Shared/Singleton/LogManager.swift b/Shared/Singleton/LogManager.swift index a54457fb..7b460a5b 100644 --- a/Shared/Singleton/LogManager.swift +++ b/Shared/Singleton/LogManager.swift @@ -6,16 +6,40 @@ // Copyright (c) 2022 Jellyfin & Jellyfin Contributors // +import Factory import Foundation import Puppy class LogManager { - static let log = Puppy() + static let service = Factory(scope: .singleton) { + Puppy.swiftfinInstance() + } - static func setup() { +// static let log = Puppy() +} - let logsDirectory = getDocumentsDirectory().appendingPathComponent("logs", isDirectory: true) +class LogFormatter: LogFormattable { + func formatMessage( + _ level: LogLevel, + message: String, + tag: String, + function: String, + file: String, + line: UInt, + swiftLogInfo: [String: String], + label: String, + date: Date, + threadID: UInt64 + ) -> String { + let file = shortFileName(file).replacingOccurrences(of: ".swift", with: "") + return " [\(level.emoji) \(level)] \(file)#\(line):\(function) \(message)" + } +} + +private extension Puppy { + static func swiftfinInstance() -> Puppy { + let logsDirectory = URL.documents.appendingPathComponent("logs", isDirectory: true) do { try FileManager.default.createDirectory( @@ -38,33 +62,9 @@ class LogManager { let consoleLogger = ConsoleLogger("org.jellyfin.swiftfin.logger.console") consoleLogger.format = LogFormatter() - log.add(fileRotationLogger, withLevel: .debug) - log.add(consoleLogger, withLevel: .debug) - } - - private static func getDocumentsDirectory() -> URL { - // find all possible documents directories for this user - let paths = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask) - - // just send back the first one, which ought to be the only one - return paths[0] - } -} - -class LogFormatter: LogFormattable { - func formatMessage( - _ level: LogLevel, - message: String, - tag: String, - function: String, - file: String, - line: UInt, - swiftLogInfo: [String: String], - label: String, - date: Date, - threadID: UInt64 - ) -> String { - let file = shortFileName(file).replacingOccurrences(of: ".swift", with: "") - return " [\(level.emoji) \(level)] \(file)#\(line):\(function) \(message)" + let logger = Puppy() + logger.add(fileRotationLogger, withLevel: .debug) + logger.add(consoleLogger, withLevel: .debug) + return logger } } diff --git a/Shared/Singleton/SwiftfinNotificationCenter.swift b/Shared/Singleton/SwiftfinNotificationCenter.swift index 78712cbc..33d60024 100644 --- a/Shared/Singleton/SwiftfinNotificationCenter.swift +++ b/Shared/Singleton/SwiftfinNotificationCenter.swift @@ -6,10 +6,14 @@ // Copyright (c) 2022 Jellyfin & Jellyfin Contributors // +import Factory import Foundation class SwiftfinNotification { + @Injected(Notifications.service) + private var notificationService + private let notificationName: Notification.Name fileprivate init(_ notificationName: Notification.Name) { @@ -17,23 +21,21 @@ class SwiftfinNotification { } func post(object: Any? = nil) { - Notifications.main.post(name: notificationName, object: object) + notificationService.post(name: notificationName, object: object) } func subscribe(_ observer: Any, selector: Selector) { - Notifications.main.addObserver(observer, selector: selector, name: notificationName, object: nil) + notificationService.addObserver(observer, selector: selector, name: notificationName, object: nil) } func unsubscribe(_ observer: Any) { - Notifications.main.removeObserver(self, name: notificationName, object: nil) + notificationService.removeObserver(self, name: notificationName, object: nil) } } enum Notifications { - static let main: NotificationCenter = { - NotificationCenter() - }() + static let service = Factory(scope: .singleton) { NotificationCenter() } final class Key { public typealias NotificationKey = Notifications.Key @@ -50,10 +52,6 @@ enum Notifications { static subscript(key: Key) -> SwiftfinNotification { key.underlyingNotification } - - static func unsubscribe(_ observer: Any) { - main.removeObserver(observer) - } } extension Notifications.Key { @@ -63,8 +61,5 @@ extension Notifications.Key { static let processDeepLink = NotificationKey("processDeepLink") static let didPurge = NotificationKey("didPurge") static let didChangeServerCurrentURI = NotificationKey("didChangeCurrentLoginURI") - static let toggleOfflineMode = NotificationKey("toggleOfflineMode") - static let didDeleteOfflineItem = NotificationKey("didDeleteOfflineItem") - static let didAddDownload = NotificationKey("didAddDownload") static let didSendStopReport = NotificationKey("didSendStopReport") } diff --git a/Shared/ViewModels/ConnectToServerViewModel.swift b/Shared/ViewModels/ConnectToServerViewModel.swift index 5315f28f..1729715d 100644 --- a/Shared/ViewModels/ConnectToServerViewModel.swift +++ b/Shared/ViewModels/ConnectToServerViewModel.swift @@ -7,6 +7,7 @@ // import Combine +import Factory import Foundation import JellyfinAPI import Stinsen @@ -49,7 +50,7 @@ final class ConnectToServerViewModel: ViewModel { let uri = uri.trimmingCharacters(in: .whitespacesAndNewlines) .trimmingCharacters(in: .objectReplacement) - LogManager.log.debug("Attempting to connect to server at \"\(uri)\"", tag: "connectToServer") + logger.debug("Attempting to connect to server at \"\(uri)\"", tag: "connectToServer") SessionManager.main.connectToServer(with: uri) .trackActivity(loading) .sink(receiveCompletion: { completion in @@ -92,7 +93,7 @@ final class ConnectToServerViewModel: ViewModel { } } }, receiveValue: { server in - LogManager.log.debug("Connected to server at \"\(uri)\"", tag: "connectToServer") + self.logger.debug("Connected to server at \"\(uri)\"", tag: "connectToServer") self.router?.route(to: \.userSignIn, server) }) .store(in: &cancellables) diff --git a/Shared/ViewModels/HomeViewModel.swift b/Shared/ViewModels/HomeViewModel.swift index 85f24224..cce5f92d 100644 --- a/Shared/ViewModels/HomeViewModel.swift +++ b/Shared/ViewModels/HomeViewModel.swift @@ -59,7 +59,7 @@ final class HomeViewModel: ViewModel { @objc func refresh() { - LogManager.log.debug("Refresh called.") + logger.debug("Refresh called.") refreshLibrariesLatest() refreshLatestAddedItems() @@ -85,7 +85,7 @@ final class HomeViewModel: ViewModel { var newLibraries: [BaseItemDto] = [] response.items!.forEach { item in - LogManager.log + self.logger .debug("Retrieved user view: \(item.id!) (\(item.name ?? "nil")) with type \(item.collectionType ?? "nil")") if item.collectionType == "movies" || item.collectionType == "tvshows" { newLibraries.append(item) @@ -165,7 +165,7 @@ final class HomeViewModel: ViewModel { self.handleAPIRequestError(completion: completion) } }, receiveValue: { response in - LogManager.log.debug("Retrieved \(String(response.items!.count)) resume items") + self.logger.debug("Retrieved \(String(response.items!.count)) resume items") self.resumeItems = response.items ?? [] }) diff --git a/Shared/ViewModels/ItemViewModel/ItemViewModel.swift b/Shared/ViewModels/ItemViewModel/ItemViewModel.swift index aa834529..9543920e 100644 --- a/Shared/ViewModels/ItemViewModel/ItemViewModel.swift +++ b/Shared/ViewModels/ItemViewModel/ItemViewModel.swift @@ -7,6 +7,7 @@ // import Combine +import Factory import Foundation import JellyfinAPI import UIKit @@ -65,7 +66,7 @@ class ItemViewModel: ViewModel { } else { // Remove if necessary. Note that this cannot be in deinit as // holding as an observer won't allow the object to be deinit-ed - Notifications.unsubscribe(self) + Notifications[.didSendStopReport].unsubscribe(self) } } diff --git a/Shared/ViewModels/ItemViewModel/SeasonItemViewModel.swift b/Shared/ViewModels/ItemViewModel/SeasonItemViewModel.swift index dd1a26fb..65293cba 100644 --- a/Shared/ViewModels/ItemViewModel/SeasonItemViewModel.swift +++ b/Shared/ViewModels/ItemViewModel/SeasonItemViewModel.swift @@ -39,7 +39,7 @@ final class SeasonItemViewModel: ItemViewModel, EpisodesRowManager { } private func requestEpisodes() { - LogManager.log + logger .debug("Getting episodes in season \(item.id!) (\(item.name!)) of show \(item.seriesId!) (\(item.seriesName!))") TvShowsAPI.getEpisodes( seriesId: item.seriesId ?? "", @@ -78,7 +78,7 @@ final class SeasonItemViewModel: ItemViewModel, EpisodesRowManager { !episode.unaired && !episode.missing && episode.seasonId ?? "" == self.item.id! }) { self.playButtonItem = nextUpItem - LogManager.log.debug("Nextup in season \(self.item.id!) (\(self.item.name!)): \(nextUpItem.id!)") + self.logger.debug("Nextup in season \(self.item.id!) (\(self.item.name!)): \(nextUpItem.id!)") } // if self.playButtonItem == nil && !self.episodes.isEmpty { diff --git a/Shared/ViewModels/ItemViewModel/SeriesItemViewModel.swift b/Shared/ViewModels/ItemViewModel/SeriesItemViewModel.swift index d197c06c..7f53dcde 100644 --- a/Shared/ViewModels/ItemViewModel/SeriesItemViewModel.swift +++ b/Shared/ViewModels/ItemViewModel/SeriesItemViewModel.swift @@ -49,7 +49,7 @@ final class SeriesItemViewModel: ItemViewModel, EpisodesRowManager { } private func getNextUp() { - LogManager.log.debug("Getting next up for show \(self.item.id!) (\(self.item.name!))") + logger.debug("Getting next up for show \(self.item.id!) (\(self.item.name!))") TvShowsAPI.getNextUp( userId: SessionManager.main.currentLogin.user.id, seriesId: self.item.id!, diff --git a/Shared/ViewModels/LiveTVChannelsViewModel.swift b/Shared/ViewModels/LiveTVChannelsViewModel.swift index 55621897..709944e9 100644 --- a/Shared/ViewModels/LiveTVChannelsViewModel.swift +++ b/Shared/ViewModels/LiveTVChannelsViewModel.swift @@ -6,6 +6,7 @@ // Copyright (c) 2022 Jellyfin & Jellyfin Contributors // +import Factory import Foundation import JellyfinAPI import SwiftUICollection @@ -69,7 +70,7 @@ final class LiveTVChannelsViewModel: ViewModel { .sink(receiveCompletion: { [weak self] completion in self?.handleAPIRequestError(completion: completion) }, receiveValue: { [weak self] _ in - LogManager.log.debug("Received Guide Info") + self?.logger.debug("Received Guide Info") guard let self = self else { return } self.getChannels() }) @@ -89,7 +90,7 @@ final class LiveTVChannelsViewModel: ViewModel { .sink(receiveCompletion: { [weak self] completion in self?.handleAPIRequestError(completion: completion) }, receiveValue: { [weak self] response in - LogManager.log.debug("Received \(response.items?.count ?? 0) Channels") + self?.logger.debug("Received \(response.items?.count ?? 0) Channels") guard let self = self else { return } self.channels = response.items ?? [] self.getPrograms() @@ -100,7 +101,7 @@ final class LiveTVChannelsViewModel: ViewModel { private func getPrograms() { // http://192.168.1.50:8096/LiveTv/Programs guard !channels.isEmpty else { - LogManager.log.debug("Cannot get programs, channels list empty. ") + logger.debug("Cannot get programs, channels list empty. ") return } let channelIds = channels.compactMap(\.id) @@ -126,7 +127,7 @@ final class LiveTVChannelsViewModel: ViewModel { .sink(receiveCompletion: { [weak self] completion in self?.handleAPIRequestError(completion: completion) }, receiveValue: { [weak self] response in - LogManager.log.debug("Received \(response.items?.count ?? 0) Programs") + self?.logger.debug("Received \(response.items?.count ?? 0) Programs") guard let self = self else { return } self.programs = response.items ?? [] self.channelPrograms = self.processChannelPrograms() @@ -178,7 +179,7 @@ final class LiveTVChannelsViewModel: ViewModel { } timer = Timer(fire: nextMinute, interval: 60 * 10, repeats: true) { [weak self] _ in guard let self = self else { return } - LogManager.log.debug("LiveTVChannels schedule check...") + self.logger.debug("LiveTVChannels schedule check...") DispatchQueue.global(qos: .background).async { let newChanPrgs = self.processChannelPrograms() DispatchQueue.main.async { diff --git a/Shared/ViewModels/LiveTVProgramsViewModel.swift b/Shared/ViewModels/LiveTVProgramsViewModel.swift index 3d983882..8aa3d9ba 100644 --- a/Shared/ViewModels/LiveTVProgramsViewModel.swift +++ b/Shared/ViewModels/LiveTVProgramsViewModel.swift @@ -49,7 +49,7 @@ final class LiveTVProgramsViewModel: ViewModel { .sink(receiveCompletion: { [weak self] completion in self?.handleAPIRequestError(completion: completion) }, receiveValue: { [weak self] response in - LogManager.log.debug("Received \(response.items?.count ?? 0) Channels") + self?.logger.debug("Received \(response.items?.count ?? 0) Channels") guard let self = self else { return } if let chans = response.items { for chan in chans { @@ -82,7 +82,7 @@ final class LiveTVProgramsViewModel: ViewModel { .sink(receiveCompletion: { [weak self] completion in self?.handleAPIRequestError(completion: completion) }, receiveValue: { [weak self] response in - LogManager.log.debug("Received \(String(response.items?.count ?? 0)) Recommended Programs") + self?.logger.debug("Received \(String(response.items?.count ?? 0)) Recommended Programs") guard let self = self else { return } self.recommendedItems = response.items ?? [] }) @@ -109,7 +109,7 @@ final class LiveTVProgramsViewModel: ViewModel { .sink(receiveCompletion: { [weak self] completion in self?.handleAPIRequestError(completion: completion) }, receiveValue: { [weak self] response in - LogManager.log.debug("Received \(String(response.items?.count ?? 0)) Series Items") + self?.logger.debug("Received \(String(response.items?.count ?? 0)) Series Items") guard let self = self else { return } self.seriesItems = response.items ?? [] }) @@ -136,7 +136,7 @@ final class LiveTVProgramsViewModel: ViewModel { .sink(receiveCompletion: { [weak self] completion in self?.handleAPIRequestError(completion: completion) }, receiveValue: { [weak self] response in - LogManager.log.debug("Received \(String(response.items?.count ?? 0)) Movie Items") + self?.logger.debug("Received \(String(response.items?.count ?? 0)) Movie Items") guard let self = self else { return } self.movieItems = response.items ?? [] }) @@ -159,7 +159,7 @@ final class LiveTVProgramsViewModel: ViewModel { .sink(receiveCompletion: { [weak self] completion in self?.handleAPIRequestError(completion: completion) }, receiveValue: { [weak self] response in - LogManager.log.debug("Received \(String(response.items?.count ?? 0)) Sports Items") + self?.logger.debug("Received \(String(response.items?.count ?? 0)) Sports Items") guard let self = self else { return } self.sportsItems = response.items ?? [] }) @@ -182,7 +182,7 @@ final class LiveTVProgramsViewModel: ViewModel { .sink(receiveCompletion: { [weak self] completion in self?.handleAPIRequestError(completion: completion) }, receiveValue: { [weak self] response in - LogManager.log.debug("Received \(String(response.items?.count ?? 0)) Kids Items") + self?.logger.debug("Received \(String(response.items?.count ?? 0)) Kids Items") guard let self = self else { return } self.kidsItems = response.items ?? [] }) @@ -205,7 +205,7 @@ final class LiveTVProgramsViewModel: ViewModel { .sink(receiveCompletion: { [weak self] completion in self?.handleAPIRequestError(completion: completion) }, receiveValue: { [weak self] response in - LogManager.log.debug("Received \(String(response.items?.count ?? 0)) News Items") + self?.logger.debug("Received \(String(response.items?.count ?? 0)) News Items") guard let self = self else { return } self.newsItems = response.items ?? [] }) diff --git a/Shared/ViewModels/QuickConnectSettingsViewModel.swift b/Shared/ViewModels/QuickConnectSettingsViewModel.swift index f575824c..2f0a82d5 100644 --- a/Shared/ViewModels/QuickConnectSettingsViewModel.swift +++ b/Shared/ViewModels/QuickConnectSettingsViewModel.swift @@ -32,13 +32,13 @@ final class QuickConnectSettingsViewModel: ViewModel { self.handleAPIRequestError(displayMessage: L10n.quickConnectInvalidError, completion: completion) switch completion { case .failure: - LogManager.log.debug("Invalid Quick Connect code entered") + self.logger.debug("Invalid Quick Connect code entered") default: break } }, receiveValue: { _ in // receiving a successful HTTP response indicates a valid code - LogManager.log.debug("Valid Quick connect code entered") + self.logger.debug("Valid Quick connect code entered") self.showSuccessMessage = true }) .store(in: &cancellables) diff --git a/Shared/ViewModels/SettingsViewModel.swift b/Shared/ViewModels/SettingsViewModel.swift index 59b78899..25d0ae19 100644 --- a/Shared/ViewModels/SettingsViewModel.swift +++ b/Shared/ViewModels/SettingsViewModel.swift @@ -24,6 +24,7 @@ final class SettingsViewModel: ViewModel { self.server = server self.user = user + super.init() // Bitrates let url = Bundle.main.url(forResource: "bitrates", withExtension: "json")! @@ -33,10 +34,10 @@ final class SettingsViewModel: ViewModel { do { self.bitrates = try JSONDecoder().decode([Bitrates].self, from: jsonData) } catch { - LogManager.log.error("Error converting processed JSON into Swift compatible schema.") + logger.error("Error converting processed JSON into Swift compatible schema.") } } catch { - LogManager.log.error("Error processing JSON file `bitrates.json`") + logger.error("Error processing JSON file `bitrates.json`") } // Track languages diff --git a/Shared/ViewModels/UserSignInViewModel.swift b/Shared/ViewModels/UserSignInViewModel.swift index 7c464246..684b857b 100644 --- a/Shared/ViewModels/UserSignInViewModel.swift +++ b/Shared/ViewModels/UserSignInViewModel.swift @@ -46,7 +46,7 @@ final class UserSignInViewModel: ViewModel { } func signIn(username: String, password: String) { - LogManager.log.debug("Attempting to login to server at \"\(server.currentURI)\"", tag: "login") + logger.debug("Attempting to login to server at \"\(server.currentURI)\"", tag: "login") let username = username.trimmingCharacters(in: .whitespacesAndNewlines) .trimmingCharacters(in: .objectReplacement) @@ -99,7 +99,7 @@ final class UserSignInViewModel: ViewModel { self.quickConnectSecret = response.secret self.quickConnectCode = response.code - LogManager.log.debug("QuickConnect code: \(response.code ?? .emptyDash)") + self.logger.debug("QuickConnect code: \(response.code ?? .emptyDash)") self.quickConnectTimer = RepeatingTimer(interval: 5) { self.checkAuthStatus(onSuccess) @@ -120,7 +120,7 @@ final class UserSignInViewModel: ViewModel { // this is a repeated call }, receiveValue: { value in guard let authenticated = value.authenticated, authenticated else { - LogManager.log.debug("QuickConnect not authenticated yet") + self.logger.debug("QuickConnect not authenticated yet") return } diff --git a/Shared/ViewModels/VideoPlayerViewModel/VideoPlayerViewModel.swift b/Shared/ViewModels/VideoPlayerViewModel/VideoPlayerViewModel.swift index 27b34c98..1b0e4144 100644 --- a/Shared/ViewModels/VideoPlayerViewModel/VideoPlayerViewModel.swift +++ b/Shared/ViewModels/VideoPlayerViewModel/VideoPlayerViewModel.swift @@ -511,7 +511,7 @@ extension VideoPlayerViewModel { .sink { completion in self.handleAPIRequestError(completion: completion) } receiveValue: { _ in - LogManager.log.debug("Start report sent for item: \(self.item.id ?? "No ID")") + self.logger.debug("Start report sent for item: \(self.item.id ?? "No ID")") } .store(in: &cancellables) } @@ -547,7 +547,7 @@ extension VideoPlayerViewModel { .sink { completion in self.handleAPIRequestError(completion: completion) } receiveValue: { _ in - LogManager.log.debug("Pause report sent for item: \(self.item.id ?? "No ID")") + self.logger.debug("Pause report sent for item: \(self.item.id ?? "No ID")") } .store(in: &cancellables) } @@ -592,7 +592,7 @@ extension VideoPlayerViewModel { .sink { completion in self.handleAPIRequestError(completion: completion) } receiveValue: { _ in - LogManager.log.debug("Playback progress sent for item: \(self.item.id ?? "No ID")") + self.logger.debug("Playback progress sent for item: \(self.item.id ?? "No ID")") } .store(in: &cancellables) @@ -619,7 +619,7 @@ extension VideoPlayerViewModel { .sink { completion in self.handleAPIRequestError(completion: completion) } receiveValue: { _ in - LogManager.log.debug("Stop report sent for item: \(self.item.id ?? "No ID")") + self.logger.debug("Stop report sent for item: \(self.item.id ?? "No ID")") Notifications[.didSendStopReport].post(object: self.item.id) } .store(in: &cancellables) diff --git a/Shared/ViewModels/ViewModel.swift b/Shared/ViewModels/ViewModel.swift index ca634b16..a30429f7 100644 --- a/Shared/ViewModels/ViewModel.swift +++ b/Shared/ViewModels/ViewModel.swift @@ -8,11 +8,14 @@ import ActivityIndicator import Combine +import Factory import Foundation import JellyfinAPI class ViewModel: ObservableObject { + @Injected(LogManager.service) + var logger @Published var isLoading = false @Published @@ -39,18 +42,18 @@ class ViewModel: ObservableObject { case .error(-1, _, _, _): networkError = .URLError(response: errorResponse, displayMessage: displayMessage) // Use the errorResponse description for debugging, rather than the user-facing friendly description which may not be implemented - LogManager.log + logger .error( "Request failed: URL request failed with error \(networkError.errorMessage.code): \(errorResponse.localizedDescription)" ) case .error(-2, _, _, _): networkError = .HTTPURLError(response: errorResponse, displayMessage: displayMessage) - LogManager.log + logger .error("Request failed: HTTP URL request failed with description: \(errorResponse.localizedDescription)") default: networkError = .JellyfinError(response: errorResponse, displayMessage: displayMessage) // Able to use user-facing friendly description here since just HTTP status codes - LogManager.log + logger .error( "Request failed: \(networkError.errorMessage.code) - \(networkError.errorMessage.title): \(networkError.errorMessage.message)\n\(error.localizedDescription)" ) @@ -66,7 +69,7 @@ class ViewModel: ObservableObject { message: swiftfinError.errorDescription ?? "" ) self.errorMessage = errorMessage - LogManager.log.error("Request failed: \(swiftfinError.errorDescription ?? "")") + logger.error("Request failed: \(swiftfinError.errorDescription ?? "")") default: let genericErrorMessage = ErrorMessage( @@ -75,7 +78,7 @@ class ViewModel: ObservableObject { message: error.localizedDescription ) self.errorMessage = genericErrorMessage - LogManager.log.error("Request failed: Generic error - \(error.localizedDescription)") + logger.error("Request failed: Generic error - \(error.localizedDescription)") } } } diff --git a/Swiftfin tvOS/Views/ItemView/Components/PlayButton.swift b/Swiftfin tvOS/Views/ItemView/Components/PlayButton.swift index 781fe873..fb9d3275 100644 --- a/Swiftfin tvOS/Views/ItemView/Components/PlayButton.swift +++ b/Swiftfin tvOS/Views/ItemView/Components/PlayButton.swift @@ -6,12 +6,15 @@ // Copyright (c) 2022 Jellyfin & Jellyfin Contributors // +import Factory import SwiftUI extension ItemView { struct PlayButton: View { + @Injected(LogManager.service) + private var logger @EnvironmentObject private var itemRouter: ItemCoordinator.Router @ObservedObject @@ -24,7 +27,7 @@ extension ItemView { if let selectedVideoPlayerViewModel = viewModel.selectedVideoPlayerViewModel { itemRouter.route(to: \.videoPlayer, selectedVideoPlayerViewModel) } else { - LogManager.log.error("Attempted to play item but no playback information available") + logger.error("Attempted to play item but no playback information available") } } label: { HStack(spacing: 15) { @@ -55,7 +58,7 @@ extension ItemView { selectedVideoPlayerViewModel.injectCustomValues(startFromBeginning: true) itemRouter.route(to: \.videoPlayer, selectedVideoPlayerViewModel) } else { - LogManager.log.error("Attempted to play item but no playback information available") + logger.error("Attempted to play item but no playback information available") } } label: { Label(L10n.playFromBeginning, systemImage: "gobackward") diff --git a/Swiftfin tvOS/Views/VideoPlayer/LiveTVPlayerViewController.swift b/Swiftfin tvOS/Views/VideoPlayer/LiveTVPlayerViewController.swift index 500090db..99e34836 100644 --- a/Swiftfin tvOS/Views/VideoPlayer/LiveTVPlayerViewController.swift +++ b/Swiftfin tvOS/Views/VideoPlayer/LiveTVPlayerViewController.swift @@ -10,6 +10,7 @@ import AVFoundation import AVKit import Combine import Defaults +import Factory import JellyfinAPI import MediaPlayer import SwiftUI @@ -20,6 +21,9 @@ import UIKit class LiveTVPlayerViewController: UIViewController { + @Injected(LogManager.service) + private var logger + // MARK: variables private var viewModel: VideoPlayerViewModel @@ -476,11 +480,11 @@ extension LiveTVPlayerViewController { viewModel = newViewModel if viewModel.streamType == .direct { - LogManager.log.debug("Player set up with direct play stream for item: \(viewModel.item.id ?? .emptyDash)") + logger.debug("Player set up with direct play stream for item: \(viewModel.item.id ?? .emptyDash)") } else if viewModel.streamType == .transcode && Defaults[.Experimental.forceDirectPlay] { - LogManager.log.debug("Player set up with forced direct stream for item: \(viewModel.item.id ?? .emptyDash)") + logger.debug("Player set up with forced direct stream for item: \(viewModel.item.id ?? .emptyDash)") } else { - LogManager.log.debug("Player set up with transcoded stream for item: \(viewModel.item.id ?? .emptyDash)") + logger.debug("Player set up with transcoded stream for item: \(viewModel.item.id ?? .emptyDash)") } } diff --git a/Swiftfin tvOS/Views/VideoPlayer/VLCPlayerViewController.swift b/Swiftfin tvOS/Views/VideoPlayer/VLCPlayerViewController.swift index 98bae2df..5158912f 100644 --- a/Swiftfin tvOS/Views/VideoPlayer/VLCPlayerViewController.swift +++ b/Swiftfin tvOS/Views/VideoPlayer/VLCPlayerViewController.swift @@ -10,6 +10,7 @@ import AVFoundation import AVKit import Combine import Defaults +import Factory import JellyfinAPI import MediaPlayer import SwiftUI @@ -20,6 +21,9 @@ import UIKit class VLCPlayerViewController: UIViewController { + @Injected(LogManager.service) + private var logger + // MARK: variables private var viewModel: VideoPlayerViewModel @@ -476,11 +480,11 @@ extension VLCPlayerViewController { viewModel = newViewModel if viewModel.streamType == .direct { - LogManager.log.debug("Player set up with direct play stream for item: \(viewModel.item.id ?? .emptyDash)") + logger.debug("Player set up with direct play stream for item: \(viewModel.item.id ?? .emptyDash)") } else if viewModel.streamType == .transcode && Defaults[.Experimental.forceDirectPlay] { - LogManager.log.debug("Player set up with forced direct stream for item: \(viewModel.item.id ?? .emptyDash)") + logger.debug("Player set up with forced direct stream for item: \(viewModel.item.id ?? .emptyDash)") } else { - LogManager.log.debug("Player set up with transcoded stream for item: \(viewModel.item.id ?? .emptyDash)") + logger.debug("Player set up with transcoded stream for item: \(viewModel.item.id ?? .emptyDash)") } } diff --git a/Swiftfin.xcodeproj/project.pbxproj b/Swiftfin.xcodeproj/project.pbxproj index aa6fe438..39e91c92 100644 --- a/Swiftfin.xcodeproj/project.pbxproj +++ b/Swiftfin.xcodeproj/project.pbxproj @@ -410,6 +410,8 @@ E19169CE272514760085832A /* HTTPScheme.swift in Sources */ = {isa = PBXBuildFile; fileRef = E19169CD272514760085832A /* HTTPScheme.swift */; }; E19169CF272514760085832A /* HTTPScheme.swift in Sources */ = {isa = PBXBuildFile; fileRef = E19169CD272514760085832A /* HTTPScheme.swift */; }; E192608028D28AAD002314B4 /* UserProfileButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = E192607F28D28AAD002314B4 /* UserProfileButton.swift */; }; + E192608328D2D0DB002314B4 /* Factory in Frameworks */ = {isa = PBXBuildFile; productRef = E192608228D2D0DB002314B4 /* Factory */; }; + E192608828D2E5F0002314B4 /* Factory in Frameworks */ = {isa = PBXBuildFile; productRef = E192608728D2E5F0002314B4 /* Factory */; }; E1937A3B288E54AD00CB80AA /* BaseItemDto+Images.swift in Sources */ = {isa = PBXBuildFile; fileRef = E1937A3A288E54AD00CB80AA /* BaseItemDto+Images.swift */; }; E1937A3C288E54AD00CB80AA /* BaseItemDto+Images.swift in Sources */ = {isa = PBXBuildFile; fileRef = E1937A3A288E54AD00CB80AA /* BaseItemDto+Images.swift */; }; E1937A3E288F0D3D00CB80AA /* UIScreenExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = E1937A3D288F0D3D00CB80AA /* UIScreenExtensions.swift */; }; @@ -1027,6 +1029,7 @@ E12186DE2718F1C50010884C /* Defaults in Frameworks */, E1347DB6279E3CA500BC6161 /* Puppy in Frameworks */, 53ABFDED26799D7700886593 /* ActivityIndicator in Frameworks */, + E192608828D2E5F0002314B4 /* Factory in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -1045,6 +1048,7 @@ 62666E0327E5017100EC0ECD /* CoreMedia.framework in Frameworks */, 62666E0627E5017A00EC0ECD /* CoreVideo.framework in Frameworks */, E10EAA4D277BB716000269ED /* Sliders in Frameworks */, + E192608328D2D0DB002314B4 /* Factory in Frameworks */, 62C29E9C26D0FE4200C1D2E7 /* Stinsen in Frameworks */, 62666E0227E5016D00EC0ECD /* CoreGraphics.framework in Frameworks */, 62666E1027E501B400EC0ECD /* VideoToolbox.framework in Frameworks */, @@ -2269,6 +2273,7 @@ E13AF3B928A0C598009093AB /* NukeUI */, E13AF3BB28A0C59E009093AB /* BlurHashKit */, E1734D7D28B9578100C66367 /* CollectionView */, + E192608728D2E5F0002314B4 /* Factory */, ); productName = "JellyfinPlayer tvOS"; productReference = 535870602669D21600D05A09 /* Swiftfin tvOS.app */; @@ -2308,6 +2313,7 @@ E19E6E0628A0B958005C10C8 /* NukeUI */, E19E6E0928A0BEFF005C10C8 /* BlurHashKit */, E1734D7B28B9577700C66367 /* CollectionView */, + E192608228D2D0DB002314B4 /* Factory */, ); productName = JellyfinPlayer; productReference = 5377CBF1263B596A003A4E83 /* Swiftfin iOS.app */; @@ -2373,6 +2379,7 @@ E19E6E0328A0B958005C10C8 /* XCRemoteSwiftPackageReference "Nuke" */, E19E6E0828A0BEFF005C10C8 /* XCRemoteSwiftPackageReference "BlurHashKit" */, E1734D7A28B9577700C66367 /* XCRemoteSwiftPackageReference "CollectionView" */, + E192608128D2D0DB002314B4 /* XCRemoteSwiftPackageReference "Factory" */, ); productRefGroup = 5377CBF2263B596A003A4E83 /* Products */; projectDirPath = ""; @@ -3447,6 +3454,14 @@ kind = branch; }; }; + E192608128D2D0DB002314B4 /* XCRemoteSwiftPackageReference "Factory" */ = { + isa = XCRemoteSwiftPackageReference; + repositoryURL = "https://github.com/hmlongco/Factory"; + requirement = { + kind = upToNextMajorVersion; + minimumVersion = 1.0.0; + }; + }; E19E6E0328A0B958005C10C8 /* XCRemoteSwiftPackageReference "Nuke" */ = { isa = XCRemoteSwiftPackageReference; repositoryURL = "https://github.com/kean/Nuke"; @@ -3609,6 +3624,16 @@ package = E10EAA43277BB646000269ED /* XCRemoteSwiftPackageReference "jellyfin-sdk-swift" */; productName = JellyfinAPI; }; + E192608228D2D0DB002314B4 /* Factory */ = { + isa = XCSwiftPackageProductDependency; + package = E192608128D2D0DB002314B4 /* XCRemoteSwiftPackageReference "Factory" */; + productName = Factory; + }; + E192608728D2E5F0002314B4 /* Factory */ = { + isa = XCSwiftPackageProductDependency; + package = E192608128D2D0DB002314B4 /* XCRemoteSwiftPackageReference "Factory" */; + productName = Factory; + }; E19E6E0428A0B958005C10C8 /* Nuke */ = { isa = XCSwiftPackageProductDependency; package = E19E6E0328A0B958005C10C8 /* XCRemoteSwiftPackageReference "Nuke" */; diff --git a/Swiftfin.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved b/Swiftfin.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved index 2d005916..9330ec0d 100644 --- a/Swiftfin.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved +++ b/Swiftfin.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved @@ -54,6 +54,15 @@ "version" : "6.3.0" } }, + { + "identity" : "factory", + "kind" : "remoteSourceControl", + "location" : "https://github.com/hmlongco/Factory", + "state" : { + "revision" : "8557426f3286e20b631ecdac8115242f888656e0", + "version" : "1.2.8" + } + }, { "identity" : "jellyfin-sdk-swift", "kind" : "remoteSourceControl", diff --git a/Swiftfin/App/AppDelegate.swift b/Swiftfin/App/AppDelegate.swift index d86d6261..e5e5488d 100644 --- a/Swiftfin/App/AppDelegate.swift +++ b/Swiftfin/App/AppDelegate.swift @@ -20,7 +20,6 @@ class AppDelegate: NSObject, UIApplicationDelegate { // Lazily initialize datastack _ = SwiftfinStore.dataStack - LogManager.setup() let audioSession = AVAudioSession.sharedInstance() do { diff --git a/Swiftfin/Views/ItemView/Components/PlayButton.swift b/Swiftfin/Views/ItemView/Components/PlayButton.swift index 90f801d5..f765ff8e 100644 --- a/Swiftfin/Views/ItemView/Components/PlayButton.swift +++ b/Swiftfin/Views/ItemView/Components/PlayButton.swift @@ -6,12 +6,15 @@ // Copyright (c) 2022 Jellyfin & Jellyfin Contributors // +import Factory import SwiftUI extension ItemView { struct PlayButton: View { + @Injected(LogManager.service) + private var logger @EnvironmentObject private var itemRouter: ItemCoordinator.Router @ObservedObject @@ -22,7 +25,7 @@ extension ItemView { if let selectedVideoPlayerViewModel = viewModel.selectedVideoPlayerViewModel { itemRouter.route(to: \.videoPlayer, selectedVideoPlayerViewModel) } else { - LogManager.log.error("Attempted to play item but no playback information available") + logger.error("Attempted to play item but no playback information available") } } label: { ZStack { @@ -47,7 +50,7 @@ extension ItemView { selectedVideoPlayerViewModel.injectCustomValues(startFromBeginning: true) itemRouter.route(to: \.videoPlayer, selectedVideoPlayerViewModel) } else { - LogManager.log.error("Attempted to play item but no playback information available") + logger.error("Attempted to play item but no playback information available") } } label: { Label(L10n.playFromBeginning, systemImage: "gobackward") diff --git a/Swiftfin/Views/VideoPlayer/LiveTVPlayerViewController.swift b/Swiftfin/Views/VideoPlayer/LiveTVPlayerViewController.swift index e4b36067..0bf9c072 100644 --- a/Swiftfin/Views/VideoPlayer/LiveTVPlayerViewController.swift +++ b/Swiftfin/Views/VideoPlayer/LiveTVPlayerViewController.swift @@ -10,6 +10,7 @@ import AVFoundation import AVKit import Combine import Defaults +import Factory import JellyfinAPI import MediaPlayer import MobileVLCKit @@ -19,6 +20,10 @@ import UIKit // TODO: Look at making the VLC player layer a view class LiveTVPlayerViewController: UIViewController { + + @Injected(LogManager.service) + private var logger + // MARK: variables private var viewModel: VideoPlayerViewModel @@ -532,11 +537,11 @@ extension LiveTVPlayerViewController { viewModel = newViewModel if viewModel.streamType == .direct { - LogManager.log.debug("Player set up with direct play stream for item: \(viewModel.item.id ?? .emptyDash)") + logger.debug("Player set up with direct play stream for item: \(viewModel.item.id ?? .emptyDash)") } else if viewModel.streamType == .transcode && Defaults[.Experimental.forceDirectPlay] { - LogManager.log.debug("Player set up with forced direct stream for item: \(viewModel.item.id ?? .emptyDash)") + logger.debug("Player set up with forced direct stream for item: \(viewModel.item.id ?? .emptyDash)") } else { - LogManager.log.debug("Player set up with transcoded stream for item: \(viewModel.item.id ?? .emptyDash)") + logger.debug("Player set up with transcoded stream for item: \(viewModel.item.id ?? .emptyDash)") } } diff --git a/Swiftfin/Views/VideoPlayer/VLCPlayerViewController.swift b/Swiftfin/Views/VideoPlayer/VLCPlayerViewController.swift index 92e58782..8721f522 100644 --- a/Swiftfin/Views/VideoPlayer/VLCPlayerViewController.swift +++ b/Swiftfin/Views/VideoPlayer/VLCPlayerViewController.swift @@ -10,6 +10,7 @@ import AVFoundation import AVKit import Combine import Defaults +import Factory import JellyfinAPI import MediaPlayer import MobileVLCKit @@ -19,6 +20,10 @@ import UIKit // TODO: Look at making the VLC player layer a view class VLCPlayerViewController: UIViewController { + + @Injected(LogManager.service) + private var logger + // MARK: variables private var viewModel: VideoPlayerViewModel @@ -615,11 +620,11 @@ extension VLCPlayerViewController { viewModel = newViewModel if viewModel.streamType == .direct { - LogManager.log.debug("Player set up with direct play stream for item: \(viewModel.item.id ?? .emptyDash)") + logger.debug("Player set up with direct play stream for item: \(viewModel.item.id ?? .emptyDash)") } else if viewModel.streamType == .transcode && Defaults[.Experimental.forceDirectPlay] { - LogManager.log.debug("Player set up with forced direct stream for item: \(viewModel.item.id ?? .emptyDash)") + logger.debug("Player set up with forced direct stream for item: \(viewModel.item.id ?? .emptyDash)") } else { - LogManager.log.debug("Player set up with transcoded stream for item: \(viewModel.item.id ?? .emptyDash)") + logger.debug("Player set up with transcoded stream for item: \(viewModel.item.id ?? .emptyDash)") } }