fix: history load notifies and never overwrites unreadable file; dictionary keeps ids across external edits, undo safety
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -35,6 +35,8 @@ public final class HistoryStore {
|
||||
case empty
|
||||
case loaded
|
||||
case recoveredCorrupt(backup: URL)
|
||||
/// The file exists but cannot be read; it is left alone and nothing is written over it.
|
||||
case unreadable(String)
|
||||
}
|
||||
|
||||
private struct Document: Codable {
|
||||
@@ -47,6 +49,7 @@ public final class HistoryStore {
|
||||
public var onChange: (() -> Void)?
|
||||
|
||||
private let fileURL: URL
|
||||
private var readOnly = false
|
||||
private var saveTask: Task<Void, Never>?
|
||||
|
||||
public init(fileURL: URL) {
|
||||
@@ -68,18 +71,29 @@ public final class HistoryStore {
|
||||
|
||||
@discardableResult
|
||||
public func load() -> LoadOutcome {
|
||||
guard let data = try? Data(contentsOf: fileURL) else {
|
||||
entries = []
|
||||
return .empty
|
||||
let data: Data
|
||||
do {
|
||||
data = try Data(contentsOf: fileURL)
|
||||
} catch {
|
||||
if !FileManager.default.fileExists(atPath: fileURL.path) {
|
||||
entries = []
|
||||
onChange?()
|
||||
return .empty
|
||||
}
|
||||
readOnly = true
|
||||
lastError = error.localizedDescription
|
||||
return .unreadable(error.localizedDescription)
|
||||
}
|
||||
do {
|
||||
entries = try Self.makeDecoder().decode(Document.self, from: data).entries
|
||||
onChange?()
|
||||
return .loaded
|
||||
} catch {
|
||||
let stamp = ISO8601DateFormatter().string(from: Date()).replacingOccurrences(of: ":", with: "-")
|
||||
let backup = fileURL.deletingLastPathComponent().appending(path: "history.corrupt-\(stamp).json")
|
||||
try? FileManager.default.moveItem(at: fileURL, to: backup)
|
||||
entries = []
|
||||
onChange?()
|
||||
return .recoveredCorrupt(backup: backup)
|
||||
}
|
||||
}
|
||||
@@ -103,6 +117,7 @@ public final class HistoryStore {
|
||||
public func flush() {
|
||||
saveTask?.cancel()
|
||||
saveTask = nil
|
||||
guard !readOnly else { return }
|
||||
do {
|
||||
try FileManager.default.createDirectory(
|
||||
at: fileURL.deletingLastPathComponent(), withIntermediateDirectories: true)
|
||||
|
||||
Reference in New Issue
Block a user