diff --git a/CoreDataStack/CoreData.xcdatamodeld/CoreData.xcdatamodel/contents b/CoreDataStack/CoreData.xcdatamodeld/CoreData.xcdatamodel/contents
index ce454981b..e8d8dd605 100644
--- a/CoreDataStack/CoreData.xcdatamodeld/CoreData.xcdatamodel/contents
+++ b/CoreDataStack/CoreData.xcdatamodeld/CoreData.xcdatamodel/contents
@@ -1,6 +1,7 @@
+
@@ -116,13 +117,14 @@
+
+
-
\ No newline at end of file
diff --git a/CoreDataStack/Entity/Toot.swift b/CoreDataStack/Entity/Toot.swift
index 65971ffe9..c43d0d6c3 100644
--- a/CoreDataStack/Entity/Toot.swift
+++ b/CoreDataStack/Entity/Toot.swift
@@ -89,9 +89,8 @@ public extension Toot {
toot.sensitive = property.sensitive
toot.spoilerText = property.spoilerText
- if let application = property.application {
- toot.mutableSetValue(forKey: #keyPath(Toot.application)).add(application)
- }
+ toot.application = property.application
+
if let mentions = property.mentions {
toot.mutableSetValue(forKey: #keyPath(Toot.mentions)).addObjects(from: mentions)
}
@@ -139,6 +138,28 @@ public extension Toot {
return toot
}
+ func update(reblogsCount: NSNumber) {
+ if self.reblogsCount.intValue != reblogsCount.intValue {
+ self.reblogsCount = reblogsCount
+ }
+ }
+ func update(favouritesCount: NSNumber) {
+ if self.favouritesCount.intValue != favouritesCount.intValue {
+ self.favouritesCount = favouritesCount
+ }
+ }
+ func update(repliesCount: NSNumber?) {
+ guard let count = repliesCount else {
+ return
+ }
+ if self.repliesCount?.intValue != count.intValue {
+ self.repliesCount = repliesCount
+ }
+ }
+ func didUpdate(at networkDate: Date) {
+ self.updatedAt = networkDate
+ }
+
}
public extension Toot {
diff --git a/Mastodon.xcodeproj/project.pbxproj b/Mastodon.xcodeproj/project.pbxproj
index eb61a8d76..e67aded30 100644
--- a/Mastodon.xcodeproj/project.pbxproj
+++ b/Mastodon.xcodeproj/project.pbxproj
@@ -23,6 +23,7 @@
2D61335E25C1894B00CAE157 /* APIService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2D61335D25C1894B00CAE157 /* APIService.swift */; };
2D61336925C18A4F00CAE157 /* AlamofireNetworkActivityIndicator in Frameworks */ = {isa = PBXBuildFile; productRef = 2D61336825C18A4F00CAE157 /* AlamofireNetworkActivityIndicator */; };
2D69CFF425CA9E2200C3A1B2 /* LoadMoreConfigurableTableViewContainer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2D69CFF325CA9E2200C3A1B2 /* LoadMoreConfigurableTableViewContainer.swift */; };
+ 2D69D00A25CAA00300C3A1B2 /* APIService+CoreData+Toot.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2D69D00925CAA00300C3A1B2 /* APIService+CoreData+Toot.swift */; };
2D76316525C14BD100929FB9 /* PublicTimelineViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2D76316425C14BD100929FB9 /* PublicTimelineViewController.swift */; };
2D76316B25C14D4C00929FB9 /* PublicTimelineViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2D76316A25C14D4C00929FB9 /* PublicTimelineViewModel.swift */; };
2D76317D25C14DF500929FB9 /* PublicTimelineViewController+StatusProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2D76317C25C14DF400929FB9 /* PublicTimelineViewController+StatusProvider.swift */; };
@@ -36,7 +37,6 @@
2D927F1425C7EDD9004F19B8 /* Emoji.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2D927F1325C7EDD9004F19B8 /* Emoji.swift */; };
2DA7D04425CA52B200804E11 /* TimelineLoaderTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2DA7D04325CA52B200804E11 /* TimelineLoaderTableViewCell.swift */; };
2DA7D04A25CA52CB00804E11 /* TimelineBottomLoaderTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2DA7D04925CA52CB00804E11 /* TimelineBottomLoaderTableViewCell.swift */; };
- 2DA7D05125CA545E00804E11 /* LoadMoreConfigurableTableViewContainer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2DA7D05025CA545E00804E11 /* LoadMoreConfigurableTableViewContainer.swift */; };
2DA7D05725CA693F00804E11 /* Application.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2DA7D05625CA693F00804E11 /* Application.swift */; };
2DF123A725C3B0210020F248 /* ActiveLabel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2DF123A625C3B0210020F248 /* ActiveLabel.swift */; };
3533495136D843E85211E3E2 /* Pods_Mastodon_MastodonUITests.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = A1B4523A7981F1044DE89C21 /* Pods_Mastodon_MastodonUITests.framework */; };
@@ -165,6 +165,7 @@
2D61335725C188A000CAE157 /* APIService+Persist+Timeline.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "APIService+Persist+Timeline.swift"; sourceTree = ""; };
2D61335D25C1894B00CAE157 /* APIService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = APIService.swift; sourceTree = ""; };
2D69CFF325CA9E2200C3A1B2 /* LoadMoreConfigurableTableViewContainer.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LoadMoreConfigurableTableViewContainer.swift; sourceTree = ""; };
+ 2D69D00925CAA00300C3A1B2 /* APIService+CoreData+Toot.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "APIService+CoreData+Toot.swift"; sourceTree = ""; };
2D76316425C14BD100929FB9 /* PublicTimelineViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PublicTimelineViewController.swift; sourceTree = ""; };
2D76316A25C14D4C00929FB9 /* PublicTimelineViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PublicTimelineViewModel.swift; sourceTree = ""; };
2D76317C25C14DF400929FB9 /* PublicTimelineViewController+StatusProvider.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "PublicTimelineViewController+StatusProvider.swift"; sourceTree = ""; };
@@ -573,6 +574,7 @@
DB45FB0925CA87BC005A8AC7 /* CoreData */ = {
isa = PBXGroup;
children = (
+ 2D69D00925CAA00300C3A1B2 /* APIService+CoreData+Toot.swift */,
DB45FADC25CA6F6B005A8AC7 /* APIService+CoreData+MastodonUser.swift */,
DB45FAF825CA80A2005A8AC7 /* APIService+CoreData+MastodonAuthentication.swift */,
);
@@ -1067,6 +1069,7 @@
2D42FF8F25C8228A004A627A /* UIButton.swift in Sources */,
2D61335825C188A000CAE157 /* APIService+Persist+Timeline.swift in Sources */,
DB45FAE325CA7181005A8AC7 /* MastodonUser.swift in Sources */,
+ 2D69D00A25CAA00300C3A1B2 /* APIService+CoreData+Toot.swift in Sources */,
DB98338825C945ED00AD9700 /* Assets.swift in Sources */,
2DA7D04425CA52B200804E11 /* TimelineLoaderTableViewCell.swift in Sources */,
DB8AF52F25C13561002E6C99 /* DocumentStore.swift in Sources */,
diff --git a/Mastodon/Service/APIService/CoreData/APIService+CoreData+Toot.swift b/Mastodon/Service/APIService/CoreData/APIService+CoreData+Toot.swift
new file mode 100644
index 000000000..8f11fa214
--- /dev/null
+++ b/Mastodon/Service/APIService/CoreData/APIService+CoreData+Toot.swift
@@ -0,0 +1,130 @@
+//
+// APIService+CoreData+Toot.swift
+// Mastodon
+//
+// Created by sxiaojian on 2021/2/3.
+//
+
+import Foundation
+import CoreData
+import CoreDataStack
+import CommonOSLog
+import MastodonSDK
+
+extension APIService.CoreData {
+
+ static func createOrMergeTweet(
+ into managedObjectContext: NSManagedObjectContext,
+ for requestMastodonUser: MastodonUser,
+ entity: Mastodon.Entity.Toot,
+ domain: String,
+ networkDate: Date,
+ log: OSLog
+ ) -> (Toot: Toot, isTweetCreated: Bool, isMastodonUserCreated: Bool) {
+
+ // build tree
+ let reblog = entity.reblog.flatMap { entity -> Toot in
+ let (toot, _, _) = createOrMergeTweet(into: managedObjectContext, for: requestMastodonUser, entity: entity,domain: domain, networkDate: networkDate, log: log)
+ return toot
+ }
+
+ // fetch old Toot
+ let oldTweet: Toot? = {
+ let request = Toot.sortedFetchRequest
+ request.predicate = Toot.predicate(idStr: entity.id)
+ request.returnsObjectsAsFaults = false
+ do {
+ return try managedObjectContext.fetch(request).first
+ } catch {
+ assertionFailure(error.localizedDescription)
+ return nil
+ }
+ }()
+
+ if let oldTweet = oldTweet {
+ // merge old Toot
+ APIService.CoreData.mergeToot(for: requestMastodonUser, old: oldTweet,in: domain, entity: entity, networkDate: networkDate)
+ return (oldTweet, false, false)
+ } else {
+
+ let (mastodonUser, isMastodonUserCreated) = createOrMergeMastodonUser(into: managedObjectContext, for: requestMastodonUser,in: domain, entity: entity.account, networkDate: networkDate, log: log)
+ let application = entity.application.flatMap { (app) -> Application? in
+ Application.insert(into: managedObjectContext, property: Application.Property(name: app.name, website: app.website, vapidKey: app.vapidKey))
+ }
+
+ let metions = entity.mentions?.compactMap({ (mention) -> Mention in
+ Mention.insert(into: managedObjectContext, property: Mention.Property(id: mention.id, username: mention.username, acct: mention.acct, url: mention.url))
+ })
+ let emojis = entity.emojis?.compactMap({ (emoji) -> Emoji in
+ Emoji.insert(into: managedObjectContext, property: Emoji.Property(shortcode: emoji.shortcode, url: emoji.url, staticURL: emoji.staticURL, visibleInPicker: emoji.visibleInPicker, category: emoji.category))
+ })
+ let tags = entity.tags?.compactMap({ (tag) -> Tag in
+ let histories = tag.history?.compactMap({ (history) -> History in
+ History.insert(into: managedObjectContext, property: History.Property(day: history.day, uses: history.uses, accounts: history.accounts))
+ })
+ return Tag.insert(into: managedObjectContext, property: Tag.Property(name: tag.name, url: tag.url, histories: histories))
+ })
+ let tootProperty = Toot.Property(
+ domain: domain,
+ id: entity.id,
+ uri: entity.uri,
+ createdAt: entity.createdAt,
+ content: entity.content,
+ visibility: entity.visibility?.rawValue,
+ sensitive: entity.sensitive ?? false,
+ spoilerText: entity.spoilerText,
+ application: application,
+ mentions: metions,
+ emojis: emojis,
+ tags: tags,
+ reblogsCount: NSNumber(value: entity.reblogsCount),
+ favouritesCount: NSNumber(value: entity.favouritesCount),
+ repliesCount: (entity.repliesCount != nil) ? NSNumber(value: entity.repliesCount!) : nil,
+ url: entity.uri,
+ inReplyToID: entity.inReplyToID,
+ inReplyToAccountID: entity.inReplyToAccountID,
+ reblog: reblog,
+ language: entity.language,
+ text: entity.text,
+ favouritedBy: (entity.favourited ?? false) ? mastodonUser : nil,
+ rebloggedBy: (entity.reblogged ?? false) ? mastodonUser : nil,
+ mutedBy: (entity.muted ?? false) ? mastodonUser : nil,
+ bookmarkedBy: (entity.bookmarked ?? false) ? mastodonUser : nil,
+ pinnedBy: (entity.pinned ?? false) ? mastodonUser : nil,
+ updatedAt: networkDate,
+ deletedAt: nil,
+ author: requestMastodonUser,
+ homeTimelineIndexes: nil)
+ let toot = Toot.insert(into: managedObjectContext, property: tootProperty, author: mastodonUser)
+ return (toot, true, isMastodonUserCreated)
+ }
+ }
+ static func mergeToot(for requestMastodonUser: MastodonUser?, old toot: Toot,in domain: String, entity: Mastodon.Entity.Toot, networkDate: Date) {
+ guard networkDate > toot.updatedAt else { return }
+
+ // merge
+ if entity.favouritesCount != toot.favouritesCount.intValue {
+ toot.update(favouritesCount:NSNumber(value: entity.favouritesCount))
+ }
+ if let repliesCount = entity.repliesCount {
+ if (repliesCount != toot.repliesCount?.intValue) {
+ toot.update(repliesCount:NSNumber(value: repliesCount))
+ }
+ }
+ if entity.reblogsCount != toot.reblogsCount.intValue {
+ toot.update(reblogsCount:NSNumber(value: entity.reblogsCount))
+ }
+
+
+ // set updateAt
+ toot.didUpdate(at: networkDate)
+
+ // merge user
+ mergeMastodonUser(for: requestMastodonUser, old: toot.author, in: domain, entity: entity.account, networkDate: networkDate)
+ // merge indirect reblog & quote
+ if let reblog = entity.reblog {
+ mergeToot(for: requestMastodonUser, old: toot.reblog!,in: domain, entity: reblog, networkDate: networkDate)
+ }
+ }
+
+}