From e5d35a3782ce57886484e9b03bdbc1170eddceef Mon Sep 17 00:00:00 2001 From: Seiike <122684677+Seeike@users.noreply.github.com> Date: Sun, 19 Jan 2025 12:51:03 +0100 Subject: [PATCH 1/5] Update Localizable.xcstrings --- Sora/Localizable.xcstrings | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/Sora/Localizable.xcstrings b/Sora/Localizable.xcstrings index 4edc2fe..188585c 100644 --- a/Sora/Localizable.xcstrings +++ b/Sora/Localizable.xcstrings @@ -60,6 +60,12 @@ "value" : "Autor: %@" } }, + "sk" : { + "stringUnit" : { + "state" : "translated", + "value" : "Autor: %@" + } + }, "sq" : { "stringUnit" : { "state" : "translated", @@ -67,6 +73,12 @@ } } } + }, + "Clear Logs" : { + + }, + "Copy to Clipboard" : { + }, "Copy URL" : { "localizations" : { @@ -99,6 +111,9 @@ } } } + }, + "Debug" : { + }, "Delete" : { "localizations" : { @@ -307,6 +322,9 @@ } } } + }, + "Logs" : { + }, "Mark as Watched" : { "localizations" : { From 292c5ca770f5371ce56f027cc94bffbb8cfa0ce7 Mon Sep 17 00:00:00 2001 From: Seiike <122684677+Seeike@users.noreply.github.com> Date: Mon, 20 Jan 2025 02:17:03 +0100 Subject: [PATCH 2/5] Added logging for js modules, and for modules(adding/removing) --- Sora/Utils/Loaders/JSController.swift | 54 +++++++++++++++------------ Sora/Utils/Logger/Logger.swift | 4 +- Sora/Utils/Modules/Modules.swift | 6 ++- 3 files changed, 37 insertions(+), 27 deletions(-) diff --git a/Sora/Utils/Loaders/JSController.swift b/Sora/Utils/Loaders/JSController.swift index fbac42f..3887b9a 100644 --- a/Sora/Utils/Loaders/JSController.swift +++ b/Sora/Utils/Loaders/JSController.swift @@ -17,45 +17,52 @@ class JSController: ObservableObject { private func setupContext() { let logFunction: @convention(block) (String) -> Void = { message in - Logger.shared.log("JavaScript log: \(message)") + Logger.shared.log("JavaScript log: \(message)", level: .info) } context.setObject(logFunction, forKeyedSubscript: "log" as NSString) - + + // Override console.log + let consoleLogOverride = """ + var console = { + log: function() { + var message = Array.from(arguments).map(String).join(" "); + log(message); + } + }; + """ + context.evaluateScript(consoleLogOverride) + + // Add other JavaScript functions like fetchNative let fetchNativeFunction: @convention(block) (String, JSValue, JSValue) -> Void = { urlString, resolve, reject in guard let url = URL(string: urlString) else { - Logger.shared.log("Invalid URL") + Logger.shared.log("Invalid URL", level: .error) reject.call(withArguments: ["Invalid URL"]) return } - let task = URLSession.custom.dataTask(with: url) { data, _, error in + let task = URLSession.shared.dataTask(with: url) { data, _, error in if let error = error { - Logger.shared.log("Network error in fetchNativeFunction: \(error.localizedDescription)") + Logger.shared.log("Network error in fetchNative: \(error.localizedDescription)", level: .error) reject.call(withArguments: [error.localizedDescription]) return } - guard let data = data else { - Logger.shared.log("No data in response") - reject.call(withArguments: ["No data"]) + guard let data = data, let text = String(data: data, encoding: .utf8) else { + Logger.shared.log("Failed to decode response data", level: .error) + reject.call(withArguments: ["Failed to decode response data"]) return } - if let text = String(data: data, encoding: .utf8) { - resolve.call(withArguments: [text]) - } else { - Logger.shared.log("Unable to decode data to text") - reject.call(withArguments: ["Unable to decode data"]) - } + resolve.call(withArguments: [text]) } task.resume() } context.setObject(fetchNativeFunction, forKeyedSubscript: "fetchNative" as NSString) - + let fetchDefinition = """ - function fetch(url) { - return new Promise(function(resolve, reject) { - fetchNative(url, resolve, reject); - }); - } - """ + function fetch(url) { + return new Promise(function(resolve, reject) { + fetchNative(url, resolve, reject); + }); + } + """ context.evaluateScript(fetchDefinition) } @@ -182,6 +189,7 @@ class JSController: ObservableObject { if let parseFunction = self.context.objectForKeyedSubscript("extractStreamUrl"), let streamUrl = parseFunction.call(withArguments: [html]).toString() { + Logger.shared.log("Staring stream from: \(streamUrl)", level: .info) DispatchQueue.main.async { completion(streamUrl) } @@ -230,13 +238,13 @@ class JSController: ObservableObject { } } else { - Logger.shared.log("Failed to parse JSON") + Logger.shared.log("Failed to parse JSON", level: .error) DispatchQueue.main.async { completion([]) } } } catch { - Logger.shared.log("JSON parsing error: \(error)") + Logger.shared.log("JSON parsing error: \(error)", level: .error) DispatchQueue.main.async { completion([]) } diff --git a/Sora/Utils/Logger/Logger.swift b/Sora/Utils/Logger/Logger.swift index f2a9a23..849dd7e 100644 --- a/Sora/Utils/Logger/Logger.swift +++ b/Sora/Utils/Logger/Logger.swift @@ -35,7 +35,7 @@ class Logger { let dateFormatter = DateFormatter() dateFormatter.dateFormat = "yyyy-MM-dd HH:mm:ss" return logs.map { "[\(dateFormatter.string(from: $0.timestamp))] [\($0.level.rawValue)] \($0.message)" } - .joined(separator: "\n---\n") + .joined(separator: "\n----------------------------------------------------------\n") } func clearLogs() { @@ -82,4 +82,4 @@ class Logger { } } } -} \ No newline at end of file +} diff --git a/Sora/Utils/Modules/Modules.swift b/Sora/Utils/Modules/Modules.swift index 413a872..cbf96ea 100644 --- a/Sora/Utils/Modules/Modules.swift +++ b/Sora/Utils/Modules/Modules.swift @@ -103,19 +103,21 @@ class ModuleManager: ObservableObject { DispatchQueue.main.async { self.modules.append(module) self.saveModules() + Logger.shared.log("Added module: \(module.metadata.sourceName)") } return module } - + func deleteModule(_ module: ScrapingModule) { let localUrl = getDocumentsDirectory().appendingPathComponent(module.localPath) try? fileManager.removeItem(at: localUrl) modules.removeAll { $0.id == module.id } saveModules() + Logger.shared.log("Deleted module: \(module.metadata.sourceName)") } - + func getModuleContent(_ module: ScrapingModule) throws -> String { let localUrl = getDocumentsDirectory().appendingPathComponent(module.localPath) return try String(contentsOf: localUrl, encoding: .utf8) From 65ee766579db54a65fbc651ccae715246a0d0271 Mon Sep 17 00:00:00 2001 From: Seiike <122684677+Seeike@users.noreply.github.com> Date: Wed, 22 Jan 2025 01:32:23 +0100 Subject: [PATCH 3/5] WAY better logging --- Sora.xcodeproj/project.pbxproj | 4 + Sora/Utils/Loaders/JSController.swift | 78 +++++++-------- Sora/Utils/Logger/Logger.swift | 50 ++++------ Sora/Views/LibraryView/LibraryManager.swift | 4 +- Sora/Views/MediaInfoView/MediaInfoView.swift | 2 +- .../SettingsSubViews/SettingsViewLogger.swift | 36 ++++--- .../SettingsViewLoggerFilter.swift | 94 +++++++++++++++++++ 7 files changed, 177 insertions(+), 91 deletions(-) create mode 100644 Sora/Views/SettingsView/SettingsSubViews/SettingsViewLoggerFilter.swift diff --git a/Sora.xcodeproj/project.pbxproj b/Sora.xcodeproj/project.pbxproj index 9b73dd4..a408626 100644 --- a/Sora.xcodeproj/project.pbxproj +++ b/Sora.xcodeproj/project.pbxproj @@ -32,6 +32,7 @@ 13EA2BD72D32D97400C1EBD7 /* MusicProgressSlider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 13EA2BD42D32D97400C1EBD7 /* MusicProgressSlider.swift */; }; 13EA2BD92D32D98400C1EBD7 /* NormalPlayer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 13EA2BD82D32D98400C1EBD7 /* NormalPlayer.swift */; }; 13EA2BDC2D32D9FF00C1EBD7 /* MiruDataStruct.swift in Sources */ = {isa = PBXBuildFile; fileRef = 13EA2BDB2D32D9FF00C1EBD7 /* MiruDataStruct.swift */; }; + 1E9FF1D32D403E49008AC100 /* SettingsViewLoggerFilter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1E9FF1D22D403E42008AC100 /* SettingsViewLoggerFilter.swift */; }; 1EE1DA962D3553C2002AEF73 /* Localizable.xcstrings in Resources */ = {isa = PBXBuildFile; fileRef = 1EE1DA952D3553C2002AEF73 /* Localizable.xcstrings */; }; /* End PBXBuildFile section */ @@ -62,6 +63,7 @@ 13EA2BD42D32D97400C1EBD7 /* MusicProgressSlider.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MusicProgressSlider.swift; sourceTree = ""; }; 13EA2BD82D32D98400C1EBD7 /* NormalPlayer.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NormalPlayer.swift; sourceTree = ""; }; 13EA2BDB2D32D9FF00C1EBD7 /* MiruDataStruct.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MiruDataStruct.swift; sourceTree = ""; }; + 1E9FF1D22D403E42008AC100 /* SettingsViewLoggerFilter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsViewLoggerFilter.swift; sourceTree = ""; }; 1EE1DA952D3553C2002AEF73 /* Localizable.xcstrings */ = {isa = PBXFileReference; lastKnownFileType = text.json.xcstrings; path = Localizable.xcstrings; sourceTree = ""; }; /* End PBXFileReference section */ @@ -141,6 +143,7 @@ 133D7C832D2BE2630075467E /* SettingsSubViews */ = { isa = PBXGroup; children = ( + 1E9FF1D22D403E42008AC100 /* SettingsViewLoggerFilter.swift */, 1399FAD32D3AB38C00E97C31 /* SettingsViewLogger.swift */, 133D7C842D2BE2630075467E /* SettingsViewModule.swift */, ); @@ -340,6 +343,7 @@ 1399FAD62D3AB3DB00E97C31 /* Logger.swift in Sources */, 133D7C902D2BE2640075467E /* SettingsView.swift in Sources */, 13EA2BD72D32D97400C1EBD7 /* MusicProgressSlider.swift in Sources */, + 1E9FF1D32D403E49008AC100 /* SettingsViewLoggerFilter.swift in Sources */, 13EA2BD92D32D98400C1EBD7 /* NormalPlayer.swift in Sources */, 133D7C932D2BE2640075467E /* Modules.swift in Sources */, 133D7C702D2BE2500075467E /* ContentView.swift in Sources */, diff --git a/Sora/Utils/Loaders/JSController.swift b/Sora/Utils/Loaders/JSController.swift index e2e0559..573a712 100644 --- a/Sora/Utils/Loaders/JSController.swift +++ b/Sora/Utils/Loaders/JSController.swift @@ -17,7 +17,7 @@ class JSController: ObservableObject { private func setupContext() { let logFunction: @convention(block) (String) -> Void = { message in - Logger.shared.log("JavaScript log: \(message)", level: .info) + Logger.shared.log("JavaScript log: \(message)", type: "Debug") } context.setObject(logFunction, forKeyedSubscript: "log" as NSString) @@ -35,18 +35,18 @@ class JSController: ObservableObject { // Add other JavaScript functions like fetchNative let fetchNativeFunction: @convention(block) (String, JSValue, JSValue) -> Void = { urlString, resolve, reject in guard let url = URL(string: urlString) else { - Logger.shared.log("Invalid URL", level: .error) + Logger.shared.log("Invalid URL", type: "Error") reject.call(withArguments: ["Invalid URL"]) return } let task = URLSession.shared.dataTask(with: url) { data, _, error in if let error = error { - Logger.shared.log("Network error in fetchNative: \(error.localizedDescription)", level: .error) + Logger.shared.log("Network error in fetchNative: \(error.localizedDescription)", type: "Error") reject.call(withArguments: [error.localizedDescription]) return } guard let data = data, let text = String(data: data, encoding: .utf8) else { - Logger.shared.log("Failed to decode response data", level: .error) + Logger.shared.log("Failed to decode response data", type: "Error") reject.call(withArguments: ["Failed to decode response data"]) return } @@ -84,18 +84,18 @@ class JSController: ObservableObject { guard let self = self else { return } if let error = error { - Logger.shared.log("Network error: \(error)") + Logger.shared.log("Network error: \(error)", type: "Error") DispatchQueue.main.async { completion([]) } return } guard let data = data, let html = String(data: data, encoding: .utf8) else { - Logger.shared.log("Failed to decode HTML") + Logger.shared.log("Failed to decode HTML", type: "Error") DispatchQueue.main.async { completion([]) } return } - Logger.shared.log(html) + Logger.shared.log(html, type: "Debug") if let parseFunction = self.context.objectForKeyedSubscript("searchResults"), let results = parseFunction.call(withArguments: [html]).toArray() as? [[String: String]] { let resultItems = results.map { item in @@ -109,7 +109,7 @@ class JSController: ObservableObject { completion(resultItems) } } else { - Logger.shared.log("Failed to parse results") + Logger.shared.log("Failed to parse results", type: "Error") DispatchQueue.main.async { completion([]) } } }.resume() @@ -125,13 +125,13 @@ class JSController: ObservableObject { guard let self = self else { return } if let error = error { - Logger.shared.log("Network error: \(error)") + Logger.shared.log("Network error: \(error)", type: "Error") DispatchQueue.main.async { completion([], []) } return } guard let data = data, let html = String(data: data, encoding: .utf8) else { - Logger.shared.log("Failed to decode HTML") + Logger.shared.log("Failed to decode HTML", type: "Error") DispatchQueue.main.async { completion([], []) } return } @@ -139,7 +139,7 @@ class JSController: ObservableObject { var resultItems: [MediaItem] = [] var episodeLinks: [EpisodeLink] = [] - Logger.shared.log(html) + Logger.shared.log(html, type: "Debug") if let parseFunction = self.context.objectForKeyedSubscript("extractDetails"), let results = parseFunction.call(withArguments: [html]).toArray() as? [[String: String]] { resultItems = results.map { item in @@ -150,7 +150,7 @@ class JSController: ObservableObject { ) } } else { - Logger.shared.log("Failed to parse results") + Logger.shared.log("Failed to parse results", type: "Error") } if let fetchEpisodesFunction = self.context.objectForKeyedSubscript("extractEpisodes"), @@ -178,26 +178,26 @@ class JSController: ObservableObject { guard let self = self else { return } if let error = error { - Logger.shared.log("Network error: \(error)") + Logger.shared.log("Network error: \(error)", type: "Error") DispatchQueue.main.async { completion(nil) } return } guard let data = data, let html = String(data: data, encoding: .utf8) else { - Logger.shared.log("Failed to decode HTML") + Logger.shared.log("Failed to decode HTML", type: "Error") DispatchQueue.main.async { completion(nil) } return } - Logger.shared.log(html) + Logger.shared.log(html, type: "Debug") if let parseFunction = self.context.objectForKeyedSubscript("extractStreamUrl"), let streamUrl = parseFunction.call(withArguments: [html]).toString() { - Logger.shared.log("Staring stream from: \(streamUrl)", level: .info) + Logger.shared.log("Staring stream from: \(streamUrl)", type: "Stream") DispatchQueue.main.async { completion(streamUrl) } } else { - Logger.shared.log("Failed to extract stream URL") + Logger.shared.log("Failed to extract stream URL", type: "Error") DispatchQueue.main.async { completion(nil) } } }.resume() @@ -205,27 +205,27 @@ class JSController: ObservableObject { func fetchJsSearchResults(keyword: String, module: ScrapingModule, completion: @escaping ([SearchItem]) -> Void) { if let exception = context.exception { - Logger.shared.log("JavaScript exception: \(exception)") + Logger.shared.log("JavaScript exception: \(exception)", type: "Error") completion([]) return } guard let searchResultsFunction = context.objectForKeyedSubscript("searchResults") else { - Logger.shared.log("No JavaScript function searchResults found") + Logger.shared.log("No JavaScript function searchResults found", type: "Error") completion([]) return } let promiseValue = searchResultsFunction.call(withArguments: [keyword]) guard let promise = promiseValue else { - Logger.shared.log("searchResults did not return a Promise") + Logger.shared.log("searchResults did not return a Promise", type: "Error") completion([]) return } let thenBlock: @convention(block) (JSValue) -> Void = { result in - Logger.shared.log(result.toString()) + Logger.shared.log(result.toString(), type: "Debug") if let jsonString = result.toString(), let data = jsonString.data(using: .utf8) { do { @@ -242,13 +242,13 @@ class JSController: ObservableObject { } } else { - Logger.shared.log("Failed to parse JSON", level: .error) + Logger.shared.log("Failed to parse JSON", type: "Error") DispatchQueue.main.async { completion([]) } } } catch { - Logger.shared.log("JSON parsing error: \(error)", level: .error) + Logger.shared.log("JSON parsing error: \(error)", type: "Error") DispatchQueue.main.async { completion([]) } @@ -262,7 +262,7 @@ class JSController: ObservableObject { } let catchBlock: @convention(block) (JSValue) -> Void = { error in - Logger.shared.log("Promise rejected: \(String(describing: error.toString()))") + Logger.shared.log("Promise rejected: \(String(describing: error.toString()))", type: "Error") DispatchQueue.main.async { completion([]) } @@ -282,19 +282,19 @@ class JSController: ObservableObject { } if let exception = context.exception { - Logger.shared.log("JavaScript exception: \(exception)") + Logger.shared.log("JavaScript exception: \(exception)", type: "Error") completion([], []) return } guard let extractDetailsFunction = context.objectForKeyedSubscript("extractDetails") else { - Logger.shared.log("No JavaScript function extractDetails found") + Logger.shared.log("No JavaScript function extractDetails found", type: "Error") completion([], []) return } guard let extractEpisodesFunction = context.objectForKeyedSubscript("extractEpisodes") else { - Logger.shared.log("No JavaScript function extractEpisodes found") + Logger.shared.log("No JavaScript function extractEpisodes found", type: "Error") completion([], []) return } @@ -304,14 +304,14 @@ class JSController: ObservableObject { let promiseValueDetails = extractDetailsFunction.call(withArguments: [url.absoluteString]) guard let promiseDetails = promiseValueDetails else { - Logger.shared.log("extractDetails did not return a Promise") + Logger.shared.log("extractDetails did not return a Promise", type: "Error") completion([], []) return } let thenBlockDetails: @convention(block) (JSValue) -> Void = { result in - Logger.shared.log(result.toString()) + Logger.shared.log(result.toString(), type: "Debug") if let jsonOfDetails = result.toString(), let dataDetails = jsonOfDetails.data(using: .utf8) { do { @@ -323,19 +323,19 @@ class JSController: ObservableObject { return MediaItem(description: description, aliases: aliases, airdate: airdate) } } else { - Logger.shared.log("Failed to parse JSON of extractDetails") + Logger.shared.log("Failed to parse JSON of extractDetails", type: "Error") DispatchQueue.main.async { completion([], []) } } } catch { - Logger.shared.log("JSON parsing error of extract details: \(error)") + Logger.shared.log("JSON parsing error of extract details: \(error)", type: "Error") DispatchQueue.main.async { completion([], []) } } } else { - Logger.shared.log("Result is not a string of extractDetails") + Logger.shared.log("Result is not a string of extractDetails", type: "Error") DispatchQueue.main.async { completion([], []) } @@ -343,7 +343,7 @@ class JSController: ObservableObject { } let catchBlockDetails: @convention(block) (JSValue) -> Void = { error in - Logger.shared.log("Promise rejected of extractDetails: \(String(describing: error.toString()))") + Logger.shared.log("Promise rejected of extractDetails: \(String(describing: error.toString()))", type: "Error") DispatchQueue.main.async { completion([], []) } @@ -358,14 +358,14 @@ class JSController: ObservableObject { let promiseValueEpisodes = extractEpisodesFunction.call(withArguments: [url.absoluteString]) guard let promiseEpisodes = promiseValueEpisodes else { - Logger.shared.log("extractEpisodes did not return a Promise") + Logger.shared.log("extractEpisodes did not return a Promise", type: "Error") completion([], []) return } let thenBlockEpisodes: @convention(block) (JSValue) -> Void = { result in - Logger.shared.log(result.toString()) + Logger.shared.log(result.toString(), type: "Debug") if let jsonOfEpisodes = result.toString(), let dataEpisodes = jsonOfEpisodes.data(using: .utf8) { do { @@ -381,19 +381,19 @@ class JSController: ObservableObject { } } else { - Logger.shared.log("Failed to parse JSON of extractEpisodes") + Logger.shared.log("Failed to parse JSON of extractEpisodes", type: "Error") DispatchQueue.main.async { completion([], []) } } } catch { - Logger.shared.log("JSON parsing error of extractEpisodes: \(error)") + Logger.shared.log("JSON parsing error of extractEpisodes: \(error)", type: "Error") DispatchQueue.main.async { completion([], []) } } } else { - Logger.shared.log("Result is not a string of extractEpisodes") + Logger.shared.log("Result is not a string of extractEpisodes", type: "Error") DispatchQueue.main.async { completion([], []) } @@ -401,7 +401,7 @@ class JSController: ObservableObject { } let catchBlockEpisodes: @convention(block) (JSValue) -> Void = { error in - Logger.shared.log("Promise rejected of extractEpisodes: \(String(describing: error.toString()))") + Logger.shared.log("Promise rejected of extractEpisodes: \(String(describing: error.toString()))", type: "Error") DispatchQueue.main.async { completion([], []) } diff --git a/Sora/Utils/Logger/Logger.swift b/Sora/Utils/Logger/Logger.swift index 849dd7e..e752db7 100644 --- a/Sora/Utils/Logger/Logger.swift +++ b/Sora/Utils/Logger/Logger.swift @@ -10,31 +10,34 @@ import Foundation class Logger { static let shared = Logger() - enum LogLevel: String { - case info = "INFO" - case warning = "WARNING" - case error = "ERROR" + struct LogEntry { + let message: String + let type: String + let timestamp: Date } - private var logs: [(level: LogLevel, message: String, timestamp: Date)] = [] + private var logs: [LogEntry] = [] private let logFileURL: URL + private let logFilterViewModel = LogFilterViewModel.shared // Use shared instance private init() { let documentDirectory = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first! logFileURL = documentDirectory.appendingPathComponent("logs.txt") - loadLogs() } - func log(_ message: String, level: LogLevel = .info) { - let entry = (level: level, message: message, timestamp: Date()) + func log(_ message: String, type: String = "General") { + // Check if the log type is enabled + guard logFilterViewModel.isFilterEnabled(for: type) else { return } + + let entry = LogEntry(message: message, type: type, timestamp: Date()) logs.append(entry) saveLogToFile(entry) } func getLogs() -> String { let dateFormatter = DateFormatter() - dateFormatter.dateFormat = "yyyy-MM-dd HH:mm:ss" - return logs.map { "[\(dateFormatter.string(from: $0.timestamp))] [\($0.level.rawValue)] \($0.message)" } + dateFormatter.dateFormat = "dd-MM-yyyy HH:mm:ss" + return logs.map { "[\(dateFormatter.string(from: $0.timestamp))] [\($0.type)] \($0.message)" } .joined(separator: "\n----------------------------------------------------------\n") } @@ -43,32 +46,11 @@ class Logger { try? FileManager.default.removeItem(at: logFileURL) } - private func loadLogs() { - guard let data = try? Data(contentsOf: logFileURL), - let content = String(data: data, encoding: .utf8) else { return } - + private func saveLogToFile(_ log: LogEntry) { let dateFormatter = DateFormatter() - dateFormatter.dateFormat = "yyyy-MM-dd HH:mm:ss" + dateFormatter.dateFormat = "dd-MM-yyyy HH:mm:ss" - content.components(separatedBy: "\n---\n").forEach { line in - let components = line.components(separatedBy: "] [") - guard components.count == 3, - let timestampString = components.first?.dropFirst().trimmingCharacters(in: .whitespaces), - let timestamp = dateFormatter.date(from: timestampString), - let message = components.last?.dropLast() else { return } - - let levelRaw = components[1].trimmingCharacters(in: .whitespaces) - guard let level = LogLevel(rawValue: levelRaw) else { return } - - logs.append((level: level, message: String(message), timestamp: timestamp)) - } - } - - private func saveLogToFile(_ log: (level: LogLevel, message: String, timestamp: Date)) { - let dateFormatter = DateFormatter() - dateFormatter.dateFormat = "yyyy-MM-dd HH:mm:ss" - - let logString = "[\(dateFormatter.string(from: log.timestamp))] [\(log.level.rawValue)] \(log.message)\n---\n" + let logString = "[\(dateFormatter.string(from: log.timestamp))] [\(log.type)] \(log.message)\n---\n" if let data = logString.data(using: .utf8) { if FileManager.default.fileExists(atPath: logFileURL.path) { diff --git a/Sora/Views/LibraryView/LibraryManager.swift b/Sora/Views/LibraryView/LibraryManager.swift index 0f6a9bf..bd71a38 100644 --- a/Sora/Views/LibraryView/LibraryManager.swift +++ b/Sora/Views/LibraryView/LibraryManager.swift @@ -42,7 +42,7 @@ class LibraryManager: ObservableObject { do { bookmarks = try JSONDecoder().decode([LibraryItem].self, from: data) } catch { - Logger.shared.log("Failed to decode bookmarks: \(error.localizedDescription)") + Logger.shared.log("Failed to decode bookmarks: \(error.localizedDescription)", type: "Error") } } @@ -51,7 +51,7 @@ class LibraryManager: ObservableObject { let encoded = try JSONEncoder().encode(bookmarks) UserDefaults.standard.set(encoded, forKey: bookmarksKey) } catch { - Logger.shared.log("Failed to encode bookmarks: \(error.localizedDescription)") + Logger.shared.log("Failed to encode bookmarks: \(error.localizedDescription)", type: "Error") } } diff --git a/Sora/Views/MediaInfoView/MediaInfoView.swift b/Sora/Views/MediaInfoView/MediaInfoView.swift index 3866823..f6155e5 100644 --- a/Sora/Views/MediaInfoView/MediaInfoView.swift +++ b/Sora/Views/MediaInfoView/MediaInfoView.swift @@ -266,7 +266,7 @@ struct MediaInfoView: View { private func openSafariViewController(with urlString: String) { guard let url = URL(string: urlString) else { - Logger.shared.log("Unable to open the webpage") + Logger.shared.log("Unable to open the webpage", type: "Error") return } let safariViewController = SFSafariViewController(url: url) diff --git a/Sora/Views/SettingsView/SettingsSubViews/SettingsViewLogger.swift b/Sora/Views/SettingsView/SettingsSubViews/SettingsViewLogger.swift index aeb83f2..f24ed66 100644 --- a/Sora/Views/SettingsView/SettingsSubViews/SettingsViewLogger.swift +++ b/Sora/Views/SettingsView/SettingsSubViews/SettingsViewLogger.swift @@ -9,7 +9,8 @@ import SwiftUI struct SettingsViewLogger: View { @State private var logs: String = "" - + @StateObject private var filterViewModel = LogFilterViewModel.shared // Use shared instance + var body: some View { VStack { ScrollView { @@ -27,22 +28,27 @@ struct SettingsViewLogger: View { } .toolbar { ToolbarItem(placement: .navigationBarTrailing) { - Menu { - Button(action: { - UIPasteboard.general.string = logs - }) { - Label("Copy to Clipboard", systemImage: "doc.on.doc") + HStack { + Menu { + Button(action: { + UIPasteboard.general.string = logs + }) { + Label("Copy to Clipboard", systemImage: "doc.on.doc") + } + Button(role: .destructive, action: { + Logger.shared.clearLogs() + logs = Logger.shared.getLogs() + }) { + Label("Clear Logs", systemImage: "trash") + } + } label: { + Image(systemName: "ellipsis.circle") + .resizable() + .frame(width: 20, height: 20) } - Button(role: .destructive, action: { - Logger.shared.clearLogs() - logs = Logger.shared.getLogs() - }) { - Label("Clear Logs", systemImage: "trash") + NavigationLink(destination: SettingsViewLoggerFilter(viewModel: filterViewModel)) { + Image(systemName: "slider.horizontal.3") } - } label: { - Image(systemName: "ellipsis.circle") - .resizable() - .frame(width: 20, height: 20) } } } diff --git a/Sora/Views/SettingsView/SettingsSubViews/SettingsViewLoggerFilter.swift b/Sora/Views/SettingsView/SettingsSubViews/SettingsViewLoggerFilter.swift new file mode 100644 index 0000000..859bfcb --- /dev/null +++ b/Sora/Views/SettingsView/SettingsSubViews/SettingsViewLoggerFilter.swift @@ -0,0 +1,94 @@ +// +// SettingsViewLoggerFilter.swift +// Sora +// +// Created by seiike on 21/01/2025. +// + +import SwiftUI + +struct LogFilter: Identifiable, Hashable { + let id = UUID() + let type: String + var isEnabled: Bool + let description: String +} + + +class LogFilterViewModel: ObservableObject { + static let shared = LogFilterViewModel() // Singleton instance + + @Published var filters: [LogFilter] = [] { + didSet { + saveFiltersToUserDefaults() + } + } + + private let userDefaultsKey = "LogFilterStates" + private let hardcodedFilters: [(type: String, description: String, defaultState: Bool)] = [ + ("Global", "Logs for general events and activities.", true), // Turned on by default + ("Stream", "Logs for streaming and video playback.", true), // Turned on by default + ("Error", "Logs for errors and critical issues.", true), // Turned on by default + ("Debug", "Logs for debugging and troubleshooting.", false) // Turned off by default + ] + + private init() { + loadFilters() + } + + func loadFilters() { + if let savedStates = UserDefaults.standard.dictionary(forKey: userDefaultsKey) as? [String: Bool] { + filters = hardcodedFilters.map { + LogFilter( + type: $0.type, + isEnabled: savedStates[$0.type] ?? $0.defaultState, // Use saved state if available, otherwise default + description: $0.description + ) + } + } else { + filters = hardcodedFilters.map { + LogFilter(type: $0.type, isEnabled: $0.defaultState, description: $0.description) + } + } + } + + func toggleFilter(for type: String) { + if let index = filters.firstIndex(where: { $0.type == type }) { + filters[index].isEnabled.toggle() + } + } + + func isFilterEnabled(for type: String) -> Bool { + return filters.first(where: { $0.type == type })?.isEnabled ?? true + } + + private func saveFiltersToUserDefaults() { + let states = filters.reduce(into: [String: Bool]()) { result, filter in + result[filter.type] = filter.isEnabled + } + UserDefaults.standard.set(states, forKey: userDefaultsKey) + } +} + + +struct SettingsViewLoggerFilter: View { + @ObservedObject var viewModel = LogFilterViewModel.shared + + var body: some View { + List { + ForEach($viewModel.filters) { $filter in + VStack(alignment: .leading, spacing: 5) { + Toggle(filter.type, isOn: $filter.isEnabled) + .font(.headline) + + Text(filter.description) + .font(.subheadline) + .foregroundColor(.secondary) + .padding(.leading, 5) // Indent description slightly + } + .padding(.vertical, 5) + } + } + .navigationTitle("Log Filters") + } +} From db2eae38197ed4d20b2853d77ff054758570d5de Mon Sep 17 00:00:00 2001 From: Seiike <122684677+Seeike@users.noreply.github.com> Date: Thu, 23 Jan 2025 02:09:49 +0100 Subject: [PATCH 4/5] more logging and stuff --- Sora/Utils/Loaders/JSController.swift | 81 ++++++++++--------- .../EpisodeCell/EpisodeCell.swift | 6 +- Sora/Views/MediaInfoView/MediaInfoView.swift | 4 +- Sora/Views/SearchView.swift | 3 +- .../SettingsSubViews/SettingsViewLogger.swift | 5 +- .../SettingsViewLoggerFilter.swift | 2 +- Sora/Views/SettingsView/SettingsView.swift | 2 + 7 files changed, 54 insertions(+), 49 deletions(-) diff --git a/Sora/Utils/Loaders/JSController.swift b/Sora/Utils/Loaders/JSController.swift index 6137c9e..37c9616 100644 --- a/Sora/Utils/Loaders/JSController.swift +++ b/Sora/Utils/Loaders/JSController.swift @@ -17,31 +17,31 @@ class JSController: ObservableObject { private func setupContext() { let logFunction: @convention(block) (String) -> Void = { message in - Logger.shared.log("JavaScript log: \(message)") + Logger.shared.log("JavaScript log: \(message)", type: "Debug") } context.setObject(logFunction, forKeyedSubscript: "log" as NSString) let fetchNativeFunction: @convention(block) (String, JSValue, JSValue) -> Void = { urlString, resolve, reject in guard let url = URL(string: urlString) else { - Logger.shared.log("Invalid URL") + Logger.shared.log("Invalid URL",type: "Error") reject.call(withArguments: ["Invalid URL"]) return } let task = URLSession.custom.dataTask(with: url) { data, _, error in if let error = error { - Logger.shared.log("Network error in fetchNativeFunction: \(error.localizedDescription)") + Logger.shared.log("Network error in fetchNativeFunction: \(error.localizedDescription)",type: "Error") reject.call(withArguments: [error.localizedDescription]) return } guard let data = data else { - Logger.shared.log("No data in response") + Logger.shared.log("No data in response",type: "Error") reject.call(withArguments: ["No data"]) return } if let text = String(data: data, encoding: .utf8) { resolve.call(withArguments: [text]) } else { - Logger.shared.log("Unable to decode data to text") + Logger.shared.log("Unable to decode data to text",type: "Error") reject.call(withArguments: ["Unable to decode data"]) } } @@ -77,18 +77,18 @@ class JSController: ObservableObject { guard let self = self else { return } if let error = error { - Logger.shared.log("Network error: \(error)") + Logger.shared.log("Network error: \(error)",type: "Error") DispatchQueue.main.async { completion([]) } return } guard let data = data, let html = String(data: data, encoding: .utf8) else { - Logger.shared.log("Failed to decode HTML") + Logger.shared.log("Failed to decode HTML",type: "Error") DispatchQueue.main.async { completion([]) } return } - Logger.shared.log(html) + Logger.shared.log(html,type: "Debug") if let parseFunction = self.context.objectForKeyedSubscript("searchResults"), let results = parseFunction.call(withArguments: [html]).toArray() as? [[String: String]] { let resultItems = results.map { item in @@ -102,7 +102,7 @@ class JSController: ObservableObject { completion(resultItems) } } else { - Logger.shared.log("Failed to parse results") + Logger.shared.log("Failed to parse results",type: "Error") DispatchQueue.main.async { completion([]) } } }.resume() @@ -118,13 +118,13 @@ class JSController: ObservableObject { guard let self = self else { return } if let error = error { - Logger.shared.log("Network error: \(error)") + Logger.shared.log("Network error: \(error)",type: "Error") DispatchQueue.main.async { completion([], []) } return } guard let data = data, let html = String(data: data, encoding: .utf8) else { - Logger.shared.log("Failed to decode HTML") + Logger.shared.log("Failed to decode HTML",type: "Error") DispatchQueue.main.async { completion([], []) } return } @@ -132,7 +132,7 @@ class JSController: ObservableObject { var resultItems: [MediaItem] = [] var episodeLinks: [EpisodeLink] = [] - Logger.shared.log(html) + Logger.shared.log(html,type: "Debug") if let parseFunction = self.context.objectForKeyedSubscript("extractDetails"), let results = parseFunction.call(withArguments: [html]).toArray() as? [[String: String]] { resultItems = results.map { item in @@ -143,7 +143,7 @@ class JSController: ObservableObject { ) } } else { - Logger.shared.log("Failed to parse results") + Logger.shared.log("Failed to parse results",type: "Error") } if let fetchEpisodesFunction = self.context.objectForKeyedSubscript("extractEpisodes"), @@ -171,25 +171,26 @@ class JSController: ObservableObject { guard let self = self else { return } if let error = error { - Logger.shared.log("Network error: \(error)") + Logger.shared.log("Network error: \(error)",type: "Error") DispatchQueue.main.async { completion(nil) } return } guard let data = data, let html = String(data: data, encoding: .utf8) else { - Logger.shared.log("Failed to decode HTML") + Logger.shared.log("Failed to decode HTML",type: "Error") DispatchQueue.main.async { completion(nil) } return } - Logger.shared.log(html) + Logger.shared.log(html,type: "Debug") if let parseFunction = self.context.objectForKeyedSubscript("extractStreamUrl"), let streamUrl = parseFunction.call(withArguments: [html]).toString() { + Logger.shared.log("Staring stream from: \(streamUrl)", type: "Stream") DispatchQueue.main.async { completion(streamUrl) } } else { - Logger.shared.log("Failed to extract stream URL") + Logger.shared.log("Failed to extract stream URL",type: "Error") DispatchQueue.main.async { completion(nil) } } }.resume() @@ -197,27 +198,27 @@ class JSController: ObservableObject { func fetchJsSearchResults(keyword: String, module: ScrapingModule, completion: @escaping ([SearchItem]) -> Void) { if let exception = context.exception { - Logger.shared.log("JavaScript exception: \(exception)") + Logger.shared.log("JavaScript exception: \(exception)",type: "Error") completion([]) return } guard let searchResultsFunction = context.objectForKeyedSubscript("searchResults") else { - Logger.shared.log("No JavaScript function searchResults found") + Logger.shared.log("No JavaScript function searchResults found",type: "Error") completion([]) return } let promiseValue = searchResultsFunction.call(withArguments: [keyword]) guard let promise = promiseValue else { - Logger.shared.log("searchResults did not return a Promise") + Logger.shared.log("searchResults did not return a Promise",type: "Error") completion([]) return } let thenBlock: @convention(block) (JSValue) -> Void = { result in - Logger.shared.log(result.toString()) + Logger.shared.log(result.toString(),type: "Debug") if let jsonString = result.toString(), let data = jsonString.data(using: .utf8) { do { @@ -234,19 +235,19 @@ class JSController: ObservableObject { } } else { - Logger.shared.log("Failed to parse JSON") + Logger.shared.log("Failed to parse JSON",type: "Error") DispatchQueue.main.async { completion([]) } } } catch { - Logger.shared.log("JSON parsing error: \(error)") + Logger.shared.log("JSON parsing error: \(error)",type: "Error") DispatchQueue.main.async { completion([]) } } } else { - Logger.shared.log("Result is not a string") + Logger.shared.log("Result is not a string",type: "Error") DispatchQueue.main.async { completion([]) } @@ -254,7 +255,7 @@ class JSController: ObservableObject { } let catchBlock: @convention(block) (JSValue) -> Void = { error in - Logger.shared.log("Promise rejected: \(String(describing: error.toString()))") + Logger.shared.log("Promise rejected: \(String(describing: error.toString()))",type: "Error") DispatchQueue.main.async { completion([]) } @@ -274,19 +275,19 @@ class JSController: ObservableObject { } if let exception = context.exception { - Logger.shared.log("JavaScript exception: \(exception)") + Logger.shared.log("JavaScript exception: \(exception)",type: "Error") completion([], []) return } guard let extractDetailsFunction = context.objectForKeyedSubscript("extractDetails") else { - Logger.shared.log("No JavaScript function extractDetails found") + Logger.shared.log("No JavaScript function extractDetails found",type: "Error") completion([], []) return } guard let extractEpisodesFunction = context.objectForKeyedSubscript("extractEpisodes") else { - Logger.shared.log("No JavaScript function extractEpisodes found") + Logger.shared.log("No JavaScript function extractEpisodes found",type: "Error") completion([], []) return } @@ -299,13 +300,13 @@ class JSController: ObservableObject { dispatchGroup.enter() let promiseValueDetails = extractDetailsFunction.call(withArguments: [url.absoluteString]) guard let promiseDetails = promiseValueDetails else { - Logger.shared.log("extractDetails did not return a Promise") + Logger.shared.log("extractDetails did not return a Promise",type: "Error") completion([], []) return } let thenBlockDetails: @convention(block) (JSValue) -> Void = { result in - Logger.shared.log(result.toString()) + Logger.shared.log(result.toString(),type: "Debug") if let jsonOfDetails = result.toString(), let dataDetails = jsonOfDetails.data(using: .utf8) { do { @@ -318,19 +319,19 @@ class JSController: ObservableObject { ) } } else { - Logger.shared.log("Failed to parse JSON of extractDetails") + Logger.shared.log("Failed to parse JSON of extractDetails",type: "Error") } } catch { - Logger.shared.log("JSON parsing error of extract details: \(error)") + Logger.shared.log("JSON parsing error of extract details: \(error)",type: "Error") } } else { - Logger.shared.log("Result is not a string of extractDetails") + Logger.shared.log("Result is not a string of extractDetails",type: "Error") } dispatchGroup.leave() } let catchBlockDetails: @convention(block) (JSValue) -> Void = { error in - Logger.shared.log("Promise rejected of extractDetails: \(String(describing: error.toString()))") + Logger.shared.log("Promise rejected of extractDetails: \(String(describing: error.toString()))",type: "Error") dispatchGroup.leave() } @@ -343,13 +344,13 @@ class JSController: ObservableObject { dispatchGroup.enter() let promiseValueEpisodes = extractEpisodesFunction.call(withArguments: [url.absoluteString]) guard let promiseEpisodes = promiseValueEpisodes else { - Logger.shared.log("extractEpisodes did not return a Promise") + Logger.shared.log("extractEpisodes did not return a Promise",type: "Error") completion([], []) return } let thenBlockEpisodes: @convention(block) (JSValue) -> Void = { result in - Logger.shared.log(result.toString()) + Logger.shared.log(result.toString(),type: "Debug") if let jsonOfEpisodes = result.toString(), let dataEpisodes = jsonOfEpisodes.data(using: .utf8) { do { @@ -361,19 +362,19 @@ class JSController: ObservableObject { ) } } else { - Logger.shared.log("Failed to parse JSON of extractEpisodes") + Logger.shared.log("Failed to parse JSON of extractEpisodes",type: "Error") } } catch { - Logger.shared.log("JSON parsing error of extractEpisodes: \(error)") + Logger.shared.log("JSON parsing error of extractEpisodes: \(error)",type: "Error") } } else { - Logger.shared.log("Result is not a string of extractEpisodes") + Logger.shared.log("Result is not a string of extractEpisodes",type: "Error") } dispatchGroup.leave() } let catchBlockEpisodes: @convention(block) (JSValue) -> Void = { error in - Logger.shared.log("Promise rejected of extractEpisodes: \(String(describing: error.toString()))") + Logger.shared.log("Promise rejected of extractEpisodes: \(String(describing: error.toString()))",type: "Error") dispatchGroup.leave() } diff --git a/Sora/Views/MediaInfoView/EpisodeCell/EpisodeCell.swift b/Sora/Views/MediaInfoView/EpisodeCell/EpisodeCell.swift index f3acb8e..9e6e6fe 100644 --- a/Sora/Views/MediaInfoView/EpisodeCell/EpisodeCell.swift +++ b/Sora/Views/MediaInfoView/EpisodeCell/EpisodeCell.swift @@ -103,7 +103,7 @@ struct EpisodeCell: View { URLSession.custom.dataTask(with: url) { data, _, error in if let error = error { - Logger.shared.log("Failed to fetch episode details: \(error)") + Logger.shared.log("Failed to fetch episode details: \(error)", type: "Error") DispatchQueue.main.async { self.isLoading = false } @@ -133,7 +133,7 @@ struct EpisodeCell: View { let episodeDetails = episodes["\(episodeID + 1)"] as? [String: Any], let title = episodeDetails["title"] as? [String: String], let image = episodeDetails["image"] as? String else { - Logger.shared.log("Invalid response format") + Logger.shared.log("Invalid response format", type: "Error") DispatchQueue.main.async { self.isLoading = false } @@ -146,7 +146,7 @@ struct EpisodeCell: View { self.isLoading = false } } catch { - Logger.shared.log("Failed to parse JSON: \(error)") + Logger.shared.log("Failed to parse JSON: \(error)", type: "Error") DispatchQueue.main.async { self.isLoading = false } diff --git a/Sora/Views/MediaInfoView/MediaInfoView.swift b/Sora/Views/MediaInfoView/MediaInfoView.swift index 3a8b49e..fcd7fbe 100644 --- a/Sora/Views/MediaInfoView/MediaInfoView.swift +++ b/Sora/Views/MediaInfoView/MediaInfoView.swift @@ -224,7 +224,7 @@ struct MediaInfoView: View { } } } catch { - Logger.shared.log("Error loading module: \(error)") + Logger.shared.log("Error loading module: \(error)", type: "Error") self.isLoading = false } } @@ -243,7 +243,7 @@ struct MediaInfoView: View { } } } catch { - Logger.shared.log("Error loading module: \(error)") + Logger.shared.log("Error loading module: \(error)", type: "Error") self.isLoading = false } } diff --git a/Sora/Views/SearchView.swift b/Sora/Views/SearchView.swift index ca823c3..640d34c 100644 --- a/Sora/Views/SearchView.swift +++ b/Sora/Views/SearchView.swift @@ -140,6 +140,7 @@ struct SearchView: View { } private func performSearch() { + Logger.shared.log("Searching for: \(searchText)", type: "General") guard !searchText.isEmpty, let module = selectedModule else { searchItems = [] hasNoResults = false @@ -168,7 +169,7 @@ struct SearchView: View { } } } catch { - Logger.shared.log("Error loading module: \(error)") + Logger.shared.log("Error loading module: \(error)", type: "Error") isSearching = false hasNoResults = true } diff --git a/Sora/Views/SettingsView/SettingsSubViews/SettingsViewLogger.swift b/Sora/Views/SettingsView/SettingsSubViews/SettingsViewLogger.swift index f24ed66..cabf014 100644 --- a/Sora/Views/SettingsView/SettingsSubViews/SettingsViewLogger.swift +++ b/Sora/Views/SettingsView/SettingsSubViews/SettingsViewLogger.swift @@ -29,6 +29,9 @@ struct SettingsViewLogger: View { .toolbar { ToolbarItem(placement: .navigationBarTrailing) { HStack { + NavigationLink(destination: SettingsViewLoggerFilter(viewModel: filterViewModel)) { + Image(systemName: "gearshape") + Menu { Button(action: { UIPasteboard.general.string = logs @@ -46,8 +49,6 @@ struct SettingsViewLogger: View { .resizable() .frame(width: 20, height: 20) } - NavigationLink(destination: SettingsViewLoggerFilter(viewModel: filterViewModel)) { - Image(systemName: "slider.horizontal.3") } } } diff --git a/Sora/Views/SettingsView/SettingsSubViews/SettingsViewLoggerFilter.swift b/Sora/Views/SettingsView/SettingsSubViews/SettingsViewLoggerFilter.swift index 859bfcb..efc22d4 100644 --- a/Sora/Views/SettingsView/SettingsSubViews/SettingsViewLoggerFilter.swift +++ b/Sora/Views/SettingsView/SettingsSubViews/SettingsViewLoggerFilter.swift @@ -26,7 +26,7 @@ class LogFilterViewModel: ObservableObject { private let userDefaultsKey = "LogFilterStates" private let hardcodedFilters: [(type: String, description: String, defaultState: Bool)] = [ - ("Global", "Logs for general events and activities.", true), // Turned on by default + ("General", "Logs for general events and activities.", true), // Turned on by default ("Stream", "Logs for streaming and video playback.", true), // Turned on by default ("Error", "Logs for errors and critical issues.", true), // Turned on by default ("Debug", "Logs for debugging and troubleshooting.", false) // Turned off by default diff --git a/Sora/Views/SettingsView/SettingsView.swift b/Sora/Views/SettingsView/SettingsView.swift index 83e976f..c5cae53 100644 --- a/Sora/Views/SettingsView/SettingsView.swift +++ b/Sora/Views/SettingsView/SettingsView.swift @@ -28,12 +28,14 @@ struct SettingsView: View { Section(header: Text("External Features")) { NavigationLink(destination: SettingsViewModule()) { + Image(systemName: "puzzlepiece.fill") Text("Modules") } } Section(header: Text("Debug")) { NavigationLink(destination: SettingsViewLogger()) { + Image(systemName: "doc.text.fill") Text("Logs") } } From 2655d57269f5f56f28a38549c48051d5a9d9ba77 Mon Sep 17 00:00:00 2001 From: Seiike <122684677+Seeike@users.noreply.github.com> Date: Thu, 23 Jan 2025 14:28:18 +0100 Subject: [PATCH 5/5] done captian! --- Sora/Utils/Logger/Logger.swift | 5 ++--- .../SettingsSubViews/SettingsViewLogger.swift | 2 +- .../SettingsSubViews/SettingsViewLoggerFilter.swift | 12 ++++++------ Sora/Views/SettingsView/SettingsView.swift | 2 -- 4 files changed, 9 insertions(+), 12 deletions(-) diff --git a/Sora/Utils/Logger/Logger.swift b/Sora/Utils/Logger/Logger.swift index e752db7..2f34f81 100644 --- a/Sora/Utils/Logger/Logger.swift +++ b/Sora/Utils/Logger/Logger.swift @@ -18,7 +18,7 @@ class Logger { private var logs: [LogEntry] = [] private let logFileURL: URL - private let logFilterViewModel = LogFilterViewModel.shared // Use shared instance + private let logFilterViewModel = LogFilterViewModel.shared private init() { let documentDirectory = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first! @@ -26,7 +26,6 @@ class Logger { } func log(_ message: String, type: String = "General") { - // Check if the log type is enabled guard logFilterViewModel.isFilterEnabled(for: type) else { return } let entry = LogEntry(message: message, type: type, timestamp: Date()) @@ -38,7 +37,7 @@ class Logger { let dateFormatter = DateFormatter() dateFormatter.dateFormat = "dd-MM-yyyy HH:mm:ss" return logs.map { "[\(dateFormatter.string(from: $0.timestamp))] [\($0.type)] \($0.message)" } - .joined(separator: "\n----------------------------------------------------------\n") + .joined(separator: "\n----\n") } func clearLogs() { diff --git a/Sora/Views/SettingsView/SettingsSubViews/SettingsViewLogger.swift b/Sora/Views/SettingsView/SettingsSubViews/SettingsViewLogger.swift index cabf014..4d7f433 100644 --- a/Sora/Views/SettingsView/SettingsSubViews/SettingsViewLogger.swift +++ b/Sora/Views/SettingsView/SettingsSubViews/SettingsViewLogger.swift @@ -9,7 +9,7 @@ import SwiftUI struct SettingsViewLogger: View { @State private var logs: String = "" - @StateObject private var filterViewModel = LogFilterViewModel.shared // Use shared instance + @StateObject private var filterViewModel = LogFilterViewModel.shared var body: some View { VStack { diff --git a/Sora/Views/SettingsView/SettingsSubViews/SettingsViewLoggerFilter.swift b/Sora/Views/SettingsView/SettingsSubViews/SettingsViewLoggerFilter.swift index efc22d4..299948a 100644 --- a/Sora/Views/SettingsView/SettingsSubViews/SettingsViewLoggerFilter.swift +++ b/Sora/Views/SettingsView/SettingsSubViews/SettingsViewLoggerFilter.swift @@ -26,10 +26,10 @@ class LogFilterViewModel: ObservableObject { private let userDefaultsKey = "LogFilterStates" private let hardcodedFilters: [(type: String, description: String, defaultState: Bool)] = [ - ("General", "Logs for general events and activities.", true), // Turned on by default - ("Stream", "Logs for streaming and video playback.", true), // Turned on by default - ("Error", "Logs for errors and critical issues.", true), // Turned on by default - ("Debug", "Logs for debugging and troubleshooting.", false) // Turned off by default + ("General", "Logs for general events and activities.", true), + ("Stream", "Logs for streaming and video playback.", true), + ("Error", "Logs for errors and critical issues.", true), + ("Debug", "Logs for debugging and troubleshooting.", false) ] private init() { @@ -41,7 +41,7 @@ class LogFilterViewModel: ObservableObject { filters = hardcodedFilters.map { LogFilter( type: $0.type, - isEnabled: savedStates[$0.type] ?? $0.defaultState, // Use saved state if available, otherwise default + isEnabled: savedStates[$0.type] ?? $0.defaultState, description: $0.description ) } @@ -84,7 +84,7 @@ struct SettingsViewLoggerFilter: View { Text(filter.description) .font(.subheadline) .foregroundColor(.secondary) - .padding(.leading, 5) // Indent description slightly + .padding(.leading, 5) } .padding(.vertical, 5) } diff --git a/Sora/Views/SettingsView/SettingsView.swift b/Sora/Views/SettingsView/SettingsView.swift index c5cae53..83e976f 100644 --- a/Sora/Views/SettingsView/SettingsView.swift +++ b/Sora/Views/SettingsView/SettingsView.swift @@ -28,14 +28,12 @@ struct SettingsView: View { Section(header: Text("External Features")) { NavigationLink(destination: SettingsViewModule()) { - Image(systemName: "puzzlepiece.fill") Text("Modules") } } Section(header: Text("Debug")) { NavigationLink(destination: SettingsViewLogger()) { - Image(systemName: "doc.text.fill") Text("Logs") } }