HistoryStorageService.swift 2.4 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283
  1. import Foundation
  2. import os
  3. extension Notification.Name {
  4. static let historyDidChange = Notification.Name("historyDidChange")
  5. }
  6. protocol HistoryStoring {
  7. func loadEntries() -> [HistoryEntry]
  8. func save(_ entry: HistoryEntry)
  9. func delete(id: UUID)
  10. func clearAll()
  11. }
  12. final class HistoryStorageService: HistoryStoring {
  13. static let shared = HistoryStorageService()
  14. private let defaults: UserDefaults
  15. private let storageKey = "gramora.history.entries"
  16. private let maxEntries = 100
  17. private let queue = DispatchQueue(label: "com.gramora.history.storage")
  18. private let logger = Logger(subsystem: Bundle.main.bundleIdentifier ?? "com.Gramora", category: "HistoryStorage")
  19. init(defaults: UserDefaults = .standard) {
  20. self.defaults = defaults
  21. }
  22. func loadEntries() -> [HistoryEntry] {
  23. queue.sync {
  24. loadEntriesUnlocked()
  25. }
  26. }
  27. func save(_ entry: HistoryEntry) {
  28. queue.sync {
  29. var entries = loadEntriesUnlocked()
  30. entries.removeAll { $0.id == entry.id }
  31. entries.insert(entry, at: 0)
  32. entries = Array(entries.prefix(maxEntries))
  33. persistUnlocked(entries)
  34. }
  35. }
  36. func delete(id: UUID) {
  37. queue.sync {
  38. var entries = loadEntriesUnlocked()
  39. entries.removeAll { $0.id == id }
  40. persistUnlocked(entries)
  41. }
  42. }
  43. func clearAll() {
  44. queue.sync {
  45. persistUnlocked([])
  46. }
  47. }
  48. private func loadEntriesUnlocked() -> [HistoryEntry] {
  49. guard let data = defaults.data(forKey: storageKey) else {
  50. return []
  51. }
  52. do {
  53. let entries = try JSONDecoder().decode([HistoryEntry].self, from: data)
  54. return entries.sorted { $0.createdAt > $1.createdAt }
  55. } catch {
  56. logger.error("Failed to decode history entries: \(error.localizedDescription, privacy: .public)")
  57. return []
  58. }
  59. }
  60. private func persistUnlocked(_ entries: [HistoryEntry]) {
  61. do {
  62. let data = try JSONEncoder().encode(entries)
  63. defaults.set(data, forKey: storageKey)
  64. DispatchQueue.main.async {
  65. NotificationCenter.default.post(name: .historyDidChange, object: nil)
  66. }
  67. } catch {
  68. logger.error("Failed to persist history entries: \(error.localizedDescription, privacy: .public)")
  69. }
  70. }
  71. }