From 71788d8f69c763de6c25399621dccd4c9f1c39cf Mon Sep 17 00:00:00 2001 From: Maurice Parker Date: Sun, 26 Apr 2020 04:28:42 -0500 Subject: [PATCH] Collapse the articles and statuses into a single CKRecord --- .../CloudKit/CloudKitAccountDelegate.swift | 16 +- .../CloudKitAccountZoneDelegate.swift | 8 +- .../CloudKit/CloudKitArticlesZone.swift | 138 +++++++----------- .../CloudKitArticlesZoneDelegate.swift | 16 +- 4 files changed, 72 insertions(+), 106 deletions(-) diff --git a/Frameworks/Account/CloudKit/CloudKitAccountDelegate.swift b/Frameworks/Account/CloudKit/CloudKitAccountDelegate.swift index bf9850a91..d654b3829 100644 --- a/Frameworks/Account/CloudKit/CloudKitAccountDelegate.swift +++ b/Frameworks/Account/CloudKit/CloudKitAccountDelegate.swift @@ -112,7 +112,7 @@ final class CloudKitAccountDelegate: AccountDelegate { func processWithArticles(_ articles: Set
) { - self.articlesZone.modifyArticlesAndStatuses(syncStatuses, articles: articles) { result in + self.articlesZone.modifyArticles(syncStatuses, articles: articles) { result in switch result { case .success: self.database.deleteSelectedForProcessing(syncStatuses.map({ $0.articleID }) ) @@ -151,7 +151,7 @@ final class CloudKitAccountDelegate: AccountDelegate { func refreshArticleStatus(for account: Account, completion: @escaping ((Result) -> Void)) { os_log(.debug, log: log, "Refreshing article statuses...") - articlesZone.refreshArticlesAndStatuses() { result in + articlesZone.refreshArticles() { result in os_log(.debug, log: self.log, "Done refreshing article statuses.") switch result { case .success: @@ -616,9 +616,9 @@ private extension CloudKitAccountDelegate { group.notify(queue: DispatchQueue.main) { - self.articlesZone.deleteArticlesAndStatuses(deletedArticles) { _ in + self.articlesZone.deleteArticles(deletedArticles) { _ in self.refreshProgress.completeTask() - self.articlesZone.saveNewArticlesAndStatuses(newArticles) { _ in + self.articlesZone.saveNewArticles(newArticles) { _ in self.refreshProgress.completeTask() completion() } @@ -665,9 +665,9 @@ private extension CloudKitAccountDelegate { let newArticles = articleChanges.newArticles ?? Set
() let deletedArticles = articleChanges.deletedArticles ?? Set
() - self.articlesZone.deleteArticlesAndStatuses(deletedArticles) { _ in + self.articlesZone.deleteArticles(deletedArticles) { _ in self.refreshProgress.completeTask() - self.articlesZone.saveNewArticlesAndStatuses(newArticles) { _ in + self.articlesZone.saveNewArticles(newArticles) { _ in self.refreshProgress.clear() completion(.success(feed)) } @@ -744,9 +744,9 @@ private extension CloudKitAccountDelegate { let newArticles = articleChanges.newArticles ?? Set
() let deletedArticles = articleChanges.deletedArticles ?? Set
() - self.articlesZone.deleteArticlesAndStatuses(deletedArticles) { _ in + self.articlesZone.deleteArticles(deletedArticles) { _ in self.refreshProgress.completeTask() - self.articlesZone.saveNewArticlesAndStatuses(newArticles) { _ in + self.articlesZone.saveNewArticles(newArticles) { _ in self.refreshProgress.clear() completion(.success(feed)) } diff --git a/Frameworks/Account/CloudKit/CloudKitAccountZoneDelegate.swift b/Frameworks/Account/CloudKit/CloudKitAccountZoneDelegate.swift index 606df77eb..50b383489 100644 --- a/Frameworks/Account/CloudKit/CloudKitAccountZoneDelegate.swift +++ b/Frameworks/Account/CloudKit/CloudKitAccountZoneDelegate.swift @@ -214,9 +214,9 @@ private extension CloudKitAcountZoneDelegate { switch result { case .success(let articleChanges): - self.articlesZone?.deleteArticlesAndStatuses(articleChanges.deletedArticles ?? Set
()) { _ in + self.articlesZone?.deleteArticles(articleChanges.deletedArticles ?? Set
()) { _ in self.refreshProgress?.completeTask() - self.articlesZone?.saveNewArticlesAndStatuses(articleChanges.newArticles ?? Set
()) { _ in + self.articlesZone?.saveNewArticles(articleChanges.newArticles ?? Set
()) { _ in self.refreshProgress?.completeTask() completion(webFeed) } @@ -251,9 +251,9 @@ private extension CloudKitAcountZoneDelegate { BatchUpdate.shared.end() switch result { case .success(let articleChanges): - self.articlesZone?.deleteArticlesAndStatuses(articleChanges.deletedArticles ?? Set
()) { _ in + self.articlesZone?.deleteArticles(articleChanges.deletedArticles ?? Set
()) { _ in self.refreshProgress?.completeTask() - self.articlesZone?.saveNewArticlesAndStatuses(articleChanges.newArticles ?? Set
()) { _ in + self.articlesZone?.saveNewArticles(articleChanges.newArticles ?? Set
()) { _ in self.refreshProgress?.completeTask() completion(webFeed) } diff --git a/Frameworks/Account/CloudKit/CloudKitArticlesZone.swift b/Frameworks/Account/CloudKit/CloudKitArticlesZone.swift index 89bf64d96..37e7f14a7 100644 --- a/Frameworks/Account/CloudKit/CloudKitArticlesZone.swift +++ b/Frameworks/Account/CloudKit/CloudKitArticlesZone.swift @@ -29,7 +29,7 @@ final class CloudKitArticlesZone: CloudKitZone { struct CloudKitArticle { static let recordType = "Article" struct Fields { - static let articleStatus = "articleStatus" + static let hollow = "hollow" static let webFeedURL = "webFeedURL" static let uniqueID = "uniqueID" static let title = "title" @@ -42,24 +42,17 @@ final class CloudKitArticlesZone: CloudKitZone { static let datePublished = "datePublished" static let dateModified = "dateModified" static let parsedAuthors = "parsedAuthors" - } - } - - struct CloudKitArticleStatus { - static let recordType = "ArticleStatus" - struct Fields { - static let webFeedExternalID = "webFeedExternalID" static let read = "read" static let starred = "starred" } } - + init(container: CKContainer) { self.container = container self.database = container.privateCloudDatabase } - func refreshArticlesAndStatuses(completion: @escaping ((Result) -> Void)) { + func refreshArticles(completion: @escaping ((Result) -> Void)) { fetchChangesInZone() { result in switch result { case .success: @@ -69,7 +62,7 @@ final class CloudKitArticlesZone: CloudKitZone { self.createZoneRecord() { result in switch result { case .success: - self.refreshArticlesAndStatuses(completion: completion) + self.refreshArticles(completion: completion) case .failure(let error): completion(.failure(error)) } @@ -81,13 +74,13 @@ final class CloudKitArticlesZone: CloudKitZone { } } - func saveNewArticlesAndStatuses(_ articles: Set
, completion: @escaping ((Result) -> Void)) { + func saveNewArticles(_ articles: Set
, completion: @escaping ((Result) -> Void)) { guard !articles.isEmpty else { completion(.success(())) return } - var records = makeNewStatusRecords(articles) + var records = [CKRecord]() for article in articles { records.append(contentsOf: makeArticleRecords(article)) } @@ -95,30 +88,30 @@ final class CloudKitArticlesZone: CloudKitZone { saveIfNew(records, completion: completion) } - func deleteArticlesAndStatuses(_ articles: Set
, completion: @escaping ((Result) -> Void)) { + func deleteArticles(_ articles: Set
, completion: @escaping ((Result) -> Void)) { guard !articles.isEmpty else { completion(.success(())) return } - let recordIDs = articles.map { CKRecord.ID(recordName: statusID($0.articleID), zoneID: Self.zoneID) } + let recordIDs = articles.map { CKRecord.ID(recordName: $0.articleID, zoneID: Self.zoneID) } delete(recordIDs: recordIDs, completion: completion) } - func modifyArticlesAndStatuses(_ syncStatuses: [SyncStatus], articles: Set
, completion: @escaping ((Result) -> Void)) { - - var records = makeStatusRecords(syncStatuses, articles) - + func modifyArticles(_ syncStatuses: [SyncStatus], articles: Set
, completion: @escaping ((Result) -> Void)) { + var records = [CKRecord]() + let saveArticles = articles.filter { $0.status.read == false || $0.status.starred == true } for saveArticle in saveArticles { records.append(contentsOf: makeArticleRecords(saveArticle)) } - let deleteArticleIDs = articles.subtracting(saveArticles).map { - return CKRecord.ID(recordName: articleID($0.articleID), zoneID: Self.zoneID) + let hollowArticles = articles.subtracting(saveArticles) + for hollowArticle in hollowArticles { + records.append(contentsOf: makeHollowArticleRecords(hollowArticle)) } - self.modify(recordsToSave: records, recordIDsToDelete: deleteArticleIDs) { result in + self.modify(recordsToSave: records, recordIDsToDelete: []) { result in switch result { case .success: completion(.success(())) @@ -126,15 +119,18 @@ final class CloudKitArticlesZone: CloudKitZone { self.handleSendArticleStatusError(error, syncStatuses: syncStatuses, starredArticles: articles, completion: completion) } } - } +} + +private extension CloudKitArticlesZone { + func handleSendArticleStatusError(_ error: Error, syncStatuses: [SyncStatus], starredArticles: Set
, completion: @escaping ((Result) -> Void)) { if case CloudKitZoneError.userDeletedZone = error { self.createZoneRecord() { result in switch result { case .success: - self.modifyArticlesAndStatuses(syncStatuses, articles: starredArticles, completion: completion) + self.modifyArticles(syncStatuses, articles: starredArticles, completion: completion) case .failure(let error): completion(.failure(error)) } @@ -144,74 +140,13 @@ final class CloudKitArticlesZone: CloudKitZone { } } -} - -private extension CloudKitArticlesZone { - - func statusID(_ id: String) -> String { - return "s|\(id)" - } - - func articleID(_ id: String) -> String { - return "a|\(id)" - } - - func makeNewStatusRecords(_ articles: Set
) -> [CKRecord] { - var records = [CKRecord]() - - for article in articles { - let recordID = CKRecord.ID(recordName: statusID(article.articleID), zoneID: Self.zoneID) - let record = CKRecord(recordType: CloudKitArticleStatus.recordType, recordID: recordID) - if let webFeedExternalID = article.webFeed?.externalID { - record[CloudKitArticleStatus.Fields.webFeedExternalID] = webFeedExternalID - } - record[CloudKitArticleStatus.Fields.read] = "0" - records.append(record) - } - - return records - } - - func makeStatusRecords(_ syncStatuses: [SyncStatus], _ articles: Set
) -> [CKRecord] { - var articleDict = [String: Article]() - for article in articles { - articleDict[article.articleID] = article - } - - var records = [String: CKRecord]() - - for status in syncStatuses { - - var record = records[status.articleID] - if record == nil { - let recordID = CKRecord.ID(recordName: statusID(status.articleID), zoneID: Self.zoneID) - record = CKRecord(recordType: CloudKitArticleStatus.recordType, recordID: recordID) - records[status.articleID] = record - } - - if let webFeedExternalID = articleDict[status.articleID]?.webFeed?.externalID { - record![CloudKitArticleStatus.Fields.webFeedExternalID] = webFeedExternalID - } - - switch status.key { - case .read: - record![CloudKitArticleStatus.Fields.read] = status.flag ? "1" : "0" - case .starred: - record![CloudKitArticleStatus.Fields.starred] = status.flag ? "1" : "0" - } - } - - return Array(records.values) - } - func makeArticleRecords(_ article: Article) -> [CKRecord] { var records = [CKRecord]() - let recordID = CKRecord.ID(recordName: articleID(article.articleID), zoneID: Self.zoneID) + let recordID = CKRecord.ID(recordName: article.articleID, zoneID: Self.zoneID) let articleRecord = CKRecord(recordType: CloudKitArticle.recordType, recordID: recordID) - let articleStatusRecordID = CKRecord.ID(recordName: article.articleID, zoneID: Self.zoneID) - articleRecord[CloudKitArticle.Fields.articleStatus] = CKRecord.Reference(recordID: articleStatusRecordID, action: .deleteSelf) + articleRecord[CloudKitArticle.Fields.hollow] = "0" articleRecord[CloudKitArticle.Fields.webFeedURL] = article.webFeed?.url articleRecord[CloudKitArticle.Fields.uniqueID] = article.uniqueID articleRecord[CloudKitArticle.Fields.title] = article.title @@ -240,6 +175,35 @@ private extension CloudKitArticlesZone { articleRecord[CloudKitArticle.Fields.parsedAuthors] = parsedAuthors } + articleRecord[CloudKitArticle.Fields.read] = article.status.read ? "1" : "0" + articleRecord[CloudKitArticle.Fields.starred] = article.status.starred ? "1" : "0" + + records.append(articleRecord) + return records + } + + func makeHollowArticleRecords(_ article: Article) -> [CKRecord] { + var records = [CKRecord]() + + let recordID = CKRecord.ID(recordName: article.articleID, zoneID: Self.zoneID) + let articleRecord = CKRecord(recordType: CloudKitArticle.recordType, recordID: recordID) + + articleRecord[CloudKitArticle.Fields.hollow] = "1" + articleRecord[CloudKitArticle.Fields.webFeedURL] = article.webFeed?.url + articleRecord[CloudKitArticle.Fields.uniqueID] = nil + articleRecord[CloudKitArticle.Fields.title] = nil + articleRecord[CloudKitArticle.Fields.contentHTML] = nil + articleRecord[CloudKitArticle.Fields.contentText] = nil + articleRecord[CloudKitArticle.Fields.url] = nil + articleRecord[CloudKitArticle.Fields.externalURL] = nil + articleRecord[CloudKitArticle.Fields.summary] = nil + articleRecord[CloudKitArticle.Fields.imageURL] = nil + articleRecord[CloudKitArticle.Fields.datePublished] = nil + articleRecord[CloudKitArticle.Fields.dateModified] = nil + articleRecord[CloudKitArticle.Fields.parsedAuthors] = nil + articleRecord[CloudKitArticle.Fields.read] = article.status.read ? "1" : "0" + articleRecord[CloudKitArticle.Fields.starred] = article.status.starred ? "1" : "0" + records.append(articleRecord) return records } diff --git a/Frameworks/Account/CloudKit/CloudKitArticlesZoneDelegate.swift b/Frameworks/Account/CloudKit/CloudKitArticlesZoneDelegate.swift index d4d07ac63..ca52ec2f1 100644 --- a/Frameworks/Account/CloudKit/CloudKitArticlesZoneDelegate.swift +++ b/Frameworks/Account/CloudKit/CloudKitArticlesZoneDelegate.swift @@ -62,13 +62,11 @@ private extension CloudKitArticlesZoneDelegate { func process(records: [CKRecord], pendingReadStatusArticleIDs: Set, pendingStarredStatusArticleIDs: Set, completion: @escaping (Result) -> Void) { - let receivedUnreadArticleIDs = Set(records.filter({ $0[CloudKitArticlesZone.CloudKitArticleStatus.Fields.read] == "0" }).map({ $0.externalID })) - let receivedReadArticleIDs = Set(records.filter({ $0[CloudKitArticlesZone.CloudKitArticleStatus.Fields.read] == "1" }).map({ $0.externalID })) - let receivedUnstarredArticleIDs = Set(records.filter({ $0[CloudKitArticlesZone.CloudKitArticleStatus.Fields.starred] == "0" }).map({ $0.externalID })) - let receivedStarredArticleIDs = Set(records.filter({ $0[CloudKitArticlesZone.CloudKitArticleStatus.Fields.starred] == "1" }).map({ $0.externalID })) + let receivedUnreadArticleIDs = Set(records.filter({ $0[CloudKitArticlesZone.CloudKitArticle.Fields.read] == "0" }).map({ $0.externalID })) + let receivedReadArticleIDs = Set(records.filter({ $0[CloudKitArticlesZone.CloudKitArticle.Fields.read] == "1" }).map({ $0.externalID })) + let receivedUnstarredArticleIDs = Set(records.filter({ $0[CloudKitArticlesZone.CloudKitArticle.Fields.starred] == "0" }).map({ $0.externalID })) + let receivedStarredArticleIDs = Set(records.filter({ $0[CloudKitArticlesZone.CloudKitArticle.Fields.starred] == "1" }).map({ $0.externalID })) - let receivedArticles = records.filter({ $0.recordType == CloudKitArticlesZone.CloudKitArticle.recordType }) - let updateableUnreadArticleIDs = receivedUnreadArticleIDs.subtracting(pendingReadStatusArticleIDs) let updateableReadArticleIDs = receivedReadArticleIDs.subtracting(pendingReadStatusArticleIDs) let updateableUnstarredArticleIDs = receivedUnstarredArticleIDs.subtracting(pendingStarredStatusArticleIDs) @@ -96,7 +94,7 @@ private extension CloudKitArticlesZoneDelegate { group.leave() } - let parsedItems = receivedArticles.compactMap { makeParsedItem($0) } + let parsedItems = records.compactMap { makeParsedItem($0) } let webFeedIDsAndItems = Dictionary(grouping: parsedItems, by: { item in item.feedURL } ).mapValues { Set($0) } for (webFeedID, parsedItems) in webFeedIDsAndItems { group.enter() @@ -114,6 +112,10 @@ private extension CloudKitArticlesZoneDelegate { } func makeParsedItem(_ articleRecord: CKRecord) -> ParsedItem? { + guard articleRecord[CloudKitArticlesZone.CloudKitArticle.Fields.hollow] as? String ?? "0" == "0" else { + return nil + } + var parsedAuthors = Set() let decoder = JSONDecoder()