import Foundation import CBORCoding class HistoryManagerBase: ObservableObject { @Published var entries: [HistoryItem] = [] } protocol HistoryManagerProtocol: HistoryManagerBase { var entries: [HistoryItem] { get } func save(item: HistoryItem) throws func delete(item: HistoryItem) -> Bool } final class HistoryManager: HistoryManagerBase, HistoryManagerProtocol { private let encoder = CBOREncoder(dateEncodingStrategy: .secondsSince1970) private var fm: FileManager { .default } static var documentDirectory: URL { try! FileManager.default.url( for: .documentDirectory, in: .userDomainMask, appropriateFor: nil, create: true) } private let fileUrl: URL override init() { self.fileUrl = HistoryManager.documentDirectory.appendingPathComponent("history2.bin") super.init() Task { print("Loading history...") let all = loadEntries() DispatchQueue.main.async { self.entries = all print("History loaded (\(self.entries.count) entries)") } } } private func loadEntries() -> [HistoryItem] { guard fm.fileExists(atPath: fileUrl.path) else { print("No history data found") return [] } let content: Data do { content = try Data(contentsOf: fileUrl) } catch { print("Failed to read history data: \(error)") return [] } let decoder = CBORDecoder() var index = 0 var entries = [HistoryItem]() while index < content.count { let length = Int(content[index]) index += 1 if index + length > content.count { print("Missing bytes in history file: needed \(length), has only \(content.count - index)") return entries } let entryData = content[index.. Bool { let newItems = entries .filter { $0 != item } let data: FlattenSequence<[Data]> do { data = try newItems .map(convertForStorage) .joined() } catch { print("Failed to encode items: \(error)") return false } do { try Data(data).write(to: fileUrl) } catch { print("Failed to save items: \(error)") return false } entries = newItems return true } private func convertForStorage(_ item: HistoryItem) throws -> Data { let entryData = try encoder.encode(item) return Data([UInt8(entryData.count)]) + entryData } } final class HistoryManagerMock: HistoryManagerBase, HistoryManagerProtocol { override init() { super.init() self.entries = [.mock] } func save(item: HistoryItem) throws { entries.append(item) } func delete(item: HistoryItem) -> Bool { entries = entries.filter { $0 != item } return true } }