2022-05-01 18:30:30 +02:00
|
|
|
import Foundation
|
2023-04-11 18:18:31 +02:00
|
|
|
import CBORCoding
|
2022-05-01 18:30:30 +02:00
|
|
|
|
2023-12-12 17:33:42 +01:00
|
|
|
/*
|
2023-08-14 10:39:29 +02:00
|
|
|
class HistoryManagerBase: ObservableObject {
|
|
|
|
|
|
|
|
@Published
|
|
|
|
var entries: [HistoryItem] = []
|
|
|
|
}
|
|
|
|
|
|
|
|
protocol HistoryManagerProtocol: HistoryManagerBase {
|
|
|
|
|
|
|
|
var entries: [HistoryItem] { get }
|
2023-04-11 18:18:31 +02:00
|
|
|
|
|
|
|
func save(item: HistoryItem) throws
|
2023-08-14 10:39:29 +02:00
|
|
|
|
|
|
|
func delete(item: HistoryItem) -> Bool
|
2023-04-11 18:18:31 +02:00
|
|
|
}
|
|
|
|
|
2023-08-14 10:39:29 +02:00
|
|
|
final class HistoryManager: HistoryManagerBase, HistoryManagerProtocol {
|
2023-04-11 18:18:31 +02:00
|
|
|
|
|
|
|
private let encoder = CBOREncoder(dateEncodingStrategy: .secondsSince1970)
|
2022-05-01 18:30:30 +02:00
|
|
|
|
|
|
|
private var fm: FileManager {
|
|
|
|
.default
|
|
|
|
}
|
|
|
|
|
2023-04-11 18:18:31 +02:00
|
|
|
static var documentDirectory: URL {
|
|
|
|
try! FileManager.default.url(
|
2022-05-01 18:30:30 +02:00
|
|
|
for: .documentDirectory,
|
|
|
|
in: .userDomainMask,
|
|
|
|
appropriateFor: nil, create: true)
|
|
|
|
}
|
|
|
|
|
2023-04-11 18:18:31 +02:00
|
|
|
private let fileUrl: URL
|
|
|
|
|
2023-08-14 10:39:29 +02:00
|
|
|
override init() {
|
2023-04-11 18:18:31 +02:00
|
|
|
self.fileUrl = HistoryManager.documentDirectory.appendingPathComponent("history2.bin")
|
2023-08-14 10:39:29 +02:00
|
|
|
super.init()
|
|
|
|
Task {
|
|
|
|
print("Loading history...")
|
|
|
|
let all = loadEntries()
|
|
|
|
DispatchQueue.main.async {
|
|
|
|
self.entries = all
|
|
|
|
print("History loaded (\(self.entries.count) entries)")
|
|
|
|
}
|
|
|
|
}
|
2022-05-01 18:30:30 +02:00
|
|
|
}
|
2023-04-11 18:18:31 +02:00
|
|
|
|
2023-08-14 10:39:29 +02:00
|
|
|
private func loadEntries() -> [HistoryItem] {
|
2023-04-11 18:18:31 +02:00
|
|
|
guard fm.fileExists(atPath: fileUrl.path) else {
|
2022-05-01 18:30:30 +02:00
|
|
|
print("No history data found")
|
|
|
|
return []
|
|
|
|
}
|
|
|
|
let content: Data
|
|
|
|
do {
|
2023-04-11 18:18:31 +02:00
|
|
|
content = try Data(contentsOf: fileUrl)
|
2022-05-01 18:30:30 +02:00
|
|
|
} catch {
|
|
|
|
print("Failed to read history data: \(error)")
|
|
|
|
return []
|
|
|
|
}
|
2023-04-11 18:18:31 +02:00
|
|
|
let decoder = CBORDecoder()
|
2022-05-01 18:30:30 +02:00
|
|
|
var index = 0
|
|
|
|
var entries = [HistoryItem]()
|
|
|
|
while index < content.count {
|
2023-04-11 18:18:31 +02:00
|
|
|
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..<index+length]
|
|
|
|
index += length
|
|
|
|
do {
|
|
|
|
let entry: HistoryItem = try decoder.decode(from: entryData)
|
|
|
|
entries.append(entry)
|
|
|
|
} catch {
|
|
|
|
print("Failed to decode history (index: \(index), length \(length)): \(error)")
|
2022-05-01 18:30:30 +02:00
|
|
|
return entries
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return entries.sorted().reversed()
|
|
|
|
}
|
|
|
|
|
|
|
|
func save(item: HistoryItem) throws {
|
2023-08-14 10:39:29 +02:00
|
|
|
let data = try convertForStorage(item)
|
2023-04-11 18:18:31 +02:00
|
|
|
guard fm.fileExists(atPath: fileUrl.path) else {
|
|
|
|
try data.write(to: fileUrl)
|
|
|
|
print("First history item written (\(data[0]))")
|
2022-05-01 18:30:30 +02:00
|
|
|
return
|
|
|
|
}
|
2023-04-11 18:18:31 +02:00
|
|
|
let handle = try FileHandle(forWritingTo: fileUrl)
|
2022-05-01 18:30:30 +02:00
|
|
|
try handle.seekToEnd()
|
|
|
|
try handle.write(contentsOf: data)
|
|
|
|
try handle.close()
|
2023-04-11 18:18:31 +02:00
|
|
|
print("History item written (\(data[0]))")
|
|
|
|
}
|
2023-08-14 10:39:29 +02:00
|
|
|
|
|
|
|
@discardableResult
|
|
|
|
func delete(item: HistoryItem) -> 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
|
|
|
|
}
|
2023-04-11 18:18:31 +02:00
|
|
|
}
|
|
|
|
|
2023-08-14 10:39:29 +02:00
|
|
|
final class HistoryManagerMock: HistoryManagerBase, HistoryManagerProtocol {
|
|
|
|
|
|
|
|
override init() {
|
|
|
|
super.init()
|
|
|
|
self.entries = [.mock]
|
2023-04-11 18:18:31 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
func save(item: HistoryItem) throws {
|
2023-08-14 10:39:29 +02:00
|
|
|
entries.append(item)
|
|
|
|
}
|
|
|
|
|
|
|
|
func delete(item: HistoryItem) -> Bool {
|
|
|
|
entries = entries.filter { $0 != item }
|
|
|
|
return true
|
2022-05-01 18:30:30 +02:00
|
|
|
}
|
|
|
|
}
|
2023-12-12 17:33:42 +01:00
|
|
|
*/
|