From 102fe6ed912165950295d8083aa435b540d6650b Mon Sep 17 00:00:00 2001 From: Shadowfacts Date: Sat, 11 Apr 2020 22:23:31 -0400 Subject: [PATCH] Convert API objects to CoreData models and save them --- Tusker/CoreData/AccountMO.swift | 67 ++++++++++++++----- .../MastodonCachePersistentStore.swift | 39 +++++++++-- Tusker/CoreData/StatusMO.swift | 64 ++++++++++++++++-- .../Tusker.xcdatamodel/contents | 6 +- Tusker/MastodonCache.swift | 14 ++++ 5 files changed, 161 insertions(+), 29 deletions(-) diff --git a/Tusker/CoreData/AccountMO.swift b/Tusker/CoreData/AccountMO.swift index 74409cf8..5920781c 100644 --- a/Tusker/CoreData/AccountMO.swift +++ b/Tusker/CoreData/AccountMO.swift @@ -12,29 +12,29 @@ import CoreData import Pachyderm @objc(AccountMO) -public class AccountMO: NSManagedObject { +public final class AccountMO: NSManagedObject { @nonobjc public class func fetchRequest() -> NSFetchRequest { return NSFetchRequest(entityName: "Account") } - @NSManaged public var acct: String? - @NSManaged public var avatar: URL? + @NSManaged public var acct: String + @NSManaged public var avatar: URL @NSManaged public var bot: Bool - @NSManaged public var createdAt: Date? - @NSManaged public var displayName: String? - @NSManaged public var emojisData: Data? - @NSManaged public var fieldsData: Data? - @NSManaged public var followersCount: Int64 - @NSManaged public var followingCount: Int64 - @NSManaged public var header: URL? - @NSManaged public var id: String? + @NSManaged public var createdAt: Date + @NSManaged public var displayName: String + @NSManaged private var emojisData: Data? + @NSManaged private var fieldsData: Data? + @NSManaged public var followersCount: Int + @NSManaged public var followingCount: Int + @NSManaged public var header: URL + @NSManaged public var id: String @NSManaged public var locked: Bool @NSManaged public var moved: Bool - @NSManaged public var note: String? - @NSManaged public var statusesCount: Int64 - @NSManaged public var url: URL? - @NSManaged public var username: String? + @NSManaged public var note: String + @NSManaged public var statusesCount: Int + @NSManaged public var url: URL + @NSManaged public var username: String @NSManaged public var movedTo: AccountMO? @LazilyDecoding(arrayFrom: \AccountMO.emojisData) @@ -44,3 +44,40 @@ public class AccountMO: NSManagedObject { var fields: [Account.Field] } + +extension AccountMO { + convenience init(apiAccount account: Pachyderm.Account, container: MastodonCachePersistentStore, context: NSManagedObjectContext) { + self.init(context: context) + self.updateFrom(apiAccount: account, container: container) + } + + func updateFrom(apiAccount account: Pachyderm.Account, container: MastodonCachePersistentStore) { + guard let context = managedObjectContext else { + // we've been deleted, don't bother updating + return + } + + self.acct = account.acct + self.avatar = account.avatar + self.bot = account.bot ?? false + self.createdAt = account.createdAt + self.displayName = account.displayName + self.emojis = account.emojis + self.fields = account.fields ?? [] + self.followersCount = account.followersCount + self.followingCount = account.followingCount + self.header = account.header + self.id = account.id + self.locked = account.locked + self.moved = account.moved ?? false + self.note = account.note + self.statusesCount = account.statusesCount + self.url = account.url + self.username = account.username + if let movedTo = account.movedTo { + self.movedTo = container.account(for: movedTo.id, in: context) ?? AccountMO(apiAccount: movedTo, container: container, context: context) + } else { + self.movedTo = nil + } + } +} diff --git a/Tusker/CoreData/MastodonCachePersistentStore.swift b/Tusker/CoreData/MastodonCachePersistentStore.swift index a522cfa1..21345974 100644 --- a/Tusker/CoreData/MastodonCachePersistentStore.swift +++ b/Tusker/CoreData/MastodonCachePersistentStore.swift @@ -8,9 +8,12 @@ import Foundation import CoreData +import Pachyderm class MastodonCachePersistentStore: NSPersistentContainer { + private(set) lazy var backgroundContext = newBackgroundContext() + init(for controller: MastodonController) { let url = Bundle.main.url(forResource: "Tusker", withExtension: "momd")! let model = NSManagedObjectModel(contentsOf: url)! @@ -22,26 +25,54 @@ class MastodonCachePersistentStore: NSPersistentContainer { } } - func status(for id: String) -> StatusMO? { + func status(for id: String, in context: NSManagedObjectContext? = nil) -> StatusMO? { + let context = context ?? viewContext let request: NSFetchRequest = StatusMO.fetchRequest() request.predicate = NSPredicate(format: "id = %@", id) request.fetchLimit = 1 - if let result = try? viewContext.fetch(request), let status = result.first { + if let result = try? context.fetch(request), let status = result.first { return status } else { return nil } } - func account(for id: String) -> AccountMO? { + func addOrUpdate(status: Status, save: Bool = true) { + backgroundContext.perform { + if let statusMO = self.status(for: status.id, in: self.backgroundContext) { + statusMO.updateFrom(apiStatus: status, container: self) + } else { + _ = StatusMO(apiStatus: status, container: self, context: self.backgroundContext) + } + if save, self.backgroundContext.hasChanges { + try! self.backgroundContext.save() + } + } + } + + func account(for id: String, in context: NSManagedObjectContext? = nil) -> AccountMO? { + let context = context ?? viewContext let request: NSFetchRequest = AccountMO.fetchRequest() request.predicate = NSPredicate(format: "id = %@", id) request.fetchLimit = 1 - if let result = try? viewContext.fetch(request), let account = result.first { + if let result = try? context.fetch(request), let account = result.first { return account } else { return nil } } + func addOrUpdate(account: Account, save: Bool = true) { + backgroundContext.perform { + if let accountMO = self.account(for: account.id, in: self.backgroundContext) { + accountMO.updateFrom(apiAccount: account, container: self) + } else { + _ = AccountMO(apiAccount: account, container: self, context: self.backgroundContext) + } + if save, self.backgroundContext.hasChanges { + try! self.backgroundContext.save() + } + } + } + } diff --git a/Tusker/CoreData/StatusMO.swift b/Tusker/CoreData/StatusMO.swift index 34251ab5..182a5dc8 100644 --- a/Tusker/CoreData/StatusMO.swift +++ b/Tusker/CoreData/StatusMO.swift @@ -19,7 +19,7 @@ public final class StatusMO: NSManagedObject { } @NSManaged public var application: String? - @NSManaged public var attachmentsData: Data? + @NSManaged private var attachmentsData: Data? @NSManaged public var bookmarked: Bool @NSManaged public var content: String @NSManaged public var createdAt: Date @@ -28,25 +28,25 @@ public final class StatusMO: NSManagedObject { @NSManaged public var favouritesCount: Int @NSManaged public var hashtagsData: Data? @NSManaged public var id: String - @NSManaged public var mentionsData: Data? + @NSManaged public var inReplyToAccountID: String? + @NSManaged public var inReplyToID: String? + @NSManaged private var mentionsData: Data? @NSManaged public var muted: Bool @NSManaged public var pinned: Bool @NSManaged public var reblogged: Bool @NSManaged public var reblogsCount: Int @NSManaged public var sensitive: Bool - @NSManaged public var uri: String + @NSManaged public var uri: String // todo: are both uri and url necessary? @NSManaged public var url: URL? - @NSManaged public var visibility: String? + @NSManaged private var visibilityString: String @NSManaged public var account: AccountMO - @NSManaged public var inReplyTo: StatusMO? - @NSManaged public var inReplyToAccount: AccountMO? @NSManaged public var reblog: StatusMO? @LazilyDecoding(arrayFrom: \StatusMO.attachmentsData) var attachments: [Attachment] @LazilyDecoding(arrayFrom: \StatusMO.emojisData) - var emoji: [Emoji] + var emojis: [Emoji] @LazilyDecoding(arrayFrom: \StatusMO.hashtagsData) var hashtags: [Hashtag] @@ -54,4 +54,54 @@ public final class StatusMO: NSManagedObject { @LazilyDecoding(arrayFrom: \StatusMO.mentionsData) var mentions: [Mention] + var visibility: Status.Visibility { + get { + Pachyderm.Status.Visibility(rawValue: visibilityString) ?? .public + } + set { + visibilityString = newValue.rawValue + } + } + +} + +extension StatusMO { + convenience init(apiStatus status: Pachyderm.Status, container: MastodonCachePersistentStore, context: NSManagedObjectContext) { + self.init(context: context) + self.updateFrom(apiStatus: status, container: container) + } + + func updateFrom(apiStatus status: Pachyderm.Status, container: MastodonCachePersistentStore) { + guard let context = managedObjectContext else { + // we have been deleted, don't bother updating + return + } + + self.application = status.application?.name + self.attachments = status.attachments + self.bookmarked = status.bookmarked ?? false + self.content = status.content + self.createdAt = status.createdAt + self.emojis = status.emojis + self.favourited = status.favourited ?? false + self.favouritesCount = status.favouritesCount + self.hashtags = status.hashtags + self.inReplyToAccountID = status.inReplyToAccountID + self.inReplyToID = status.inReplyToID + self.id = status.id + self.mentions = status.mentions + self.muted = status.muted ?? false + self.pinned = status.pinned ?? false + self.reblogged = status.reblogged ?? false + self.reblogsCount = status.reblogsCount + self.sensitive = status.sensitive + self.uri = status.uri + self.visibility = status.visibility + self.account = container.account(for: status.account.id, in: context) ?? AccountMO(apiAccount: status.account, container: container, context: context) + if let reblog = status.reblog { + self.reblog = container.status(for: reblog.id, in: context) ?? StatusMO(apiStatus: reblog, container: container, context: context) + } else { + self.reblog = nil + } + } } diff --git a/Tusker/CoreData/Tusker.xcdatamodeld/Tusker.xcdatamodel/contents b/Tusker/CoreData/Tusker.xcdatamodeld/Tusker.xcdatamodel/contents index a4de90e0..c1b1a524 100644 --- a/Tusker/CoreData/Tusker.xcdatamodeld/Tusker.xcdatamodel/contents +++ b/Tusker/CoreData/Tusker.xcdatamodeld/Tusker.xcdatamodel/contents @@ -36,6 +36,8 @@ + + @@ -44,10 +46,8 @@ - + - - diff --git a/Tusker/MastodonCache.swift b/Tusker/MastodonCache.swift index 9aec17c6..4e22df13 100644 --- a/Tusker/MastodonCache.swift +++ b/Tusker/MastodonCache.swift @@ -59,10 +59,17 @@ class MastodonCache { func add(status: Status) { set(status: status, for: status.id) + mastodonController?.persistentContainer.addOrUpdate(status: status) } func addAll(statuses: [Status]) { statuses.forEach(add) + if let container = mastodonController?.persistentContainer { + statuses.forEach { container.addOrUpdate(status: $0, save: false) } + container.backgroundContext.perform { + try! container.backgroundContext.save() + } + } } // MARK: - Accounts @@ -92,10 +99,17 @@ class MastodonCache { func add(account: Account) { set(account: account, for: account.id) + mastodonController?.persistentContainer.addOrUpdate(account: account) } func addAll(accounts: [Account]) { accounts.forEach(add) + if let container = mastodonController?.persistentContainer { + accounts.forEach { container.addOrUpdate(account: $0, save: false) } + container.backgroundContext.perform { + try! container.backgroundContext.save() + } + } } // MARK: - Relationships