diff --git a/CoreDataStack/CoreData.xcdatamodeld/CoreData.xcdatamodel/contents b/CoreDataStack/CoreData.xcdatamodeld/CoreData.xcdatamodel/contents
index 24a3955e7..f635a3db0 100644
--- a/CoreDataStack/CoreData.xcdatamodeld/CoreData.xcdatamodel/contents
+++ b/CoreDataStack/CoreData.xcdatamodeld/CoreData.xcdatamodel/contents
@@ -149,6 +149,7 @@
+
@@ -194,24 +195,25 @@
+
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
-
+
\ No newline at end of file
diff --git a/CoreDataStack/Entity/History.swift b/CoreDataStack/Entity/History.swift
index 114879298..6fe703e84 100644
--- a/CoreDataStack/Entity/History.swift
+++ b/CoreDataStack/Entity/History.swift
@@ -40,6 +40,26 @@ public extension History {
}
}
+public extension History {
+ func update(day: Date) {
+ if self.day != day {
+ self.day = day
+ }
+ }
+
+ func update(uses: String) {
+ if self.uses != uses {
+ self.uses = uses
+ }
+ }
+
+ func update(accounts: String) {
+ if self.accounts != accounts {
+ self.accounts = accounts
+ }
+ }
+}
+
public extension History {
struct Property {
public let day: Date
diff --git a/CoreDataStack/Entity/SearchHistory.swift b/CoreDataStack/Entity/SearchHistory.swift
index 33b8a6010..d924917ee 100644
--- a/CoreDataStack/Entity/SearchHistory.swift
+++ b/CoreDataStack/Entity/SearchHistory.swift
@@ -12,6 +12,7 @@ public final class SearchHistory: NSManagedObject {
public typealias ID = UUID
@NSManaged public private(set) var identifier: ID
@NSManaged public private(set) var createAt: Date
+ @NSManaged public private(set) var updatedAt: Date
@NSManaged public private(set) var account: MastodonUser?
@NSManaged public private(set) var hashtag: Tag?
@@ -22,6 +23,13 @@ extension SearchHistory {
public override func awakeFromInsert() {
super.awakeFromInsert()
setPrimitiveValue(UUID(), forKey: #keyPath(SearchHistory.identifier))
+ setPrimitiveValue(Date(), forKey: #keyPath(SearchHistory.createAt))
+ setPrimitiveValue(Date(), forKey: #keyPath(SearchHistory.updatedAt))
+ }
+
+ public override func willSave() {
+ super.willSave()
+ setPrimitiveValue(Date(), forKey: #keyPath(SearchHistory.updatedAt))
}
@discardableResult
@@ -31,7 +39,6 @@ extension SearchHistory {
) -> SearchHistory {
let searchHistory: SearchHistory = context.insertObject()
searchHistory.account = account
- searchHistory.createAt = Date()
return searchHistory
}
@@ -42,13 +49,18 @@ extension SearchHistory {
) -> SearchHistory {
let searchHistory: SearchHistory = context.insertObject()
searchHistory.hashtag = hashtag
- searchHistory.createAt = Date()
return searchHistory
}
}
+public extension SearchHistory {
+ func update(updatedAt: Date) {
+ setValue(updatedAt, forKey: #keyPath(SearchHistory.updatedAt))
+ }
+}
+
extension SearchHistory: Managed {
public static var defaultSortDescriptors: [NSSortDescriptor] {
- return [NSSortDescriptor(keyPath: \SearchHistory.createAt, ascending: false)]
+ return [NSSortDescriptor(keyPath: \SearchHistory.updatedAt, ascending: false)]
}
}
diff --git a/CoreDataStack/Entity/Tag.swift b/CoreDataStack/Entity/Tag.swift
index 2f1914c4a..3044cacc0 100644
--- a/CoreDataStack/Entity/Tag.swift
+++ b/CoreDataStack/Entity/Tag.swift
@@ -12,25 +12,33 @@ public final class Tag: NSManagedObject {
public typealias ID = UUID
@NSManaged public private(set) var identifier: ID
@NSManaged public private(set) var createAt: Date
-
+ @NSManaged public private(set) var updatedAt: Date
+
@NSManaged public private(set) var name: String
@NSManaged public private(set) var url: String
-
+
// many-to-many relationship
@NSManaged public private(set) var statuses: Set?
-
+
// one-to-many relationship
@NSManaged public private(set) var histories: Set?
}
-extension Tag {
- public override func awakeFromInsert() {
+public extension Tag {
+ override func awakeFromInsert() {
super.awakeFromInsert()
setPrimitiveValue(UUID(), forKey: #keyPath(Tag.identifier))
+ setPrimitiveValue(Date(), forKey: #keyPath(Tag.createAt))
+ setPrimitiveValue(Date(), forKey: #keyPath(Tag.updatedAt))
}
-
+
+ override func willSave() {
+ super.willSave()
+ setPrimitiveValue(Date(), forKey: #keyPath(Tag.updatedAt))
+ }
+
@discardableResult
- public static func insert(
+ static func insert(
into context: NSManagedObjectContext,
property: Property
) -> Tag {
@@ -44,8 +52,8 @@ extension Tag {
}
}
-extension Tag {
- public struct Property {
+public extension Tag {
+ struct Property {
public let name: String
public let url: String
public let histories: [History]?
@@ -58,8 +66,36 @@ extension Tag {
}
}
-extension Tag: Managed {
- public static var defaultSortDescriptors: [NSSortDescriptor] {
- return [NSSortDescriptor(keyPath: \Tag.createAt, ascending: false)]
+public extension Tag {
+ func updateHistory(index: Int, day: Date, uses: String, account: String) {
+ guard let histories = self.histories?.sorted(by: {
+ $0.createAt.compare($1.createAt) == .orderedAscending
+ }) else { return }
+ let history = histories[index]
+ history.update(day: day)
+ history.update(uses: uses)
+ history.update(accounts: account)
+ }
+
+ func appendHistory(history: History) {
+ self.mutableSetValue(forKeyPath: #keyPath(Tag.histories)).add(history)
+ }
+
+ func update(url: String) {
+ if self.url != url {
+ self.url = url
+ }
+ }
+}
+
+extension Tag: Managed {
+ public static var defaultSortDescriptors: [NSSortDescriptor] {
+ [NSSortDescriptor(keyPath: \Tag.createAt, ascending: false)]
+ }
+}
+
+public extension Tag {
+ static func predicate(name: String) -> NSPredicate {
+ NSPredicate(format: "%K == %@", #keyPath(Tag.name), name)
}
}
diff --git a/Mastodon.xcodeproj/project.pbxproj b/Mastodon.xcodeproj/project.pbxproj
index 97b65eeb1..d002ccd1a 100644
--- a/Mastodon.xcodeproj/project.pbxproj
+++ b/Mastodon.xcodeproj/project.pbxproj
@@ -30,7 +30,6 @@
0FB3D33825E6401400AAD544 /* PickServerCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0FB3D33725E6401400AAD544 /* PickServerCell.swift */; };
18BC7629F65E6DB12CB8416D /* Pods_Mastodon_MastodonUITests.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 3C030226D3C73DCC23D67452 /* Pods_Mastodon_MastodonUITests.framework */; };
2D04F42525C255B9003F936F /* APIService+PublicTimeline.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2D04F42425C255B9003F936F /* APIService+PublicTimeline.swift */; };
- 2D0B7A0B261D5A5600B44727 /* Array.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2D0B7A0A261D5A5600B44727 /* Array.swift */; };
2D0B7A1D261D839600B44727 /* SearchHistory.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2D0B7A1C261D839600B44727 /* SearchHistory.swift */; };
2D152A8C25C295CC009AA50C /* StatusView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2D152A8B25C295CC009AA50C /* StatusView.swift */; };
2D152A9225C2980C009AA50C /* UIFont.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2D152A9125C2980C009AA50C /* UIFont.swift */; };
@@ -92,6 +91,7 @@
2D76319F25C1521200929FB9 /* StatusSection.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2D76319E25C1521200929FB9 /* StatusSection.swift */; };
2D7631A825C1535600929FB9 /* StatusTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2D7631A725C1535600929FB9 /* StatusTableViewCell.swift */; };
2D7631B325C159F700929FB9 /* Item.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2D7631B225C159F700929FB9 /* Item.swift */; };
+ 2D79E701261EA5550011E398 /* APIService+CoreData+Tag.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2D79E700261EA5550011E398 /* APIService+CoreData+Tag.swift */; };
2D82B9FF25E7863200E36F0F /* OnboardingViewControllerAppearance.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2D82B9FE25E7863200E36F0F /* OnboardingViewControllerAppearance.swift */; };
2D82BA0525E7897700E36F0F /* MastodonResendEmailViewModelNavigationDelegateShim.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2D82BA0425E7897700E36F0F /* MastodonResendEmailViewModelNavigationDelegateShim.swift */; };
2D8434F525FF465D00EECE90 /* HomeTimelineNavigationBarTitleViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2D8434F425FF465D00EECE90 /* HomeTimelineNavigationBarTitleViewModel.swift */; };
@@ -401,7 +401,6 @@
0FB3D33125E5F50E00AAD544 /* PickServerSearchCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PickServerSearchCell.swift; sourceTree = ""; };
0FB3D33725E6401400AAD544 /* PickServerCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PickServerCell.swift; sourceTree = ""; };
2D04F42425C255B9003F936F /* APIService+PublicTimeline.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "APIService+PublicTimeline.swift"; sourceTree = ""; };
- 2D0B7A0A261D5A5600B44727 /* Array.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Array.swift; sourceTree = ""; };
2D0B7A1C261D839600B44727 /* SearchHistory.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SearchHistory.swift; sourceTree = ""; };
2D152A8B25C295CC009AA50C /* StatusView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StatusView.swift; sourceTree = ""; };
2D152A9125C2980C009AA50C /* UIFont.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UIFont.swift; sourceTree = ""; };
@@ -460,6 +459,7 @@
2D76319E25C1521200929FB9 /* StatusSection.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StatusSection.swift; sourceTree = ""; };
2D7631A725C1535600929FB9 /* StatusTableViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StatusTableViewCell.swift; sourceTree = ""; };
2D7631B225C159F700929FB9 /* Item.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Item.swift; sourceTree = ""; };
+ 2D79E700261EA5550011E398 /* APIService+CoreData+Tag.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "APIService+CoreData+Tag.swift"; sourceTree = ""; };
2D82B9FE25E7863200E36F0F /* OnboardingViewControllerAppearance.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OnboardingViewControllerAppearance.swift; sourceTree = ""; };
2D82BA0425E7897700E36F0F /* MastodonResendEmailViewModelNavigationDelegateShim.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MastodonResendEmailViewModelNavigationDelegateShim.swift; sourceTree = ""; };
2D8434F425FF465D00EECE90 /* HomeTimelineNavigationBarTitleViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HomeTimelineNavigationBarTitleViewModel.swift; sourceTree = ""; };
@@ -1321,6 +1321,7 @@
2D69D00925CAA00300C3A1B2 /* APIService+CoreData+Status.swift */,
DB45FADC25CA6F6B005A8AC7 /* APIService+CoreData+MastodonUser.swift */,
DB45FAF825CA80A2005A8AC7 /* APIService+CoreData+MastodonAuthentication.swift */,
+ 2D79E700261EA5550011E398 /* APIService+CoreData+Tag.swift */,
);
path = CoreData;
sourceTree = "";
@@ -1550,7 +1551,6 @@
2D46975D25C2A54100CF4AA9 /* NSLayoutConstraint.swift */,
DB68586325E619B700F0A850 /* NSKeyValueObservation.swift */,
DB0140CE25C42AEE00F9F3CF /* OSLog.swift */,
- 2D0B7A0A261D5A5600B44727 /* Array.swift */,
DB68A06225E905E000CFDF14 /* UIApplication.swift */,
DB45FAB525CA5485005A8AC7 /* UIAlertController.swift */,
2D42FF8E25C8228A004A627A /* UIButton.swift */,
@@ -2207,6 +2207,7 @@
2DE0FACE2615F7AD00CDF649 /* RecommendAccountSection.swift in Sources */,
DB49A62B25FF36C700B98345 /* APIService+CustomEmoji.swift in Sources */,
DBCBED1D26132E1A00B49291 /* StatusFetchedResultsController.swift in Sources */,
+ 2D79E701261EA5550011E398 /* APIService+CoreData+Tag.swift in Sources */,
2D939AB525EDD8A90076FA61 /* String.swift in Sources */,
DB4481B925EE289600BEFB67 /* UITableView.swift in Sources */,
DBE3CDBB261C427900430CC6 /* TimelineHeaderTableViewCell.swift in Sources */,
@@ -2284,7 +2285,6 @@
2D939AE825EE1CF80076FA61 /* MastodonRegisterViewController+Avatar.swift in Sources */,
DB1D186C25EF5BA7003F1F23 /* PollTableView.swift in Sources */,
2D5981A125E4A593000FB903 /* MastodonConfirmEmailViewModel.swift in Sources */,
- 2D0B7A0B261D5A5600B44727 /* Array.swift in Sources */,
DB87D4452609BE0500D12C0D /* ComposeStatusPollOptionCollectionViewCell.swift in Sources */,
DB8AF55025C13703002E6C99 /* MainTabBarController.swift in Sources */,
DB9D6BE925E4F5340051B173 /* SearchViewController.swift in Sources */,
diff --git a/Mastodon/Extension/Array.swift b/Mastodon/Extension/Array.swift
deleted file mode 100644
index 91c2e3d66..000000000
--- a/Mastodon/Extension/Array.swift
+++ /dev/null
@@ -1,20 +0,0 @@
-//
-// Array.swift
-// Mastodon
-//
-// Created by sxiaojian on 2021/4/7.
-//
-
-import Foundation
-
-public extension Array where Element: Equatable {
-
- func removeDuplicate() -> Array {
- return self.enumerated().filter { (index,value) -> Bool in
- return self.firstIndex(of: value) == index
- }.map { (_, value) in
- value
- }
- }
-}
-
diff --git a/Mastodon/Scene/Search/CollectionViewCell/SearchRecommendTagsCollectionViewCell.swift b/Mastodon/Scene/Search/CollectionViewCell/SearchRecommendTagsCollectionViewCell.swift
index 08090f804..f7ff5f33e 100644
--- a/Mastodon/Scene/Search/CollectionViewCell/SearchRecommendTagsCollectionViewCell.swift
+++ b/Mastodon/Scene/Search/CollectionViewCell/SearchRecommendTagsCollectionViewCell.swift
@@ -82,7 +82,8 @@ extension SearchRecommendTagsCollectionViewCell {
peopleLabel.text = ""
return
}
- let recentHistory = historys[0...2]
+
+ let recentHistory = historys.prefix(2)
let peopleAreTalking = recentHistory.compactMap({ Int($0.accounts) }).reduce(0, +)
let string = L10n.Scene.Search.Recommend.HashTag.peopleTalking(String(peopleAreTalking))
peopleLabel.text = string
diff --git a/Mastodon/Scene/Search/SearchViewModel+LoadOldestState.swift b/Mastodon/Scene/Search/SearchViewModel+LoadOldestState.swift
index 7088f1360..c76ab202c 100644
--- a/Mastodon/Scene/Search/SearchViewModel+LoadOldestState.swift
+++ b/Mastodon/Scene/Search/SearchViewModel+LoadOldestState.swift
@@ -89,7 +89,8 @@ extension SearchViewModel.LoadOldestState {
var newAccounts = [Mastodon.Entity.Account]()
newAccounts.append(contentsOf: oldSearchResult.accounts)
newAccounts.append(contentsOf: result.value.accounts)
- viewModel.searchResult.value = Mastodon.Entity.SearchResult(accounts: newAccounts.removeDuplicate(), statuses: oldSearchResult.statuses, hashtags: oldSearchResult.hashtags)
+ newAccounts.removeDuplicates()
+ viewModel.searchResult.value = Mastodon.Entity.SearchResult(accounts: newAccounts, statuses: oldSearchResult.statuses, hashtags: oldSearchResult.hashtags)
stateMachine.enter(Idle.self)
}
case Mastodon.API.Search.SearchType.hashtags:
@@ -99,7 +100,8 @@ extension SearchViewModel.LoadOldestState {
var newTags = [Mastodon.Entity.Tag]()
newTags.append(contentsOf: oldSearchResult.hashtags)
newTags.append(contentsOf: result.value.hashtags)
- viewModel.searchResult.value = Mastodon.Entity.SearchResult(accounts: oldSearchResult.accounts, statuses: oldSearchResult.statuses, hashtags: newTags.removeDuplicate())
+ newTags.removeDuplicates()
+ viewModel.searchResult.value = Mastodon.Entity.SearchResult(accounts: oldSearchResult.accounts, statuses: oldSearchResult.statuses, hashtags: newTags)
stateMachine.enter(Idle.self)
}
default:
diff --git a/Mastodon/Scene/Search/SearchViewModel.swift b/Mastodon/Scene/Search/SearchViewModel.swift
index 06b654b3d..18954665c 100644
--- a/Mastodon/Scene/Search/SearchViewModel.swift
+++ b/Mastodon/Scene/Search/SearchViewModel.swift
@@ -234,6 +234,7 @@ final class SearchViewModel: NSObject {
}
func saveItemToCoreData(item: SearchResultItem) {
+ let searchHistories = self.fetchSearchHistory()
_ = context.managedObjectContext.performChanges { [weak self] in
guard let self = self else { return }
switch item {
@@ -255,15 +256,55 @@ final class SearchViewModel: NSObject {
}
}()
let (mastodonUser, _) = APIService.CoreData.createOrMergeMastodonUser(into: self.context.managedObjectContext, for: requestMastodonUser, in: activeMastodonAuthenticationBox.domain, entity: account, userCache: nil, networkDate: Date(), log: OSLog.api)
- SearchHistory.insert(into: self.context.managedObjectContext, account: mastodonUser)
+ if let searchHistories = searchHistories {
+ let history = searchHistories.first { history -> Bool in
+ guard let account = history.account else { return false }
+ return account.objectID == mastodonUser.objectID
+ }
+ if let history = history {
+ history.update(updatedAt: Date())
+ } else {
+ SearchHistory.insert(into: self.context.managedObjectContext, account: mastodonUser)
+ }
+ } else {
+ SearchHistory.insert(into: self.context.managedObjectContext, account: mastodonUser)
+ }
case .hashtag(let tag):
- let histories = tag.history?[0 ... 2].compactMap { history -> History in
- History.insert(into: self.context.managedObjectContext, property: History.Property(day: history.day, uses: history.uses, accounts: history.accounts))
+ let (tagInCoreData,_) = APIService.CoreData.createOrMergeTag(into: self.context.managedObjectContext, entity: tag)
+ if let searchHistories = searchHistories {
+ let history = searchHistories.first { history -> Bool in
+ guard let hashtag = history.hashtag else { return false }
+ return hashtag.objectID == tagInCoreData.objectID
+ }
+ if let history = history {
+ history.update(updatedAt: Date())
+ } else {
+ SearchHistory.insert(into: self.context.managedObjectContext, hashtag: tagInCoreData)
+ }
+ } else {
+ SearchHistory.insert(into: self.context.managedObjectContext, hashtag: tagInCoreData)
+ }
+ case .accountObjectID(let accountObjectID):
+ if let searchHistories = searchHistories {
+ let history = searchHistories.first { history -> Bool in
+ guard let account = history.account else { return false }
+ return account.objectID == accountObjectID
+ }
+ if let history = history {
+ history.update(updatedAt: Date())
+ }
+ }
+ case .hashtagObjectID(let hashtagObjectID):
+ if let searchHistories = searchHistories {
+ let history = searchHistories.first { history -> Bool in
+ guard let hashtag = history.hashtag else { return false }
+ return hashtag.objectID == hashtagObjectID
+ }
+ if let history = history {
+ history.update(updatedAt: Date())
+ }
}
- let tagInCoreData = Tag.insert(into: self.context.managedObjectContext, property: Tag.Property(name: tag.name, url: tag.url, histories: histories))
- SearchHistory.insert(into: self.context.managedObjectContext, hashtag: tagInCoreData)
-
default:
break
}
diff --git a/Mastodon/Scene/Search/TableViewCell/SearchingTableViewCell.swift b/Mastodon/Scene/Search/TableViewCell/SearchingTableViewCell.swift
index aab8a5706..9fe0f1336 100644
--- a/Mastodon/Scene/Search/TableViewCell/SearchingTableViewCell.swift
+++ b/Mastodon/Scene/Search/TableViewCell/SearchingTableViewCell.swift
@@ -96,7 +96,7 @@ extension SearchingTableViewCell {
_subTitleLabel.text = ""
return
}
- let recentHistory = historys[0 ... 2]
+ let recentHistory = historys.prefix(2)
let peopleAreTalking = recentHistory.compactMap { Int($0.accounts) }.reduce(0, +)
let string = L10n.Scene.Search.Recommend.HashTag.peopleTalking(String(peopleAreTalking))
_subTitleLabel.text = string
@@ -112,7 +112,7 @@ extension SearchingTableViewCell {
_subTitleLabel.text = ""
return
}
- let recentHistory = historys[0 ... 2]
+ let recentHistory = historys.prefix(2)
let peopleAreTalking = recentHistory.compactMap { Int($0.accounts) }.reduce(0, +)
let string = L10n.Scene.Search.Recommend.HashTag.peopleTalking(String(peopleAreTalking))
_subTitleLabel.text = string
diff --git a/Mastodon/Service/APIService/CoreData/APIService+CoreData+Tag.swift b/Mastodon/Service/APIService/CoreData/APIService+CoreData+Tag.swift
new file mode 100644
index 000000000..3f931ddea
--- /dev/null
+++ b/Mastodon/Service/APIService/CoreData/APIService+CoreData+Tag.swift
@@ -0,0 +1,66 @@
+//
+// APIService+CoreData+Tag.swift
+// Mastodon
+//
+// Created by sxiaojian on 2021/4/8.
+//
+
+import CoreData
+import CoreDataStack
+import Foundation
+import MastodonSDK
+
+extension APIService.CoreData {
+ static func createOrMergeTag(
+ into managedObjectContext: NSManagedObjectContext,
+ entity: Mastodon.Entity.Tag
+ ) -> (Tag: Tag, isCreated: Bool) {
+ // fetch old mastodon user
+ let oldTag: Tag? = {
+ let request = Tag.sortedFetchRequest
+ request.predicate = Tag.predicate(name: entity.name)
+ request.fetchLimit = 1
+ request.returnsObjectsAsFaults = false
+ do {
+ return try managedObjectContext.fetch(request).first
+ } catch {
+ assertionFailure(error.localizedDescription)
+ return nil
+ }
+ }()
+
+ if let oldTag = oldTag {
+ APIService.CoreData.merge(tag: oldTag, entity: entity, into: managedObjectContext)
+ return (oldTag, false)
+ } else {
+ let histories = entity.history?.prefix(2).compactMap { history -> History in
+ History.insert(into: managedObjectContext, property: History.Property(day: history.day, uses: history.uses, accounts: history.accounts))
+ }
+ let tagInCoreData = Tag.insert(into: managedObjectContext, property: Tag.Property(name: entity.name, url: entity.url, histories: histories))
+ return (tagInCoreData, true)
+ }
+ }
+
+ static func merge(tag:Tag,entity:Mastodon.Entity.Tag,into managedObjectContext: NSManagedObjectContext) {
+ tag.update(url: tag.url)
+ guard let tagHistories = tag.histories else { return }
+ guard let entityHistories = entity.history?.prefix(2) else { return }
+ let entityHistoriesCount = entityHistories.count
+ if entityHistoriesCount == 0 {
+ return
+ }
+ for n in 0..