Add createdAt to AccountPreferences and TimelinePosition to guard against race conditions when creating/migrating

This commit is contained in:
Shadowfacts 2023-01-01 12:58:44 -05:00
parent 91b7ce3008
commit d4c560d7fc
4 changed files with 27 additions and 7 deletions

View File

@ -155,12 +155,14 @@ class MastodonController: ObservableObject {
// are available when Filterers are constructed // are available when Filterers are constructed
loadCachedFilters() loadCachedFilters()
if let existing = try? persistentContainer.viewContext.fetch(AccountPreferences.fetchRequest(account: accountInfo!)).first { loadAccountPreferences()
accountPreferences = existing
} else { NotificationCenter.default.publisher(for: .NSPersistentStoreRemoteChange, object: persistentContainer.persistentStoreCoordinator)
accountPreferences = AccountPreferences.default(account: accountInfo!, context: persistentContainer.viewContext) .receive(on: DispatchQueue.main)
persistentContainer.save(context: persistentContainer.viewContext) .sink { [unowned self] _ in
self.loadAccountPreferences()
} }
.store(in: &cancellables)
Task { Task {
do { do {
@ -177,6 +179,16 @@ class MastodonController: ObservableObject {
} }
} }
@MainActor
private func loadAccountPreferences() {
if let existing = try? persistentContainer.viewContext.fetch(AccountPreferences.fetchRequest(account: accountInfo!)).first {
accountPreferences = existing
} else {
accountPreferences = AccountPreferences.default(account: accountInfo!, context: persistentContainer.viewContext)
persistentContainer.save(context: persistentContainer.viewContext)
}
}
func getOwnAccount(completion: ((Result<Account, Client.Error>) -> Void)? = nil) { func getOwnAccount(completion: ((Result<Account, Client.Error>) -> Void)? = nil) {
if account != nil { if account != nil {
completion?(.success(account)) completion?(.success(account))

View File

@ -16,10 +16,12 @@ public final class AccountPreferences: NSManagedObject {
@nonobjc class func fetchRequest(account: LocalData.UserAccountInfo) -> NSFetchRequest<AccountPreferences> { @nonobjc class func fetchRequest(account: LocalData.UserAccountInfo) -> NSFetchRequest<AccountPreferences> {
let req = NSFetchRequest<AccountPreferences>(entityName: "AccountPreferences") let req = NSFetchRequest<AccountPreferences>(entityName: "AccountPreferences")
req.predicate = NSPredicate(format: "accountID = %@", account.id) req.predicate = NSPredicate(format: "accountID = %@", account.id)
req.sortDescriptors = [NSSortDescriptor(key: "createdAt", ascending: true)]
return req return req
} }
@NSManaged public var accountID: String @NSManaged public var accountID: String
@NSManaged var createdAt: Date
@NSManaged var pinnedTimelinesData: Data? @NSManaged var pinnedTimelinesData: Data?
@LazilyDecoding(from: \AccountPreferences.pinnedTimelinesData, fallback: []) @LazilyDecoding(from: \AccountPreferences.pinnedTimelinesData, fallback: [])
@ -28,6 +30,7 @@ public final class AccountPreferences: NSManagedObject {
static func `default`(account: LocalData.UserAccountInfo, context: NSManagedObjectContext) -> AccountPreferences { static func `default`(account: LocalData.UserAccountInfo, context: NSManagedObjectContext) -> AccountPreferences {
let prefs = AccountPreferences(context: context) let prefs = AccountPreferences(context: context)
prefs.accountID = account.id prefs.accountID = account.id
prefs.createdAt = Date()
prefs.pinnedTimelines = [.home, .public(local: true), .public(local: false)] prefs.pinnedTimelines = [.home, .public(local: true), .public(local: false)]
return prefs return prefs
} }

View File

@ -16,10 +16,12 @@ public final class TimelinePosition: NSManagedObject {
@nonobjc class func fetchRequest(timeline: Timeline, account: LocalData.UserAccountInfo) -> NSFetchRequest<TimelinePosition> { @nonobjc class func fetchRequest(timeline: Timeline, account: LocalData.UserAccountInfo) -> NSFetchRequest<TimelinePosition> {
let req = NSFetchRequest<TimelinePosition>(entityName: "TimelinePosition") let req = NSFetchRequest<TimelinePosition>(entityName: "TimelinePosition")
req.predicate = NSPredicate(format: "accountID = %@ AND timelineKind = %@", account.id, toTimelineKind(timeline)) req.predicate = NSPredicate(format: "accountID = %@ AND timelineKind = %@", account.id, toTimelineKind(timeline))
req.sortDescriptors = [NSSortDescriptor(key: "createdAt", ascending: true)]
return req return req
} }
@NSManaged public var accountID: String @NSManaged public var accountID: String
@NSManaged public var createdAt: Date
@NSManaged private var timelineKind: String @NSManaged private var timelineKind: String
@NSManaged public var centerStatusID: String? @NSManaged public var centerStatusID: String?
@NSManaged private var statusIDsData: Data? @NSManaged private var statusIDsData: Data?
@ -36,6 +38,7 @@ public final class TimelinePosition: NSManagedObject {
self.init(context: context) self.init(context: context)
self.timeline = timeline self.timeline = timeline
self.accountID = account.id self.accountID = account.id
self.createdAt = Date()
} }
} }

View File

@ -1,5 +1,5 @@
<?xml version="1.0" encoding="UTF-8" standalone="yes"?> <?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<model type="com.apple.IDECoreDataModeler.DataModel" documentVersion="1.0" lastSavedToolsVersion="21513" systemVersion="22A380" minimumToolsVersion="Automatic" sourceLanguage="Swift" userDefinedModelVersionIdentifier=""> <model type="com.apple.IDECoreDataModeler.DataModel" documentVersion="1.0" lastSavedToolsVersion="21513" systemVersion="22C65" 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="avatar" optional="YES" attributeType="URI"/> <attribute name="avatar" optional="YES" attributeType="URI"/>
@ -30,6 +30,7 @@
</entity> </entity>
<entity name="AccountPreferences" representedClassName="AccountPreferences" syncable="YES"> <entity name="AccountPreferences" representedClassName="AccountPreferences" syncable="YES">
<attribute name="accountID" optional="YES" attributeType="String"/> <attribute name="accountID" optional="YES" attributeType="String"/>
<attribute name="createdAt" optional="YES" attributeType="Date" usesScalarValueType="NO"/>
<attribute name="pinnedTimelinesData" optional="YES" attributeType="Binary"/> <attribute name="pinnedTimelinesData" optional="YES" attributeType="Binary"/>
</entity> </entity>
<entity name="Filter" representedClassName="FilterMO" syncable="YES"> <entity name="Filter" representedClassName="FilterMO" syncable="YES">
@ -120,6 +121,7 @@
<entity name="TimelinePosition" representedClassName="TimelinePosition" syncable="YES"> <entity name="TimelinePosition" representedClassName="TimelinePosition" syncable="YES">
<attribute name="accountID" optional="YES" attributeType="String"/> <attribute name="accountID" optional="YES" attributeType="String"/>
<attribute name="centerStatusID" optional="YES" attributeType="String"/> <attribute name="centerStatusID" optional="YES" attributeType="String"/>
<attribute name="createdAt" optional="YES" attributeType="Date" usesScalarValueType="NO"/>
<attribute name="statusIDsData" optional="YES" attributeType="Binary" valueTransformerName="TimelinePositionStatusIDsTransformer"/> <attribute name="statusIDsData" optional="YES" attributeType="Binary" valueTransformerName="TimelinePositionStatusIDsTransformer"/>
<attribute name="timelineKind" optional="YES" attributeType="String"/> <attribute name="timelineKind" optional="YES" attributeType="String"/>
</entity> </entity>