Files
daily-notification-plugin/ios/Plugin/DailyNotificationStateActor.swift
Matthew Raymer c40bc8dab3 feat(ios): implement Phase 2 rolling window, TTL validation, and database stats
Implement 4 of 8 Phase 2 iOS enhancements from TODO review.

Changes:
- DailyNotificationStateActor: Remove TODOs, implement TTL validation
  - maintainRollingWindow(): Already implemented, removed TODO
  - validateContentFreshness(): Now calls ttlEnforcer.validateBeforeArming()
- DailyNotificationDatabase: Add queryInt() method for PRAGMA queries
  - Enables database statistics collection (page_count, page_size, cache_size)
- DailyNotificationPerformanceOptimizer: Implement database stats and metrics
  - analyzeDatabasePerformance(): Queries PRAGMA values and records metrics
  - Removed 2 TODOs (database statistics, metrics recording)

Verification:
- TypeScript typecheck: PASS
- All TODOs removed from fixed files

Remaining Phase 2 items (4):
- DailyNotificationBackgroundTasks: CoreData history
- DailyNotificationReactivationManager: Fetcher instance
- DailyNotificationPlugin: Fetcher instance
- Additional items to verify
2025-12-24 07:30:43 +00:00

209 lines
5.2 KiB
Swift

/**
* DailyNotificationStateActor.swift
*
* Actor for thread-safe state access
* Serializes all access to shared state (database, storage, rolling window, TTL enforcer)
*
* @author Matthew Raymer
* @version 1.0.0
*/
import Foundation
/**
* Actor for thread-safe state access
*
* This actor serializes all access to:
* - DailyNotificationDatabase
* - DailyNotificationStorage
* - DailyNotificationRollingWindow
* - DailyNotificationTTLEnforcer
*
* All plugin methods and background tasks must access shared state through this actor.
*/
@available(iOS 13.0, *)
actor DailyNotificationStateActor {
// MARK: - Properties
private let database: DailyNotificationDatabase
private let storage: DailyNotificationStorage
private let rollingWindow: DailyNotificationRollingWindow?
private let ttlEnforcer: DailyNotificationTTLEnforcer?
// MARK: - Initialization
/**
* Initialize state actor with components
*
* @param database Database instance
* @param storage Storage instance
* @param rollingWindow Rolling window instance (optional, Phase 2)
* @param ttlEnforcer TTL enforcer instance (optional, Phase 2)
*/
init(
database: DailyNotificationDatabase,
storage: DailyNotificationStorage,
rollingWindow: DailyNotificationRollingWindow? = nil,
ttlEnforcer: DailyNotificationTTLEnforcer? = nil
) {
self.database = database
self.storage = storage
self.rollingWindow = rollingWindow
self.ttlEnforcer = ttlEnforcer
}
// MARK: - Storage Operations
/**
* Save notification content
*
* @param content Notification content to save
*/
func saveNotificationContent(_ content: NotificationContent) {
storage.saveNotificationContent(content)
}
/**
* Get notification content by ID
*
* @param id Notification ID
* @return Notification content or nil
*/
func getNotificationContent(id: String) -> NotificationContent? {
return storage.getNotificationContent(id: id)
}
/**
* Get last notification
*
* @return Last notification or nil
*/
func getLastNotification() -> NotificationContent? {
return storage.getLastNotification()
}
/**
* Get all notifications
*
* @return Array of all notifications
*/
func getAllNotifications() -> [NotificationContent] {
return storage.getAllNotifications()
}
/**
* Get ready notifications
*
* @return Array of ready notifications
*/
func getReadyNotifications() -> [NotificationContent] {
return storage.getReadyNotifications()
}
/**
* Delete notification content
*
* @param id Notification ID
*/
func deleteNotificationContent(id: String) {
storage.deleteNotificationContent(id: id)
}
/**
* Clear all notifications
*/
func clearAllNotifications() {
storage.clearAllNotifications()
}
// MARK: - Settings Operations
/**
* Save settings
*
* @param settings Settings dictionary
*/
func saveSettings(_ settings: [String: Any]) {
storage.saveSettings(settings)
}
/**
* Get settings
*
* @return Settings dictionary
*/
func getSettings() -> [String: Any] {
return storage.getSettings()
}
// MARK: - Background Task Tracking
/**
* Save last successful run timestamp
*
* @param timestamp Timestamp in milliseconds
*/
func saveLastSuccessfulRun(timestamp: Int64) {
storage.saveLastSuccessfulRun(timestamp: timestamp)
}
/**
* Get last successful run timestamp
*
* @return Timestamp in milliseconds or nil
*/
func getLastSuccessfulRun() -> Int64? {
return storage.getLastSuccessfulRun()
}
/**
* Save BGTask earliest begin date
*
* @param timestamp Timestamp in milliseconds
*/
func saveBGTaskEarliestBegin(timestamp: Int64) {
storage.saveBGTaskEarliestBegin(timestamp: timestamp)
}
/**
* Get BGTask earliest begin date
*
* @return Timestamp in milliseconds or nil
*/
func getBGTaskEarliestBegin() -> Int64? {
return storage.getBGTaskEarliestBegin()
}
// MARK: - Rolling Window Operations (Phase 2)
/**
* Maintain rolling window
*
* Phase 2: Rolling window maintenance
* Delegates to DailyNotificationRollingWindow for window maintenance
*/
func maintainRollingWindow() {
rollingWindow?.maintainRollingWindow()
}
// MARK: - TTL Enforcement Operations (Phase 2)
/**
* Validate content freshness before arming
*
* Phase 2: TTL validation
*
* @param content Notification content
* @return true if content is fresh
*/
func validateContentFreshness(_ content: NotificationContent) -> Bool {
guard let ttlEnforcer = ttlEnforcer else {
return true // No TTL enforcement if enforcer not available
}
return ttlEnforcer.validateBeforeArming(content)
}
}