import AppKit import Foundation import LilithLogging import MacSyncShared private let log = AppLogger.logger(for: "INote.Sync") // MARK: - Stats public struct INoteSyncStats: Equatable, Sendable { public var folderCount: Int = 0 public var noteCount: Int = 0 public init() {} } // MARK: - Sync Error public enum INoteSyncError: Equatable, Sendable { case none case noteAccessRequired case backendUnreachable case connectionFailed(String) public var message: String { switch self { case .none: return "" case .noteAccessRequired: return "Notes automation 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 = NotesReader.shared private let apiClient = APIClient.shared private let noteBatchSize = 100 // Outbound send queue (server → Mac Notes writes). private lazy var sendQueueClient: SendQueueClient = { let transport = NoteSendTransport(apiClient: apiClient) let sender = NoteSender() return SendQueueClient( label: "inotes", transport: transport, interval: 60 ) { item in await sender.apply(item) } }() public override func didStartSync() { sendQueueClient.start() } public override func willStopSync() { sendQueueClient.stop() } private init() { super.init( initialStats: INoteSyncStats(), noError: .none, persistenceKey: "inotes", timerInterval: 600 ) } // 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("Notes automation access denied") syncError = .noteAccessRequired } public func openNotesAccessSettings() { if let url = URL(string: "x-apple.systempreferences:com.apple.preference.security?Privacy_Automation") { NSWorkspace.shared.open(url) } } // MARK: - Sync cycle public override func performSync() async { log.info("performSync starting") currentOperation = "Fetching notes…" let notes = await reader.fetchAllNotes() log.info("Fetched \(notes.count) notes") let df = ISO8601DateFormatter() if !notes.isEmpty { let batches = stride(from: 0, to: notes.count, by: noteBatchSize).map { Array(notes[$0..