Allow for multiple sync status records in sync database.

This commit is contained in:
Maurice Parker 2020-04-29 05:24:35 -05:00
parent a48cbfe1ca
commit bedce4946f
4 changed files with 95 additions and 43 deletions

View File

@ -44,6 +44,7 @@
513323082281070D00C30F19 /* AccountFeedbinSyncTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 513323072281070C00C30F19 /* AccountFeedbinSyncTest.swift */; };
5133230A2281082F00C30F19 /* subscriptions_initial.json in Resources */ = {isa = PBXBuildFile; fileRef = 513323092281082F00C30F19 /* subscriptions_initial.json */; };
5133230C2281088A00C30F19 /* subscriptions_add.json in Resources */ = {isa = PBXBuildFile; fileRef = 5133230B2281088A00C30F19 /* subscriptions_add.json */; };
5139A6382459822D004D960C /* CloudKitArticleStatusUpdate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5139A6372459822D004D960C /* CloudKitArticleStatusUpdate.swift */; };
5144EA49227B497600D19003 /* FeedbinAPICaller.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5144EA48227B497600D19003 /* FeedbinAPICaller.swift */; };
5144EA4E227B829A00D19003 /* FeedbinAccountDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5144EA4D227B829A00D19003 /* FeedbinAccountDelegate.swift */; };
514BF5202391B0DB00902FE8 /* SingleArticleFetcher.swift in Sources */ = {isa = PBXBuildFile; fileRef = 514BF51F2391B0DB00902FE8 /* SingleArticleFetcher.swift */; };
@ -294,6 +295,7 @@
513323072281070C00C30F19 /* AccountFeedbinSyncTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AccountFeedbinSyncTest.swift; sourceTree = "<group>"; };
513323092281082F00C30F19 /* subscriptions_initial.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = subscriptions_initial.json; sourceTree = "<group>"; };
5133230B2281088A00C30F19 /* subscriptions_add.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = subscriptions_add.json; sourceTree = "<group>"; };
5139A6372459822D004D960C /* CloudKitArticleStatusUpdate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CloudKitArticleStatusUpdate.swift; sourceTree = "<group>"; };
5144EA48227B497600D19003 /* FeedbinAPICaller.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FeedbinAPICaller.swift; sourceTree = "<group>"; };
5144EA4D227B829A00D19003 /* FeedbinAccountDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FeedbinAccountDelegate.swift; sourceTree = "<group>"; };
514BF51F2391B0DB00902FE8 /* SingleArticleFetcher.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SingleArticleFetcher.swift; sourceTree = "<group>"; };
@ -556,6 +558,7 @@
519E84A72434C5EF00D238B0 /* CloudKitArticlesZone.swift */,
519E84AB2435019100D238B0 /* CloudKitArticlesZoneDelegate.swift */,
5150FFFD243823B800C1A442 /* CloudKitError.swift */,
5139A6372459822D004D960C /* CloudKitArticleStatusUpdate.swift */,
51E4DB2D242633ED0091EB5B /* CloudKitZone.swift */,
51C034DE242D65D20014DC71 /* CloudKitZoneResult.swift */,
);
@ -1158,6 +1161,7 @@
9E12B0202334696A00ADE5A0 /* FeedlyCreateFeedsForCollectionFoldersOperation.swift in Sources */,
552032FD229D5D5A009559E0 /* ReaderAPITagging.swift in Sources */,
9EAEC62A23331EE70085D7C9 /* FeedlyOrigin.swift in Sources */,
5139A6382459822D004D960C /* CloudKitArticleStatusUpdate.swift in Sources */,
9E5EC15B23E01DEF00A4E503 /* FeedlyRTLTextSanitizer.swift in Sources */,
51B36305244B6135000DEF2A /* TwitterEntities.swift in Sources */,
5132DE832449306F00806ADE /* TwitterStatus.swift in Sources */,

View File

@ -120,12 +120,15 @@ final class CloudKitAccountDelegate: AccountDelegate {
func processWithArticles(_ articles: Set<Article>) {
let syncStatusesDict = Dictionary(grouping: syncStatuses, by: { $0.articleID })
let articlesDict = articles.reduce(into: [String: Article]()) { result, article in
result[article.articleID] = article
}
let statusedArticles = syncStatuses.map { ($0, articlesDict[$0.articleID]) }
let statusUpdates = syncStatusesDict.map { (key, value) in
return CloudKitArticleStatusUpdate(articleID: key, statuses: value, article: articlesDict[key])
}
self.articlesZone.modifyArticles(statusedArticles) { result in
self.articlesZone.modifyArticles(statusUpdates) { result in
switch result {
case .success:
self.database.deleteSelectedForProcessing(syncStatuses.map({ $0.articleID })) { _ in

View File

@ -0,0 +1,66 @@
//
// CloudKitArticleStatusUpdate.swift
// Account
//
// Created by Maurice Parker on 4/29/20.
// Copyright © 2020 Ranchero Software, LLC. All rights reserved.
//
import Foundation
import SyncDatabase
import Articles
struct CloudKitArticleStatusUpdate {
enum Record {
case all
case statusOnly
case delete
}
var articleID: String
var statuses: [SyncStatus]
var article: Article?
var record: Record {
if statuses.contains(where: { $0.key == .deleted }) {
return .delete
}
if let article = article {
if statuses.contains(where: { $0.key == .new }) {
return .all
}
if article.status.read == false || article.status.starred == true {
return .all
}
}
return .statusOnly
}
var isRead: Bool {
if let article = article {
return article.status.read
}
if let status = statuses.first(where: { $0.key == .read }) {
return status.flag
}
return true
}
var isStarred: Bool {
if let article = article {
return article.status.starred
}
if let status = statuses.first(where: { $0.key == .starred }) {
return status.flag
}
return false
}
}

View File

@ -104,8 +104,8 @@ final class CloudKitArticlesZone: CloudKitZone {
delete(ckQuery: ckQuery, completion: completion)
}
func modifyArticles(_ statusArticles: [(status: SyncStatus, article: Article?)], completion: @escaping ((Result<Void, Error>) -> Void)) {
guard !statusArticles.isEmpty else {
func modifyArticles(_ statusUpdates: [CloudKitArticleStatusUpdate], completion: @escaping ((Result<Void, Error>) -> Void)) {
guard !statusUpdates.isEmpty else {
completion(.success(()))
return
}
@ -113,25 +113,16 @@ final class CloudKitArticlesZone: CloudKitZone {
var modifyRecords = [CKRecord]()
var deleteRecordIDs = [CKRecord.ID]()
for statusArticle in statusArticles {
switch (statusArticle.status.key, statusArticle.status.flag) {
case (.new, _):
modifyRecords.append(makeStatusRecord(statusArticle))
if let article = statusArticle.article {
if article.status.read == false || article.status.starred == true {
modifyRecords.append(makeArticleRecord(article))
}
}
case (.starred, true), (.read, false):
modifyRecords.append(makeStatusRecord(statusArticle))
if let article = statusArticle.article {
modifyRecords.append(makeArticleRecord(article))
}
case (.deleted, true):
deleteRecordIDs.append(CKRecord.ID(recordName: statusID(statusArticle.status.articleID), zoneID: Self.zoneID))
default:
modifyRecords.append(makeStatusRecord(statusArticle))
deleteRecordIDs.append(CKRecord.ID(recordName: articleID(statusArticle.status.articleID), zoneID: Self.zoneID))
for statusUpdate in statusUpdates {
switch statusUpdate.record {
case .all:
modifyRecords.append(makeStatusRecord(statusUpdate))
modifyRecords.append(makeArticleRecord(statusUpdate.article!))
case .delete:
deleteRecordIDs.append(CKRecord.ID(recordName: statusID(statusUpdate.articleID), zoneID: Self.zoneID))
case .statusOnly:
modifyRecords.append(makeStatusRecord(statusUpdate))
deleteRecordIDs.append(CKRecord.ID(recordName: articleID(statusUpdate.articleID), zoneID: Self.zoneID))
}
}
@ -140,7 +131,7 @@ final class CloudKitArticlesZone: CloudKitZone {
case .success:
completion(.success(()))
case .failure(let error):
self.handleModifyArticlesError(error, statusArticles: statusArticles, completion: completion)
self.handleModifyArticlesError(error, statusUpdates: statusUpdates, completion: completion)
}
}
}
@ -149,12 +140,12 @@ final class CloudKitArticlesZone: CloudKitZone {
private extension CloudKitArticlesZone {
func handleModifyArticlesError(_ error: Error, statusArticles: [(status: SyncStatus, article: Article?)], completion: @escaping ((Result<Void, Error>) -> Void)) {
func handleModifyArticlesError(_ error: Error, statusUpdates: [CloudKitArticleStatusUpdate], completion: @escaping ((Result<Void, Error>) -> Void)) {
if case CloudKitZoneError.userDeletedZone = error {
self.createZoneRecord() { result in
switch result {
case .success:
self.modifyArticles(statusArticles, completion: completion)
self.modifyArticles(statusUpdates, completion: completion)
case .failure(let error):
completion(.failure(error))
}
@ -183,28 +174,16 @@ private extension CloudKitArticlesZone {
return record
}
func makeStatusRecord(_ statusArticle: (status: SyncStatus, article: Article?)) -> CKRecord {
let status = statusArticle.status
let recordID = CKRecord.ID(recordName: statusID(status.articleID), zoneID: Self.zoneID)
func makeStatusRecord(_ statusUpdate: CloudKitArticleStatusUpdate) -> CKRecord {
let recordID = CKRecord.ID(recordName: statusID(statusUpdate.articleID), zoneID: Self.zoneID)
let record = CKRecord(recordType: CloudKitArticleStatus.recordType, recordID: recordID)
if let webFeedExternalID = statusArticle.article?.webFeed?.externalID {
if let webFeedExternalID = statusUpdate.article?.webFeed?.externalID {
record[CloudKitArticleStatus.Fields.webFeedExternalID] = webFeedExternalID
}
if let article = statusArticle.article {
record[CloudKitArticleStatus.Fields.read] = article.status.read ? "1" : "0"
record[CloudKitArticleStatus.Fields.starred] = article.status.starred ? "1" : "0"
} else {
switch status.key {
case .read:
record[CloudKitArticleStatus.Fields.read] = status.flag ? "1" : "0"
case .starred:
record[CloudKitArticleStatus.Fields.starred] = status.flag ? "1" : "0"
default:
break
}
}
record[CloudKitArticleStatus.Fields.read] = statusUpdate.isRead ? "1" : "0"
record[CloudKitArticleStatus.Fields.starred] = statusUpdate.isStarred ? "1" : "0"
return record
}