From da88303a22fabbe9c6097068a789b934276a4810 Mon Sep 17 00:00:00 2001 From: Shadowfacts Date: Sun, 28 May 2023 22:23:04 -0700 Subject: [PATCH] Cache active account ID in CoreData See #251 --- Tusker/API/MastodonController.swift | 37 +++++++++++++------ Tusker/CoreData/AccountMO.swift | 2 + .../MastodonCachePersistentStore.swift | 8 ++++ .../Tusker.xcdatamodel/contents | 1 + 4 files changed, 36 insertions(+), 12 deletions(-) diff --git a/Tusker/API/MastodonController.swift b/Tusker/API/MastodonController.swift index 97efb6b7..efbee1ac 100644 --- a/Tusker/API/MastodonController.swift +++ b/Tusker/API/MastodonController.swift @@ -23,6 +23,7 @@ class MastodonController: ObservableObject { @available(*, message: "do something less dumb") static var first: MastodonController { all.first!.value } + @MainActor static func getForAccount(_ account: UserAccountInfo) -> MastodonController { if let controller = all[account] { return controller @@ -51,6 +52,7 @@ class MastodonController: ObservableObject { let client: Client! let instanceFeatures = InstanceFeatures() + @Published private(set) var accountID: String? @Published private(set) var account: Account! @Published private(set) var instance: Instance? @Published private(set) var instanceInfo: InstanceInfo! @@ -69,6 +71,8 @@ class MastodonController: ObservableObject { accountInfo != nil } + // main-actor b/c fetchActiveAccountID and fetchActiveInstance use the viewContext + @MainActor init(instanceURL: URL, accountInfo: UserAccountInfo?) { self.instanceURL = instanceURL self.accountInfo = accountInfo @@ -81,6 +85,7 @@ class MastodonController: ObservableObject { self.client.accessToken = accountInfo?.accessToken if !transient { + fetchActiveAccountID() fetchActiveInstance() } @@ -119,6 +124,7 @@ class MastodonController: ObservableObject { .store(in: &cancellables) } + @MainActor convenience init(instanceURL: URL, transient: Bool) { precondition(transient, "account info must be provided if transient is false") self.init(instanceURL: instanceURL, accountInfo: nil) @@ -242,13 +248,14 @@ class MastodonController: ObservableObject { DispatchQueue.main.async { self.account = account } - self.persistentContainer.backgroundContext.perform { - if let accountMO = self.persistentContainer.account(for: account.id, in: self.persistentContainer.backgroundContext) { + let context = self.persistentContainer.backgroundContext + context.perform { + if let accountMO = self.persistentContainer.account(for: account.id, in: context) { accountMO.updateFrom(apiAccount: account, container: self.persistentContainer) + accountMO.active = true } else { - // the first time the user's account is added to the store, - // increment its reference count so that it's never removed - self.persistentContainer.addOrUpdate(account: account) + let account = self.persistentContainer.addOrUpdateSynchronously(account: account, in: context) + account.active = true } completion?(.success(account)) } @@ -367,14 +374,20 @@ class MastodonController: ObservableObject { } } + @MainActor + private func fetchActiveAccountID() { + let req = AccountMO.fetchRequest() + req.predicate = NSPredicate(format: "active = YES") + if let activeAccount = try? persistentContainer.viewContext.fetch(req).first { + accountID = activeAccount.id + } + } + + @MainActor private func fetchActiveInstance() { - persistentContainer.performBackgroundTask { context in - if let activeInstance = try? context.fetch(ActiveInstance.fetchRequest()).first { - let info = InstanceInfo(activeInstance: activeInstance) - DispatchQueue.main.async { - self.instanceInfo = info - } - } + if let activeInstance = try? persistentContainer.viewContext.fetch(ActiveInstance.fetchRequest()).first { + let info = InstanceInfo(activeInstance: activeInstance) + self.instanceInfo = info } } diff --git a/Tusker/CoreData/AccountMO.swift b/Tusker/CoreData/AccountMO.swift index 001fab92..df237ecc 100644 --- a/Tusker/CoreData/AccountMO.swift +++ b/Tusker/CoreData/AccountMO.swift @@ -25,6 +25,8 @@ public final class AccountMO: NSManagedObject, AccountProtocol { } @NSManaged public var acct: String + /// Whether this AccountMO is the active (logged-in) account. + @NSManaged public var active: Bool @NSManaged public var avatar: URL? @NSManaged public var botCD: Bool @NSManaged public var createdAt: Date diff --git a/Tusker/CoreData/MastodonCachePersistentStore.swift b/Tusker/CoreData/MastodonCachePersistentStore.swift index 9ea45356..26a2b8c8 100644 --- a/Tusker/CoreData/MastodonCachePersistentStore.swift +++ b/Tusker/CoreData/MastodonCachePersistentStore.swift @@ -311,6 +311,14 @@ class MastodonCachePersistentStore: NSPersistentCloudKitContainer { } } + /// The caller is responsible for calling this on a queue appropriate for `context`. + func addOrUpdateSynchronously(account: Account, in context: NSManagedObjectContext) -> AccountMO { + let accountMO = self.upsert(account: account, in: context) + self.save(context: context) + self.accountSubject.send(account.id) + return accountMO + } + func relationship(forAccount id: String, in context: NSManagedObjectContext? = nil) -> RelationshipMO? { let context = context ?? viewContext let request: NSFetchRequest = RelationshipMO.fetchRequest() diff --git a/Tusker/CoreData/Tusker.xcdatamodeld/Tusker.xcdatamodel/contents b/Tusker/CoreData/Tusker.xcdatamodeld/Tusker.xcdatamodel/contents index 5aa247c3..092b622f 100644 --- a/Tusker/CoreData/Tusker.xcdatamodeld/Tusker.xcdatamodel/contents +++ b/Tusker/CoreData/Tusker.xcdatamodeld/Tusker.xcdatamodel/contents @@ -2,6 +2,7 @@ +