diff --git a/Sora/Utils/Logger/Logger.swift b/Sora/Utils/Logger/Logger.swift index 8781fc1..425031a 100644 --- a/Sora/Utils/Logger/Logger.swift +++ b/Sora/Utils/Logger/Logger.swift @@ -20,8 +20,9 @@ class Logger { private var logs: [LogEntry] = [] private let logFileURL: URL private let logFilterViewModel = LogFilterViewModel.shared - + private let maxFileSize = 1024 * 512 + private let maxLogEntries = 1000 private init() { let documentDirectory = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first! @@ -35,6 +36,11 @@ class Logger { queue.async(flags: .barrier) { self.logs.append(entry) + + if self.logs.count > self.maxLogEntries { + self.logs.removeFirst(self.logs.count - self.maxLogEntries) + } + self.saveLogToFile(entry) self.debugLog(entry) } @@ -51,6 +57,18 @@ class Logger { return result } + func getLogsAsync() async -> String { + return await withCheckedContinuation { continuation in + queue.async { + let dateFormatter = DateFormatter() + dateFormatter.dateFormat = "dd-MM HH:mm:ss" + let result = self.logs.map { "[\(dateFormatter.string(from: $0.timestamp))] [\($0.type)] \($0.message)" } + .joined(separator: "\n----\n") + continuation.resume(returning: result) + } + } + } + func clearLogs() { queue.async(flags: .barrier) { self.logs.removeAll() @@ -58,6 +76,16 @@ class Logger { } } + func clearLogsAsync() async { + await withCheckedContinuation { continuation in + queue.async(flags: .barrier) { + self.logs.removeAll() + try? FileManager.default.removeItem(at: self.logFileURL) + continuation.resume() + } + } + } + private func saveLogToFile(_ log: LogEntry) { let dateFormatter = DateFormatter() dateFormatter.dateFormat = "dd-MM HH:mm:ss" @@ -69,61 +97,52 @@ class Logger { return } - if FileManager.default.fileExists(atPath: logFileURL.path) { - do { + do { + if FileManager.default.fileExists(atPath: logFileURL.path) { let attributes = try FileManager.default.attributesOfItem(atPath: logFileURL.path) let fileSize = attributes[.size] as? UInt64 ?? 0 if fileSize + UInt64(data.count) > maxFileSize { - guard var content = try? String(contentsOf: logFileURL, encoding: .utf8) else { return } - - // Ensure content is not empty and contains valid UTF-8 - guard !content.isEmpty else { - try? data.write(to: logFileURL) - return - } - - // Remove old entries until we have space - while (content.data(using: .utf8)?.count ?? 0) + data.count > maxFileSize { - if let rangeOfFirstLine = content.range(of: "\n---\n") { - // Ensure we don't try to remove beyond the string's bounds - let endIndex = min(rangeOfFirstLine.upperBound, content.endIndex) - content.removeSubrange(content.startIndex.. 10 else { return } + + let keepCount = entries.count / 2 + let truncatedEntries = Array(entries.suffix(keepCount)) + let truncatedContent = truncatedEntries.joined(separator: "\n---\n") + + if let truncatedData = truncatedContent.data(using: .utf8) { + try truncatedData.write(to: logFileURL) + } + } catch { + print("Error truncating log file: \(error)") + try? FileManager.default.removeItem(at: logFileURL) + } + } + private func debugLog(_ entry: LogEntry) { #if DEBUG let dateFormatter = DateFormatter() diff --git a/Sora/Views/SearchView.swift b/Sora/Views/SearchView/SearchView.swift similarity index 100% rename from Sora/Views/SearchView.swift rename to Sora/Views/SearchView/SearchView.swift diff --git a/Sora/Views/SettingsView/SettingsSubViews/SettingsViewData.swift b/Sora/Views/SettingsView/SettingsSubViews/SettingsViewData.swift index b1a5fe5..c2ff4df 100644 --- a/Sora/Views/SettingsView/SettingsSubViews/SettingsViewData.swift +++ b/Sora/Views/SettingsView/SettingsSubViews/SettingsViewData.swift @@ -203,8 +203,6 @@ struct SettingsViewData: View { } VStack { - Spacer() - if isCalculatingSize { ProgressView() .scaleEffect(0.7) @@ -233,9 +231,9 @@ struct SettingsViewData: View { .padding(.horizontal, 16) .padding(.vertical, 12) } - Spacer() - - Divider().padding(.horizontal, 16) + + Divider() + Button(action: clearAllCaches) { Text("Clear All Caches") .foregroundColor(.red) diff --git a/Sora/Views/SettingsView/SettingsSubViews/SettingsViewLogger.swift b/Sora/Views/SettingsView/SettingsSubViews/SettingsViewLogger.swift index 3f3d8ab..e0d32ee 100644 --- a/Sora/Views/SettingsView/SettingsSubViews/SettingsViewLogger.swift +++ b/Sora/Views/SettingsView/SettingsSubViews/SettingsViewLogger.swift @@ -61,26 +61,39 @@ fileprivate struct SettingsSection: View { struct SettingsViewLogger: View { @State private var logs: String = "" + @State private var isLoading: Bool = true @StateObject private var filterViewModel = LogFilterViewModel.shared var body: some View { ScrollView { VStack(spacing: 24) { SettingsSection(title: "Logs") { - Text(logs) - .font(.footnote) - .foregroundColor(.secondary) - .frame(maxWidth: .infinity, alignment: .leading) - .padding(.horizontal, 16) - .padding(.vertical, 12) - .textSelection(.enabled) + if isLoading { + HStack { + ProgressView() + .scaleEffect(0.8) + Text("Loading logs...") + .font(.footnote) + .foregroundColor(.secondary) + } + .frame(maxWidth: .infinity, alignment: .center) + .padding(.vertical, 20) + } else { + Text(logs) + .font(.footnote) + .foregroundColor(.secondary) + .frame(maxWidth: .infinity, alignment: .leading) + .padding(.horizontal, 16) + .padding(.vertical, 12) + .textSelection(.enabled) + } } } .padding(.vertical, 20) } .navigationTitle("Logs") .onAppear { - logs = Logger.shared.getLogs() + loadLogsAsync() } .toolbar { ToolbarItem(placement: .navigationBarTrailing) { @@ -93,8 +106,7 @@ struct SettingsViewLogger: View { Label("Copy to Clipboard", systemImage: "doc.on.doc") } Button(role: .destructive, action: { - Logger.shared.clearLogs() - logs = Logger.shared.getLogs() + clearLogsAsync() }) { Label("Clear Logs", systemImage: "trash") } @@ -111,4 +123,23 @@ struct SettingsViewLogger: View { } } } + + private func loadLogsAsync() { + Task { + let loadedLogs = await Logger.shared.getLogsAsync() + await MainActor.run { + self.logs = loadedLogs + self.isLoading = false + } + } + } + + private func clearLogsAsync() { + Task { + await Logger.shared.clearLogsAsync() + await MainActor.run { + self.logs = "" + } + } + } } diff --git a/Sulfur.xcodeproj/project.pbxproj b/Sulfur.xcodeproj/project.pbxproj index f1186b7..c7dca74 100644 --- a/Sulfur.xcodeproj/project.pbxproj +++ b/Sulfur.xcodeproj/project.pbxproj @@ -217,6 +217,7 @@ 0402DA122DE7B5EC003BB42C /* SearchView */ = { isa = PBXGroup; children = ( + 133D7C7C2D2BE2630075467E /* SearchView.swift */, 0402DA162DE7B7B8003BB42C /* SearchViewComponents.swift */, 0402DA0F2DE7B5EC003BB42C /* SearchComponents.swift */, 0402DA102DE7B5EC003BB42C /* SearchResultsGrid.swift */, @@ -348,7 +349,6 @@ 133D7C7B2D2BE2630075467E /* Views */ = { isa = PBXGroup; children = ( - 133D7C7C2D2BE2630075467E /* SearchView.swift */, 72443C7C2DC8036500A61321 /* DownloadView.swift */, 0402DA122DE7B5EC003BB42C /* SearchView */, 133D7C7F2D2BE2630075467E /* MediaInfoView */,