import AppKit import EventKit import Foundation import LilithLogging import MacSyncShared private let log = AppLogger.logger(for: "IReminder.Sync") // MARK: - Stats public struct IReminderSyncStats: Equatable, Sendable { public var calendarCount: Int = 0 public var reminderCount: Int = 0 public init() {} } // MARK: - Sync Error public enum IReminderSyncError: Equatable, Sendable { case none case reminderAccessRequired case backendUnreachable case connectionFailed(String) public var message: String { switch self { case .none: return "" case .reminderAccessRequired: return "Reminders access required" case .backendUnreachable: return "Cannot connect to backend server" case .connectionFailed(let m): return m } } public var isConnectionError: Bool { switch self { case .backendUnreachable, .connectionFailed: return true default: return false } } } // MARK: - SyncManager @MainActor public final class SyncManager: BaseSyncManager { public static let shared = SyncManager() public let reader = ReminderReader.shared private let apiClient = APIClient.shared private let reminderBatchSize = 200 private lazy var sendQueueClient: SendQueueClient = { let transport = ReminderSendTransport(apiClient: apiClient) let sender = ReminderSender(eventStore: reader.eventStore) return SendQueueClient(label: "ireminders", transport: transport, interval: 60) { item in await sender.apply(item) } }() private init() { super.init( initialStats: IReminderSyncStats(), noError: .none, persistenceKey: "ireminders", timerInterval: 300 ) } // MARK: - Authorization hooks public override func isAuthorized() async -> Bool { reader.isAuthorized } public override func requestAuthorization() async -> Bool { await reader.requestAuthorization() } public override func onAuthorizationDenied() { log.warning("Reminders access denied") syncError = .reminderAccessRequired } // MARK: - Send-queue lifecycle public override func didStartSync() { sendQueueClient.start() } public override func willStopSync() { sendQueueClient.stop() } public func openReminderAccessSettings() { if let url = URL(string: "x-apple.systempreferences:com.apple.preference.security?Privacy_Reminders") { NSWorkspace.shared.open(url) } } // MARK: - Sync cycle public override func performSync() async { log.info("performSync starting") let df = ISO8601DateFormatter() // Phase 1: Reminder lists (calendars). Server stats currently only // surfaces totalReminders; reminder-list payloads are computed for // local stats display. currentOperation = "Fetching reminder lists…" let calendars = reader.fetchCalendars() log.info("Fetched \(calendars.count) reminder lists") stats.calendarCount = calendars.count // Phase 2: Reminders currentOperation = "Fetching reminders…" let reminders = await reader.fetchReminders(since: lastSync) log.info("Fetched \(reminders.count) reminders") if !reminders.isEmpty { let batches = stride(from: 0, to: reminders.count, by: reminderBatchSize).map { Array(reminders[$0..