From 2f72d3d0736a6fac306d4e5d9406cb33b08bc2a0 Mon Sep 17 00:00:00 2001 From: Christian Cleberg Date: Fri, 17 Jul 2026 16:41:52 -0500 Subject: Add persistence, multi-repo watch list, and background polling Closes #1-#5 (milestones 0.2.0, 0.3.0). - Flat JSON persistence (PersistedState/PersistenceStore) in Application Support, chosen over SwiftData since the dataset is small and the existing model types are plain Codable value types passed across actor boundaries. - Repo watch list, per-event seen-state, and last-fetch timestamps are now persisted instead of living only in memory. - Configurable minimum severity filter, applied from a cached raw fetch so changing it doesn't require a network round-trip. - Multi-repo support: SecurityEventStore now loops over a persisted watch list instead of one hardcoded repo, with an in-popover UI (gear button) to add/remove repos. - Background polling every 15 minutes, layered on top of the existing refresh-on-open and manual refresh, so the feed stays fresh even while the popover is closed. --- octosentry/PersistenceStore.swift | 46 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 46 insertions(+) create mode 100644 octosentry/PersistenceStore.swift (limited to 'octosentry/PersistenceStore.swift') diff --git a/octosentry/PersistenceStore.swift b/octosentry/PersistenceStore.swift new file mode 100644 index 0000000..ca86eec --- /dev/null +++ b/octosentry/PersistenceStore.swift @@ -0,0 +1,46 @@ +// +// PersistenceStore.swift +// octosentry +// +// Loads and saves PersistedState as JSON in the app's Application Support +// container. No entitlement needed — sandboxed apps always get a private +// Application Support directory in their own container. +// + +import Foundation + +actor PersistenceStore { + private let fileURL: URL + + private static let decoder: JSONDecoder = { + let decoder = JSONDecoder() + decoder.dateDecodingStrategy = .iso8601 + return decoder + }() + + private static let encoder: JSONEncoder = { + let encoder = JSONEncoder() + encoder.dateEncodingStrategy = .iso8601 + return encoder + }() + + init() { + let appSupport = FileManager.default.urls(for: .applicationSupportDirectory, in: .userDomainMask)[0] + let directory = appSupport.appendingPathComponent("octosentry", isDirectory: true) + try? FileManager.default.createDirectory(at: directory, withIntermediateDirectories: true) + fileURL = directory.appendingPathComponent("state.json") + } + + func load() -> PersistedState { + guard let data = try? Data(contentsOf: fileURL), + let state = try? Self.decoder.decode(PersistedState.self, from: data) else { + return .placeholder + } + return state + } + + func save(_ state: PersistedState) { + guard let data = try? Self.encoder.encode(state) else { return } + try? data.write(to: fileURL, options: .atomic) + } +} -- cgit v1.2.3