From 578d22f3c23786248ebedc58829ea67c2f972c7e Mon Sep 17 00:00:00 2001 From: Maurice Parker Date: Sat, 2 May 2020 22:52:13 -0500 Subject: [PATCH] Prevent newly added sync status records from being deleted and process them incrementally --- .../CloudKitSendStatusOperation.swift | 94 +++++++++++-------- Frameworks/SyncDatabase/SyncDatabase.swift | 4 +- Frameworks/SyncDatabase/SyncStatusTable.swift | 9 +- 3 files changed, 61 insertions(+), 46 deletions(-) diff --git a/Frameworks/Account/CloudKit/CloudKitSendStatusOperation.swift b/Frameworks/Account/CloudKit/CloudKitSendStatusOperation.swift index 519b3d365..d94dbaea7 100644 --- a/Frameworks/Account/CloudKit/CloudKitSendStatusOperation.swift +++ b/Frameworks/Account/CloudKit/CloudKitSendStatusOperation.swift @@ -16,7 +16,8 @@ import SyncDatabase class CloudKitSendStatusOperation: MainThreadOperation { private var log = OSLog(subsystem: Bundle.main.bundleIdentifier!, category: "CloudKit") - + private let blockSize = 300 + // MainThreadOperation public var isCanceled = false public var id: Int? @@ -40,51 +41,61 @@ class CloudKitSendStatusOperation: MainThreadOperation { func run() { os_log(.debug, log: log, "Sending article statuses...") - - database.selectForProcessing { result in - - func processStatuses(_ syncStatuses: [SyncStatus]) { - guard syncStatuses.count > 0 else { - os_log(.debug, log: self.log, "Done sending article statuses.") - self.operationDelegate?.operationDidComplete(self) - return + + if showProgress { + + database.selectPendingCount() { result in + switch result { + case .success(let count): + let ticks = count / self.blockSize + self.refreshProgress?.addToNumberOfTasksAndRemaining(ticks) + self.selectForProcessing() + case .failure(let databaseError): + os_log(.error, log: self.log, "Send status count pending error: %@.", databaseError.localizedDescription) + self.operationDelegate?.cancelOperation(self) } - - let group = DispatchGroup() - let syncStatusChunks = syncStatuses.chunked(into: 300) - - if self.showProgress { - self.refreshProgress?.addToNumberOfTasksAndRemaining(syncStatusChunks.count) - } - - for syncStatusChunk in syncStatusChunks { - group.enter() - self.sendArticleStatusChunk(syncStatuses: syncStatusChunk) { - group.leave() - } - } - - group.notify(queue: DispatchQueue.global(qos: .background)) { - os_log(.debug, log: self.log, "Done sending article statuses.") - DispatchQueue.main.async { - self.operationDelegate?.operationDidComplete(self) - } - } - - } - - switch result { - case .success(let syncStatuses): - processStatuses(syncStatuses) - case .failure(let databaseError): - os_log(.error, log: self.log, "Send status error: %@.", databaseError.localizedDescription) - self.operationDelegate?.cancelOperation(self) } + + } else { + + selectForProcessing() + } } - func sendArticleStatusChunk(syncStatuses: [SyncStatus], completion: @escaping () -> Void) { +} + +private extension CloudKitSendStatusOperation { + + func selectForProcessing() { + database.selectForProcessing(limit: blockSize) { result in + switch result { + case .success(let syncStatuses): + + guard syncStatuses.count > 0 else { + if self.showProgress { + self.refreshProgress?.completeTask() + } + os_log(.debug, log: self.log, "Done sending article statuses.") + self.operationDelegate?.operationDidComplete(self) + return + } + + self.processStatuses(syncStatuses) { + self.selectForProcessing() + } + + case .failure(let databaseError): + + os_log(.error, log: self.log, "Send status error: %@.", databaseError.localizedDescription) + self.operationDelegate?.cancelOperation(self) + + } + } + } + + func processStatuses(_ syncStatuses: [SyncStatus], completion: @escaping () -> Void) { guard let account = account, let articlesZone = articlesZone else { completion() return @@ -107,7 +118,8 @@ class CloudKitSendStatusOperation: MainThreadOperation { switch result { case .success: self.database.deleteSelectedForProcessing(syncStatuses.map({ $0.articleID })) { _ in - if self.showProgress { + // Don't clear the last one since we might have had additional ticks added + if self.showProgress && self.refreshProgress?.numberRemaining ?? 0 > 1 { self.refreshProgress?.completeTask() } os_log(.debug, log: self.log, "Done sending article status block...") diff --git a/Frameworks/SyncDatabase/SyncDatabase.swift b/Frameworks/SyncDatabase/SyncDatabase.swift index 4c2c04aec..a7c754038 100644 --- a/Frameworks/SyncDatabase/SyncDatabase.swift +++ b/Frameworks/SyncDatabase/SyncDatabase.swift @@ -36,8 +36,8 @@ public struct SyncDatabase { try syncStatusTable.insertStatuses(statuses) } - public func selectForProcessing(completion: @escaping SyncStatusesCompletionBlock) { - return syncStatusTable.selectForProcessing(completion) + public func selectForProcessing(limit: Int? = nil, completion: @escaping SyncStatusesCompletionBlock) { + return syncStatusTable.selectForProcessing(limit: limit, completion: completion) } public func selectPendingCount(completion: @escaping DatabaseIntCompletionBlock) { diff --git a/Frameworks/SyncDatabase/SyncStatusTable.swift b/Frameworks/SyncDatabase/SyncStatusTable.swift index 138c769c0..4b55a9bb0 100644 --- a/Frameworks/SyncDatabase/SyncStatusTable.swift +++ b/Frameworks/SyncDatabase/SyncStatusTable.swift @@ -20,7 +20,7 @@ struct SyncStatusTable: DatabaseTable { self.queue = queue } - func selectForProcessing(_ completion: @escaping SyncStatusesCompletionBlock) { + func selectForProcessing(limit: Int?, completion: @escaping SyncStatusesCompletionBlock) { queue.runInTransaction { databaseResult in var statuses = Set() var error: DatabaseError? @@ -29,7 +29,10 @@ struct SyncStatusTable: DatabaseTable { let updateSQL = "update syncStatus set selected = true" database.executeUpdate(updateSQL, withArgumentsIn: nil) - let selectSQL = "select * from syncStatus where selected == true" + var selectSQL = "select * from syncStatus where selected == true" + if let limit = limit { + selectSQL = "\(selectSQL) limit \(limit)" + } if let resultSet = database.executeQuery(selectSQL, withArgumentsIn: nil) { statuses = resultSet.mapToSet(self.statusWithRow) } @@ -135,7 +138,7 @@ struct SyncStatusTable: DatabaseTable { func makeDatabaseCall(_ database: FMDatabase) { let parameters = articleIDs.map { $0 as AnyObject } let placeholders = NSString.rs_SQLValueList(withPlaceholders: UInt(articleIDs.count))! - let deleteSQL = "delete from syncStatus where articleID in \(placeholders)" + let deleteSQL = "delete from syncStatus where selected = true and articleID in \(placeholders)" database.executeUpdate(deleteSQL, withArgumentsIn: parameters) }