From 6c6f401e3e5ba30de54be4a03c22732c85f47df8 Mon Sep 17 00:00:00 2001 From: Maurice Parker Date: Sat, 9 May 2020 19:36:03 -0500 Subject: [PATCH] Change to save new records only if there isn't already a server side record --- .../CloudKitArticleStatusUpdate.swift | 5 ++ .../CloudKit/CloudKitArticlesZone.swift | 15 +++- .../Account/CloudKit/CloudKitZone.swift | 74 +++++++++++++++++++ 3 files changed, 92 insertions(+), 2 deletions(-) diff --git a/Frameworks/Account/CloudKit/CloudKitArticleStatusUpdate.swift b/Frameworks/Account/CloudKit/CloudKitArticleStatusUpdate.swift index 851fb3c54..65048cb9d 100644 --- a/Frameworks/Account/CloudKit/CloudKitArticleStatusUpdate.swift +++ b/Frameworks/Account/CloudKit/CloudKitArticleStatusUpdate.swift @@ -14,6 +14,7 @@ struct CloudKitArticleStatusUpdate { enum Record { case all + case new case statusOnly case delete } @@ -27,6 +28,10 @@ struct CloudKitArticleStatusUpdate { return .delete } + if statuses.count == 1, statuses.first!.key == .new { + return .new + } + if let article = article { if article.status.read == false || article.status.starred == true { return .all diff --git a/Frameworks/Account/CloudKit/CloudKitArticlesZone.swift b/Frameworks/Account/CloudKit/CloudKitArticlesZone.swift index 8c84e556d..6db210674 100644 --- a/Frameworks/Account/CloudKit/CloudKitArticlesZone.swift +++ b/Frameworks/Account/CloudKit/CloudKitArticlesZone.swift @@ -111,6 +111,7 @@ final class CloudKitArticlesZone: CloudKitZone { } var modifyRecords = [CKRecord]() + var newRecords = [CKRecord]() var deleteRecordIDs = [CKRecord.ID]() for statusUpdate in statusUpdates { @@ -118,6 +119,9 @@ final class CloudKitArticlesZone: CloudKitZone { case .all: modifyRecords.append(makeStatusRecord(statusUpdate)) modifyRecords.append(makeArticleRecord(statusUpdate.article!)) + case .new: + newRecords.append(makeStatusRecord(statusUpdate)) + newRecords.append(makeArticleRecord(statusUpdate.article!)) case .delete: deleteRecordIDs.append(CKRecord.ID(recordName: statusID(statusUpdate.articleID), zoneID: Self.zoneID)) case .statusOnly: @@ -126,10 +130,17 @@ final class CloudKitArticlesZone: CloudKitZone { } } - self.modify(recordsToSave: modifyRecords, recordIDsToDelete: deleteRecordIDs) { result in + modify(recordsToSave: modifyRecords, recordIDsToDelete: deleteRecordIDs) { result in switch result { case .success: - completion(.success(())) + self.saveIfNew(newRecords) { result in + switch result { + case .success: + completion(.success(())) + case .failure(let error): + completion(.failure(error)) + } + } case .failure(let error): self.handleModifyArticlesError(error, statusUpdates: statusUpdates, completion: completion) } diff --git a/Frameworks/Account/CloudKit/CloudKitZone.swift b/Frameworks/Account/CloudKit/CloudKitZone.swift index af8f91e1f..2fda15212 100644 --- a/Frameworks/Account/CloudKit/CloudKitZone.swift +++ b/Frameworks/Account/CloudKit/CloudKitZone.swift @@ -234,6 +234,80 @@ extension CloudKitZone { modify(recordsToSave: records, recordIDsToDelete: [], completion: completion) } + /// Saves or modifies the records as long as they are unchanged relative to the local version + func saveIfNew(_ records: [CKRecord], completion: @escaping (Result) -> Void) { + let op = CKModifyRecordsOperation(recordsToSave: records, recordIDsToDelete: [CKRecord.ID]()) + op.savePolicy = .ifServerRecordUnchanged + op.isAtomic = false + + op.modifyRecordsCompletionBlock = { [weak self] (_, _, error) in + + guard let self = self else { return } + + switch CloudKitZoneResult.resolve(error) { + case .success, .partialFailure: + DispatchQueue.main.async { + completion(.success(())) + } + + case .zoneNotFound: + self.createZoneRecord() { result in + switch result { + case .success: + self.saveIfNew(records, completion: completion) + case .failure(let error): + DispatchQueue.main.async { + completion(.failure(error)) + } + } + } + + case .userDeletedZone: + DispatchQueue.main.async { + completion(.failure(CloudKitZoneError.userDeletedZone)) + } + + case .retry(let timeToWait): + self.retryIfPossible(after: timeToWait) { + self.saveIfNew(records, completion: completion) + } + + case .limitExceeded: + + let chunkedRecords = records.chunked(into: 300) + + let group = DispatchGroup() + var errorOccurred = false + + for chunk in chunkedRecords { + group.enter() + self.saveIfNew(chunk) { result in + if case .failure(let error) = result { + os_log(.error, log: self.log, "%@ zone modify records error: %@", Self.zoneID.zoneName, error.localizedDescription) + errorOccurred = true + } + group.leave() + } + } + + group.notify(queue: DispatchQueue.main) { + if errorOccurred { + completion(.failure(CloudKitZoneError.unknown)) + } else { + completion(.success(())) + } + } + + default: + DispatchQueue.main.async { + completion(.failure(CloudKitError(error!))) + } + } + } + + database?.add(op) + } + /// Save the CKSubscription func save(_ subscription: CKSubscription, completion: @escaping (Result) -> Void) { database?.save(subscription) { [weak self] savedSubscription, error in