From 17d0bfcf9edbd33351c22cd0dfafeca636f1971e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alejandro=20Modro=C3=B1o=20Vara?= Date: Fri, 5 Jun 2026 16:02:06 +0200 Subject: [PATCH] Keep a database handle when the File Provider domain is unusable MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Right after a Sparkle update macOS temporarily disables the File Provider extension, so NSFileProviderManager.stateDirectoryURL() throws on the first launch. That throw propagated out of configureInitialDatabase, leaving the app with no database handle: loadAccounts bailed (no session activated) and sign-in failed with "Database not available" — only on the post-update launch; a later manual relaunch (extension re-enabled) worked. Always open the App Group bootstrap database first, then try to upgrade to the shared File Provider state database. The bootstrap database doesn't depend on the domain, so the app always has a working handle; the session bootstrap re-seeds and adopts the shared database once the extension is back. persistSignIn also opens a bootstrap handle if none is active, as a backstop. --- Sources/App/ViewModels/AppState.swift | 44 ++++++++++++++++----------- 1 file changed, 27 insertions(+), 17 deletions(-) diff --git a/Sources/App/ViewModels/AppState.swift b/Sources/App/ViewModels/AppState.swift index b986545..0f5b40d 100644 --- a/Sources/App/ViewModels/AppState.swift +++ b/Sources/App/ViewModels/AppState.swift @@ -133,22 +133,23 @@ final class AppState: ObservableObject { } private func configureInitialDatabase() throws { - if let siteID = userDefaults.string(forKey: Self.currentSiteIDKey) { - // Try to open the shared database directly first. - if let sharedDatabase = try openSharedDatabase(siteID: siteID, seedFrom: nil) { - database = sharedDatabase - return - } - // Shared database needs seeding — bootstrap from the app group database - // so the File Provider picks up existing data on relaunch. - let bootstrapDatabase = try Database() - if let sharedDatabase = try openSharedDatabase(siteID: siteID, seedFrom: bootstrapDatabase) { - database = sharedDatabase - } else { - database = bootstrapDatabase - } - } else { - database = try Database() + // Always open the App Group bootstrap database first, so the app has a + // working handle even when the File Provider domain isn't usable yet. + // Right after a Sparkle update macOS temporarily disables the extension, + // so `stateDirectoryURL()` (inside openSharedDatabase) throws on the first + // launch. Without this fallback that throw propagated out of init, left + // `database` nil, and blocked sign-in with "Database not available". + let bootstrapDatabase = try Database() + database = bootstrapDatabase + + guard let siteID = userDefaults.string(forKey: Self.currentSiteIDKey) else { return } + + // Upgrade to the shared File Provider state database when it's reachable. + // If it isn't (e.g. the extension is disabled post-update), keep the + // bootstrap handle — the session bootstrap re-seeds and adopts the shared + // database once the extension is back. + if let sharedDatabase = try? openSharedDatabase(siteID: siteID, seedFrom: bootstrapDatabase) { + database = sharedDatabase } } @@ -374,7 +375,16 @@ final class AppState: ObservableObject { func persistSignIn(site: MoodleSite, token: AuthToken) async throws { let user = try await moodleClient.fetchUserInfo(site: site, token: token) - guard let db = database else { throw FoodleError.databaseError(detail: "Database not available") } + // Fall back to the App Group bootstrap database if no handle is active — + // e.g. the File Provider domain was unusable at launch right after an + // update — so sign-in never fails with "Database not available". + let db: Database + if let existing = database { + db = existing + } else { + db = try Database() + database = db + } try db.saveSite(site) let account = Account(