import Foundation import Observation import os private let log = Logger(subsystem: "com.scarriffleservices.onyx", category: "Notes") /// Die Notizen und ihre Ablage. /// /// Eine Datei für alles: Notizen sind kurz, und fünfzig davon sind ein paar /// Kilobyte. Eine Datei je Notiz wäre der Ordnung wegen — nicht der Sache. /// /// **Nicht in `UserDefaults`.** Was dort steht, liest jedes Programm des /// Benutzers mit `defaults read` mit, und eine Notiz ist oft genau das, was man /// sich merkt, weil man es nicht im Kopf behalten will. @MainActor @Observable public final class NotesStore { public private(set) var notes: [Note] = [] @ObservationIgnored private let fileURL: URL private struct Archive: Codable { var version = 1 var notes: [Note] } public init(directory: URL? = nil) { let folder = directory ?? Self.defaultDirectory try? FileManager.default.createDirectory(at: folder, withIntermediateDirectories: true) fileURL = folder.appendingPathComponent("notes.json") load() } public static var defaultDirectory: URL { FileManager.default.urls(for: .applicationSupportDirectory, in: .userDomainMask)[0] .appendingPathComponent("Onyx/Notes", isDirectory: true) } /// Legt eine leere Notiz an und gibt sie zurück, damit die Anzeige den /// Schreibcursor hineinsetzen kann. /// /// Sie steht noch **nicht** in der Liste: eine leere Notiz ist keine, und /// wer auf „Neu" drückt und es sich anders überlegt, soll keine Leerzeile /// hinterlassen. Erst der erste Buchstabe macht sie zur Notiz. public func makeDraft() -> Note { Note() } public func update(_ note: Note) { var updated = note updated.updatedAt = Date() notes = NoteRules.merge(notes, updating: updated) save() } public func remove(_ note: Note) { notes.removeAll { $0.id == note.id } save() } private func load() { guard let data = try? Data(contentsOf: fileURL) else { return } guard let archive = try? JSONDecoder().decode(Archive.self, from: data), archive.version == 1 else { log.notice("Unbekannte Fassung der Notizdatei — sie bleibt unangetastet") return } notes = NoteRules.sorted(archive.notes) } private func save() { do { try JSONEncoder().encode(Archive(notes: notes)).write(to: fileURL, options: .atomic) } catch { log.error("Notizen nicht schreibbar: \(error.localizedDescription, privacy: .public)") } } }