LogViewModel.swift 3.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104
  1. //
  2. // LogViewModel.swift
  3. // LoopFollow
  4. //
  5. // Created by Jonas Björkert on 2025-01-13.
  6. // Copyright © 2025 Jon Fawcett. All rights reserved.
  7. //
  8. import Combine
  9. import Foundation
  10. class LogViewModel: ObservableObject {
  11. @Published var allLogEntries: [LogEntry] = []
  12. @Published var filteredLogEntries: [LogEntry] = []
  13. @Published var selectedCategory: LogManager.Category? = nil
  14. @Published var searchText: String = ""
  15. private var cancellables = Set<AnyCancellable>()
  16. init() {
  17. Publishers.CombineLatest($selectedCategory, $searchText)
  18. .sink { [weak self] category, search in
  19. self?.filterLogs(category: category, searchText: search)
  20. }
  21. .store(in: &cancellables)
  22. loadLogEntries()
  23. Timer.publish(every: 5.0, on: .main, in: .common)
  24. .autoconnect()
  25. .sink { [weak self] _ in
  26. self?.loadLogEntries()
  27. }
  28. .store(in: &cancellables)
  29. }
  30. func loadLogEntries() {
  31. DispatchQueue.global(qos: .background).async { [weak self] in
  32. guard let self = self else { return }
  33. let logManager = LogManager.shared
  34. let logFileURL = logManager.currentLogFileURL
  35. guard FileManager.default.fileExists(atPath: logFileURL.path) else {
  36. DispatchQueue.main.async {
  37. self.allLogEntries = []
  38. self.filteredLogEntries = []
  39. }
  40. return
  41. }
  42. do {
  43. let logContent = try String(contentsOf: logFileURL, encoding: .utf8)
  44. var logLines = logContent.components(separatedBy: .newlines)
  45. logLines = logLines.filter { !$0.isEmpty }
  46. // Reverse the log lines to have newest first
  47. logLines.reverse()
  48. let uniqueLogEntries = logLines.map { LogEntry(id: UUID(), text: $0) }
  49. DispatchQueue.main.async {
  50. self.allLogEntries = uniqueLogEntries
  51. self.filterLogs(category: self.selectedCategory, searchText: self.searchText)
  52. }
  53. } catch {
  54. print("Error reading log file: \(error)")
  55. DispatchQueue.main.async {
  56. self.allLogEntries = []
  57. self.filteredLogEntries = []
  58. }
  59. }
  60. }
  61. }
  62. private func filterLogs(category: LogManager.Category?, searchText: String) {
  63. DispatchQueue.global(qos: .userInitiated).async { [weak self] in
  64. guard let self = self else { return }
  65. var filtered = self.allLogEntries
  66. // Filter by category and remove category tag
  67. if let category = category {
  68. let categoryTag = "[\(category.rawValue)] "
  69. filtered = filtered.filter { $0.text.contains(categoryTag) }
  70. .map { logEntry in
  71. var text = logEntry.text
  72. if let range = text.range(of: categoryTag) {
  73. text.removeSubrange(range)
  74. }
  75. return LogEntry(id: logEntry.id, text: text.trimmingCharacters(in: .whitespaces))
  76. }
  77. }
  78. // Filter by search text
  79. if !searchText.isEmpty {
  80. filtered = filtered.filter { $0.text.localizedCaseInsensitiveContains(searchText) }
  81. }
  82. DispatchQueue.main.async {
  83. self.filteredLogEntries = filtered
  84. }
  85. }
  86. }
  87. }