import Foundation /// `NSFileCoordinator` brackets around the storage layer's own I/O. /// /// **Required, not defensive.** `BoardLoader` and `BoardWriter` read and write with plain /// `FileManager` calls, which is correct on the Mac where the app owns the folder outright. In a /// ubiquity container the daemon is a second writer: it materializes, evicts and replaces items /// underneath a walk with no warning. Coordination is the only thing that makes "the tree did not /// move while I read it" true, and the only thing that tells the daemon not to push a remote version /// into a folder mid-write. /// /// The bracket wraps the **package root**, not each file inside it. A board is one document /// (`LSTypeIsPackage`), so one coordination covers the whole walk — which is also the only shape /// that can hold a multi-file write (a move is two folders, a delete is a folder plus its trash /// destination) as one unit. /// /// **Every call blocks.** `coordinate` waits for the daemon and for other presenters, so these run /// off the main actor without exception; the types here are `nonisolated` and stateless so they can. enum CoordinatedFileAccess { /// Runs `body` under a read intent on `url`, and answers what it returned. /// /// `body` is handed the URL the coordinator resolved — which may differ from `url` if the item /// moved — and must use it rather than closing over the original. It is deliberately /// non-throwing: the storage layer's typed errors (`BoardLoadFailure`, `BoardWriteError`) are far /// richer than anything this layer could wrap, so a caller returns its own `Result` from `body` /// and the outer `Result` carries only the coordinator's own refusal. static func read( itemAt url: URL, options: NSFileCoordinator.ReadingOptions = [], by body: (URL) -> T ) -> Result { var captured: T? var ran = false var coordinatorError: NSError? let coordinator = NSFileCoordinator(filePresenter: nil) coordinator.coordinate(readingItemAt: url, options: options, error: &coordinatorError) { resolved in ran = true captured = body(resolved) } // `ran` rather than `captured != nil`: a `T` that is itself optional would otherwise read a // legitimate nil result as "the block never ran". guard ran, let captured else { return .failure(CoordinationFailure(coordinatorError, fallback: "the coordinated read did not run")) } return .success(captured) } /// Runs `body` under a write intent on `url` — the bracket every `BoardWriter` call on the phone /// goes through. Same contract as `read(itemAt:options:by:)`. static func write( itemAt url: URL, options: NSFileCoordinator.WritingOptions = [], by body: (URL) -> T ) -> Result { var captured: T? var ran = false var coordinatorError: NSError? let coordinator = NSFileCoordinator(filePresenter: nil) coordinator.coordinate(writingItemAt: url, options: options, error: &coordinatorError) { resolved in ran = true captured = body(resolved) } guard ran, let captured else { return .failure(CoordinationFailure(coordinatorError, fallback: "the coordinated write did not run")) } return .success(captured) } } /// The coordinator refused — a lock it could not take, an item it could not reach. /// /// A flattened value rather than the `NSError` itself: this crosses from a detached task back to the /// main actor, and three `Sendable` scalars carry everything a log line or an alert needs without /// smuggling a reference type across the boundary. struct CoordinationFailure: Error, Sendable, Equatable, CustomStringConvertible { let domain: String let code: Int let message: String init(_ error: NSError?, fallback: String) { domain = error?.domain ?? "dev.rzen.indie.KanbanMobile.coordination" code = error?.code ?? -1 message = error?.localizedDescription ?? fallback } var description: String { "\(message) (\(domain) \(code))" } }