LogViewModel.swift 3.4 KB

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