This commit is contained in:
Francesco 2025-05-31 21:49:42 +02:00
parent 8b2ee00c90
commit e9d9101526
5 changed files with 110 additions and 62 deletions

View file

@ -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..<endIndex)
} else {
// If we can't find a separator, clear the content
content = ""
break
}
// Safety check to prevent infinite loops
if content.isEmpty {
break
}
}
// Append new log entry
content += logString
// Write back to file
if let finalData = content.data(using: .utf8) {
try? finalData.write(to: logFileURL)
}
} else {
if let handle = try? FileHandle(forWritingTo: logFileURL) {
handle.seekToEndOfFile()
handle.write(data)
handle.closeFile()
}
self.truncateLogFile()
}
} catch {
print("Error managing log file: \(error)")
if let handle = try? FileHandle(forWritingTo: logFileURL) {
handle.seekToEndOfFile()
handle.write(data)
handle.closeFile()
}
} else {
try data.write(to: logFileURL)
}
} else {
} catch {
print("Error managing log file: \(error)")
try? data.write(to: logFileURL)
}
}
/// Prints log messages to the Xcode console only in DEBUG mode
private func truncateLogFile() {
do {
guard let content = try? String(contentsOf: logFileURL, encoding: .utf8),
!content.isEmpty else {
return
}
let entries = content.components(separatedBy: "\n---\n")
guard entries.count > 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()

View file

@ -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)

View file

@ -61,26 +61,39 @@ fileprivate struct SettingsSection<Content: View>: 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 = ""
}
}
}
}

View file

@ -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 */,