|
|
@@ -1,4 +1,5 @@
|
|
|
import Foundation
|
|
|
+import os
|
|
|
|
|
|
extension Notification.Name {
|
|
|
static let historyDidChange = Notification.Name("historyDidChange")
|
|
|
@@ -17,43 +18,66 @@ final class HistoryStorageService: HistoryStoring {
|
|
|
private let defaults: UserDefaults
|
|
|
private let storageKey = "gramora.history.entries"
|
|
|
private let maxEntries = 100
|
|
|
+ private let queue = DispatchQueue(label: "com.gramora.history.storage")
|
|
|
+ private let logger = Logger(subsystem: Bundle.main.bundleIdentifier ?? "gramora", category: "HistoryStorage")
|
|
|
|
|
|
init(defaults: UserDefaults = .standard) {
|
|
|
self.defaults = defaults
|
|
|
}
|
|
|
|
|
|
func loadEntries() -> [HistoryEntry] {
|
|
|
- guard
|
|
|
- let data = defaults.data(forKey: storageKey),
|
|
|
- let entries = try? JSONDecoder().decode([HistoryEntry].self, from: data)
|
|
|
- else {
|
|
|
- return []
|
|
|
+ queue.sync {
|
|
|
+ loadEntriesUnlocked()
|
|
|
}
|
|
|
-
|
|
|
- return entries.sorted { $0.createdAt > $1.createdAt }
|
|
|
}
|
|
|
|
|
|
func save(_ entry: HistoryEntry) {
|
|
|
- var entries = loadEntries()
|
|
|
- entries.removeAll { $0.id == entry.id }
|
|
|
- entries.insert(entry, at: 0)
|
|
|
- entries = Array(entries.prefix(maxEntries))
|
|
|
- persist(entries)
|
|
|
+ queue.sync {
|
|
|
+ var entries = loadEntriesUnlocked()
|
|
|
+ entries.removeAll { $0.id == entry.id }
|
|
|
+ entries.insert(entry, at: 0)
|
|
|
+ entries = Array(entries.prefix(maxEntries))
|
|
|
+ persistUnlocked(entries)
|
|
|
+ }
|
|
|
}
|
|
|
|
|
|
func delete(id: UUID) {
|
|
|
- var entries = loadEntries()
|
|
|
- entries.removeAll { $0.id == id }
|
|
|
- persist(entries)
|
|
|
+ queue.sync {
|
|
|
+ var entries = loadEntriesUnlocked()
|
|
|
+ entries.removeAll { $0.id == id }
|
|
|
+ persistUnlocked(entries)
|
|
|
+ }
|
|
|
}
|
|
|
|
|
|
func clearAll() {
|
|
|
- persist([])
|
|
|
+ queue.sync {
|
|
|
+ persistUnlocked([])
|
|
|
+ }
|
|
|
}
|
|
|
|
|
|
- private func persist(_ entries: [HistoryEntry]) {
|
|
|
- guard let data = try? JSONEncoder().encode(entries) else { return }
|
|
|
- defaults.set(data, forKey: storageKey)
|
|
|
- NotificationCenter.default.post(name: .historyDidChange, object: nil)
|
|
|
+ private func loadEntriesUnlocked() -> [HistoryEntry] {
|
|
|
+ guard let data = defaults.data(forKey: storageKey) else {
|
|
|
+ return []
|
|
|
+ }
|
|
|
+
|
|
|
+ do {
|
|
|
+ let entries = try JSONDecoder().decode([HistoryEntry].self, from: data)
|
|
|
+ return entries.sorted { $0.createdAt > $1.createdAt }
|
|
|
+ } catch {
|
|
|
+ logger.error("Failed to decode history entries: \(error.localizedDescription, privacy: .public)")
|
|
|
+ return []
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ private func persistUnlocked(_ entries: [HistoryEntry]) {
|
|
|
+ do {
|
|
|
+ let data = try JSONEncoder().encode(entries)
|
|
|
+ defaults.set(data, forKey: storageKey)
|
|
|
+ DispatchQueue.main.async {
|
|
|
+ NotificationCenter.default.post(name: .historyDidChange, object: nil)
|
|
|
+ }
|
|
|
+ } catch {
|
|
|
+ logger.error("Failed to persist history entries: \(error.localizedDescription, privacy: .public)")
|
|
|
+ }
|
|
|
}
|
|
|
}
|