diff --git a/SignalServiceKit/src/Contacts/OWSContactDiscoveryOperation.swift b/SignalServiceKit/src/Contacts/OWSContactDiscoveryOperation.swift index d630980f8..018054f7c 100644 --- a/SignalServiceKit/src/Contacts/OWSContactDiscoveryOperation.swift +++ b/SignalServiceKit/src/Contacts/OWSContactDiscoveryOperation.swift @@ -5,7 +5,7 @@ import Foundation @objc(OWSContactDiscoveryOperation) -class ContactDiscoveryOperation: OWSOperation { +class ContactDiscoveryOperation: OWSOperation, LegacyContactDiscoveryBatchOperationDelegate { let batchSize = 2048 let recipientIdsToLookup: [String] @@ -23,6 +23,7 @@ class ContactDiscoveryOperation: OWSOperation { Logger.debug("\(logTag) in \(#function) with recipientIdsToLookup: \(recipientIdsToLookup.count)") for batchIds in recipientIdsToLookup.chunked(by: batchSize) { let batchOperation = LegacyContactDiscoveryBatchOperation(recipientIdsToLookup: batchIds) + batchOperation.delegate = self self.addDependency(batchOperation) } } @@ -44,11 +45,24 @@ class ContactDiscoveryOperation: OWSOperation { self.reportSuccess() } + + // MARK: LegacyContactDiscoveryBatchOperationDelegate + func contactDiscoverBatchOperation(_ contactDiscoverBatchOperation: LegacyContactDiscoveryBatchOperation, didFailWithError error: Error) { + Logger.debug("\(logTag) in \(#function) canceling self and all dependencies.") + + self.dependencies.forEach { $0.cancel() } + self.cancel() + } +} + +protocol LegacyContactDiscoveryBatchOperationDelegate: class { + func contactDiscoverBatchOperation(_ contactDiscoverBatchOperation: LegacyContactDiscoveryBatchOperation, didFailWithError error: Error) } class LegacyContactDiscoveryBatchOperation: OWSOperation { var registeredRecipientIds: Set + weak var delegate: LegacyContactDiscoveryBatchOperationDelegate? private let recipientIdsToLookup: [String] private var networkManager: TSNetworkManager { @@ -72,10 +86,17 @@ class LegacyContactDiscoveryBatchOperation: OWSOperation { override func run() { Logger.debug("\(logTag) in \(#function)") + guard !isCancelled else { + Logger.info("\(logTag) in \(#function) no work to do, since we were canceled") + self.reportCancelled() + return + } + var phoneNumbersByHashes: [String: String] = [:] for recipientId in recipientIdsToLookup { let hash = Cryptography.truncatedSHA1Base64EncodedWithoutPadding(recipientId) + assert(phoneNumbersByHashes[hash] == nil) phoneNumbersByHashes[hash] = recipientId } @@ -121,6 +142,11 @@ class LegacyContactDiscoveryBatchOperation: OWSOperation { CDSFeedbackOperation.operationQueue.addOperations([newCDSBatchOperation, cdsFeedbackOperation], waitUntilFinished: false) } + // Called at most one time. + override func didFail(error: Error) { + self.delegate?.contactDiscoverBatchOperation(self, didFailWithError: error) + } + // MARK: Private Helpers private func parse(response: Any?, phoneNumbersByHashes: [String: String]) throws -> Set { diff --git a/SignalServiceKit/src/Util/OWSOperation.h b/SignalServiceKit/src/Util/OWSOperation.h index c86c90394..55e30bed0 100644 --- a/SignalServiceKit/src/Util/OWSOperation.h +++ b/SignalServiceKit/src/Util/OWSOperation.h @@ -42,17 +42,27 @@ typedef NS_ENUM(NSInteger, OWSOperationState) { // Called at most one time. - (void)didSucceed; +// Called at most one time. +- (void)didCancel; + // Called at most one time, once retry is no longer possible. - (void)didFailWithError:(NSError *)error NS_SWIFT_NAME(didFail(error:)); #pragma mark - Success/Error - Do Not Override -// Complete the operation successfully. -// Should be called at most once per operation instance. -// You must ensure that `run` cannot fail after calling `reportSuccess`. +// Report that the operation completed successfully. +// +// Each invocation of `run` must make exactly one call to one of: `reportSuccess`, `reportCancelled`, or `reportError:` - (void)reportSuccess; -// Should be called at most once per `run`. +// Call this when you abort before completion due to being cancelled. +// +// Each invocation of `run` must make exactly one call to one of: `reportSuccess`, `reportCancelled`, or `reportError:` +- (void)reportCancelled; + +// Report that the operation failed to complete due to an error. +// +// Each invocation of `run` must make exactly one call to one of: `reportSuccess`, `reportCancelled`, or `reportError:` // You must ensure that `run` cannot succeed after calling `reportError`, e.g. generally you'll write something like // this: // diff --git a/SignalServiceKit/src/Util/OWSOperation.m b/SignalServiceKit/src/Util/OWSOperation.m index d70cbb4c6..da9fa32e8 100644 --- a/SignalServiceKit/src/Util/OWSOperation.m +++ b/SignalServiceKit/src/Util/OWSOperation.m @@ -81,6 +81,13 @@ NSString *const OWSOperationKeyIsFinished = @"isFinished"; // Override in subclass if necessary } +// Called at most one time. +- (void)didCancel +{ + // no-op + // Override in subclass if necessary +} + // Called at most one time, once retry is no longer possible. - (void)didFailWithError:(NSError *)error { @@ -113,6 +120,14 @@ NSString *const OWSOperationKeyIsFinished = @"isFinished"; [self markAsComplete]; } +// These methods are not intended to be subclassed +- (void)reportCancelled +{ + DDLogDebug(@"%@ cancelled.", self.logTag); + [self didCancel]; + [self markAsComplete]; +} + - (void)reportError:(NSError *)error { DDLogDebug(@"%@ reportError: %@, fatal?: %d, retryable?: %d, remainingRetries: %lu",