Cache active account ID in CoreData

See #251
This commit is contained in:
Shadowfacts 2023-05-28 22:23:04 -07:00
parent cb5b70a23a
commit da88303a22
4 changed files with 36 additions and 12 deletions

View File

@ -23,6 +23,7 @@ class MastodonController: ObservableObject {
@available(*, message: "do something less dumb") @available(*, message: "do something less dumb")
static var first: MastodonController { all.first!.value } static var first: MastodonController { all.first!.value }
@MainActor
static func getForAccount(_ account: UserAccountInfo) -> MastodonController { static func getForAccount(_ account: UserAccountInfo) -> MastodonController {
if let controller = all[account] { if let controller = all[account] {
return controller return controller
@ -51,6 +52,7 @@ class MastodonController: ObservableObject {
let client: Client! let client: Client!
let instanceFeatures = InstanceFeatures() let instanceFeatures = InstanceFeatures()
@Published private(set) var accountID: String?
@Published private(set) var account: Account! @Published private(set) var account: Account!
@Published private(set) var instance: Instance? @Published private(set) var instance: Instance?
@Published private(set) var instanceInfo: InstanceInfo! @Published private(set) var instanceInfo: InstanceInfo!
@ -69,6 +71,8 @@ class MastodonController: ObservableObject {
accountInfo != nil accountInfo != nil
} }
// main-actor b/c fetchActiveAccountID and fetchActiveInstance use the viewContext
@MainActor
init(instanceURL: URL, accountInfo: UserAccountInfo?) { init(instanceURL: URL, accountInfo: UserAccountInfo?) {
self.instanceURL = instanceURL self.instanceURL = instanceURL
self.accountInfo = accountInfo self.accountInfo = accountInfo
@ -81,6 +85,7 @@ class MastodonController: ObservableObject {
self.client.accessToken = accountInfo?.accessToken self.client.accessToken = accountInfo?.accessToken
if !transient { if !transient {
fetchActiveAccountID()
fetchActiveInstance() fetchActiveInstance()
} }
@ -119,6 +124,7 @@ class MastodonController: ObservableObject {
.store(in: &cancellables) .store(in: &cancellables)
} }
@MainActor
convenience init(instanceURL: URL, transient: Bool) { convenience init(instanceURL: URL, transient: Bool) {
precondition(transient, "account info must be provided if transient is false") precondition(transient, "account info must be provided if transient is false")
self.init(instanceURL: instanceURL, accountInfo: nil) self.init(instanceURL: instanceURL, accountInfo: nil)
@ -242,13 +248,14 @@ class MastodonController: ObservableObject {
DispatchQueue.main.async { DispatchQueue.main.async {
self.account = account self.account = account
} }
self.persistentContainer.backgroundContext.perform { let context = self.persistentContainer.backgroundContext
if let accountMO = self.persistentContainer.account(for: account.id, in: self.persistentContainer.backgroundContext) { context.perform {
if let accountMO = self.persistentContainer.account(for: account.id, in: context) {
accountMO.updateFrom(apiAccount: account, container: self.persistentContainer) accountMO.updateFrom(apiAccount: account, container: self.persistentContainer)
accountMO.active = true
} else { } else {
// the first time the user's account is added to the store, let account = self.persistentContainer.addOrUpdateSynchronously(account: account, in: context)
// increment its reference count so that it's never removed account.active = true
self.persistentContainer.addOrUpdate(account: account)
} }
completion?(.success(account)) completion?(.success(account))
} }
@ -367,16 +374,22 @@ 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() { private func fetchActiveInstance() {
persistentContainer.performBackgroundTask { context in if let activeInstance = try? persistentContainer.viewContext.fetch(ActiveInstance.fetchRequest()).first {
if let activeInstance = try? context.fetch(ActiveInstance.fetchRequest()).first {
let info = InstanceInfo(activeInstance: activeInstance) let info = InstanceInfo(activeInstance: activeInstance)
DispatchQueue.main.async {
self.instanceInfo = info self.instanceInfo = info
} }
} }
}
}
func getCustomEmojis(completion: @escaping ([Emoji]) -> Void) { func getCustomEmojis(completion: @escaping ([Emoji]) -> Void) {
if let emojis = self.customEmojis { if let emojis = self.customEmojis {

View File

@ -25,6 +25,8 @@ public final class AccountMO: NSManagedObject, AccountProtocol {
} }
@NSManaged public var acct: String @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 avatar: URL?
@NSManaged public var botCD: Bool @NSManaged public var botCD: Bool
@NSManaged public var createdAt: Date @NSManaged public var createdAt: Date

View File

@ -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? { func relationship(forAccount id: String, in context: NSManagedObjectContext? = nil) -> RelationshipMO? {
let context = context ?? viewContext let context = context ?? viewContext
let request: NSFetchRequest<RelationshipMO> = RelationshipMO.fetchRequest() let request: NSFetchRequest<RelationshipMO> = RelationshipMO.fetchRequest()

View File

@ -2,6 +2,7 @@
<model type="com.apple.IDECoreDataModeler.DataModel" documentVersion="1.0" lastSavedToolsVersion="21754" systemVersion="22D49" minimumToolsVersion="Automatic" sourceLanguage="Swift" userDefinedModelVersionIdentifier=""> <model type="com.apple.IDECoreDataModeler.DataModel" documentVersion="1.0" lastSavedToolsVersion="21754" systemVersion="22D49" minimumToolsVersion="Automatic" sourceLanguage="Swift" userDefinedModelVersionIdentifier="">
<entity name="Account" representedClassName="AccountMO" syncable="YES"> <entity name="Account" representedClassName="AccountMO" syncable="YES">
<attribute name="acct" attributeType="String"/> <attribute name="acct" attributeType="String"/>
<attribute name="active" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES"/>
<attribute name="avatar" optional="YES" attributeType="URI"/> <attribute name="avatar" optional="YES" attributeType="URI"/>
<attribute name="botCD" optional="YES" attributeType="Boolean" usesScalarValueType="YES"/> <attribute name="botCD" optional="YES" attributeType="Boolean" usesScalarValueType="YES"/>
<attribute name="createdAt" attributeType="Date" usesScalarValueType="NO"/> <attribute name="createdAt" attributeType="Date" usesScalarValueType="NO"/>