Convert backup logic to use promises.

This commit is contained in:
Matthew Chen 2018-11-27 14:01:25 -05:00
parent a9120906fa
commit 6d8fa78023
5 changed files with 280 additions and 286 deletions

View File

@ -471,6 +471,8 @@ NSString *const kProfileView_LastPresentedDate = @"kProfileView_LastPresentedDat
__weak ProfileViewController *weakSelf = self;
[OWSBackup.sharedManager
checkCanImportBackup:^(BOOL value) {
OWSAssertIsOnMainThread();
OWSLogInfo(@"has backup available for import? %d", value);
if (value) {

View File

@ -26,6 +26,8 @@ NSString *NSStringForBackupImportState(OWSBackupState state);
NSArray<NSString *> *MiscCollectionsToBackup(void);
NSError *OWSBackupErrorWithDescription(NSString *description);
@class AnyPromise;
@class OWSBackupIO;
@class TSAttachmentPointer;

View File

@ -65,6 +65,17 @@ NSArray<NSString *> *MiscCollectionsToBackup(void)
];
}
typedef NS_ENUM(NSInteger, OWSBackupErrorCode) {
OWSBackupErrorCodeAssertionFailure = 0,
};
NSError *OWSBackupErrorWithDescription(NSString *description)
{
return [NSError errorWithDomain:@"OWSBackupErrorDomain"
code:OWSBackupErrorCodeAssertionFailure
userInfo:@{ NSLocalizedDescriptionKey : description }];
}
// TODO: Observe Reachability.
@interface OWSBackup () <OWSBackupJobDelegate>
@ -475,17 +486,10 @@ NSArray<NSString *> *MiscCollectionsToBackup(void)
[[OWSBackupAPI checkCloudKitAccessObjc]
.thenInBackground(^{
[OWSBackupAPI checkForManifestInCloudWithRecipientId:recipientId
success:^(BOOL value) {
dispatch_async(dispatch_get_main_queue(), ^{
success(value);
});
}
failure:^(NSError *error) {
dispatch_async(dispatch_get_main_queue(), ^{
failure(error);
});
}];
return [OWSBackupAPI checkForManifestInCloudObjcWithRecipientId:recipientId];
})
.then(^(NSNumber *value) {
success(value.boolValue);
})
.catch(^(NSError *error) {
failure(error);

View File

@ -412,17 +412,23 @@ import PromiseKit
}
@objc
public class func checkForManifestInCloud(recipientId: String,
success: @escaping (Bool) -> Void,
failure: @escaping (Error) -> Void) {
public class func checkForManifestInCloudObjc(recipientId: String) -> AnyPromise {
return AnyPromise(checkForManifestInCloud(recipientId: recipientId))
}
public class func checkForManifestInCloud(recipientId: String) -> Promise<Bool> {
let (promise, resolver) = Promise<Bool>.pending()
let recordName = recordNameForManifest(recipientId: recipientId)
checkForFileInCloud(recordName: recordName,
remainingRetries: maxRetries,
success: { (record) in
success(record != nil)
resolver.fulfill(record != nil)
},
failure: failure)
failure: { (error) in
resolver.reject(error)
})
return promise
}
@objc
@ -675,26 +681,24 @@ import PromiseKit
public class func checkCloudKitAccess() -> Promise<Void> {
let (promise, resolver) = Promise<Void>.pending()
CKContainer.default().accountStatus(completionHandler: { (accountStatus, error) in
DispatchQueue.main.async {
if let error = error {
Logger.error("Unknown error: \(String(describing: error)).")
resolver.reject(error)
return
}
switch accountStatus {
case .couldNotDetermine:
Logger.error("could not determine CloudKit account status: \(String(describing: error)).")
resolver.reject(BackupError.couldNotDetermineAccountStatus)
case .noAccount:
Logger.error("no CloudKit account.")
resolver.reject(BackupError.noAccount)
case .restricted:
Logger.error("restricted CloudKit account.")
resolver.reject(BackupError.restrictedAccountStatus)
case .available:
Logger.verbose("CloudKit access okay.")
resolver.fulfill(())
}
if let error = error {
Logger.error("Unknown error: \(String(describing: error)).")
resolver.reject(error)
return
}
switch accountStatus {
case .couldNotDetermine:
Logger.error("could not determine CloudKit account status: \(String(describing: error)).")
resolver.reject(BackupError.couldNotDetermineAccountStatus)
case .noAccount:
Logger.error("no CloudKit account.")
resolver.reject(BackupError.noAccount)
case .restricted:
Logger.error("restricted CloudKit account.")
resolver.reject(BackupError.restrictedAccountStatus)
case .available:
Logger.verbose("CloudKit access okay.")
resolver.fulfill(())
}
})
return promise

View File

@ -366,66 +366,46 @@ NS_ASSUME_NONNULL_BEGIN
progress:nil];
__weak OWSBackupExportJob *weakSelf = self;
[self configureExportWithCompletion:^(BOOL configureExportSuccess) {
if (!configureExportSuccess) {
[self
failWithErrorDescription:NSLocalizedString(@"BACKUP_EXPORT_ERROR_COULD_NOT_EXPORT",
@"Error indicating the backup export could not export the user's data.")];
return;
}
[[self configureExport]
.thenInBackground(^{
return [self fetchAllRecords];
})
.thenInBackground(^{
[self updateProgressWithDescription:NSLocalizedString(@"BACKUP_EXPORT_PHASE_EXPORT",
@"Indicates that the backup export data is being exported.")
progress:nil];
if (self.isComplete) {
return;
}
[self fetchAllRecordsWithCompletion:^(BOOL tryToFetchManifestSuccess) {
if (!tryToFetchManifestSuccess) {
[self failWithErrorDescription:
NSLocalizedString(@"BACKUP_EXPORT_ERROR_COULD_NOT_EXPORT",
@"Error indicating the backup export could not export the user's data.")];
return;
}
return [self exportDatabase];
})
.thenInBackground(^{
return [self saveToCloud];
})
.thenInBackground(^{
return [self cleanUp];
})
.thenInBackground(^{
[self succeed];
})
.catch(^(NSError *error) {
OWSFailDebug(@"Backup export failed with error: %@.", error);
if (self.isComplete) {
return;
}
[self updateProgressWithDescription:NSLocalizedString(@"BACKUP_EXPORT_PHASE_EXPORT",
@"Indicates that the backup export data is being exported.")
progress:nil];
if (![self exportDatabase]) {
[self failWithErrorDescription:
NSLocalizedString(@"BACKUP_EXPORT_ERROR_COULD_NOT_EXPORT",
@"Error indicating the backup export could not export the user's data.")];
return;
}
if (self.isComplete) {
return;
}
[self saveToCloudWithCompletion:^(NSError *_Nullable saveError) {
if (saveError) {
[weakSelf failWithError:saveError];
return;
}
[self cleanUpWithCompletion:^(NSError *_Nullable cleanUpError) {
if (cleanUpError) {
[weakSelf failWithError:cleanUpError];
return;
}
[weakSelf succeed];
}];
}];
}];
}];
[weakSelf failWithErrorDescription:
NSLocalizedString(@"BACKUP_EXPORT_ERROR_COULD_NOT_EXPORT",
@"Error indicating the backup export could not export the user's data.")];
}) retainUntilComplete];
}
- (void)configureExportWithCompletion:(OWSBackupJobBoolCompletion)completion
- (AnyPromise *)configureExport
{
OWSAssertDebug(completion);
OWSLogVerbose(@"");
if (self.isComplete) {
return [AnyPromise promiseWithValue:OWSBackupErrorWithDescription(@"Backup export no longer active.")];
}
if (![self ensureJobTempDir]) {
OWSFailDebug(@"Could not create jobTempDirPath.");
return completion(NO);
return [AnyPromise promiseWithValue:OWSBackupErrorWithDescription(@"Could not create jobTempDirPath.")];
}
self.backupIO = [[OWSBackupIO alloc] initWithJobTempDirPath:self.jobTempDirPath];
@ -437,55 +417,79 @@ NS_ASSUME_NONNULL_BEGIN
//
// We use an arbitrary request that requires authentication
// to verify our account state.
TSRequest *currentSignedPreKey = [OWSRequestFactory currentSignedPreKeyRequest];
[[TSNetworkManager sharedManager] makeRequest:currentSignedPreKey
success:^(NSURLSessionDataTask *task, NSDictionary *responseObject) {
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
completion(YES);
});
}
failure:^(NSURLSessionDataTask *task, NSError *error) {
// TODO: We may want to surface this in the UI.
OWSLogError(@"could not verify account status: %@.", error);
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
completion(NO);
});
}];
AnyPromise *promise = [AnyPromise promiseWithResolverBlock:^(PMKResolver resolve) {
TSRequest *currentSignedPreKey = [OWSRequestFactory currentSignedPreKeyRequest];
[[TSNetworkManager sharedManager] makeRequest:currentSignedPreKey
success:^(NSURLSessionDataTask *task, NSDictionary *responseObject) {
resolve(@(1));
}
failure:^(NSURLSessionDataTask *task, NSError *error) {
// TODO: We may want to surface this in the UI.
OWSLogError(@"could not verify account status: %@.", error);
resolve(error);
}];
}];
[promise retainUntilComplete];
return promise;
}
- (void)fetchAllRecordsWithCompletion:(OWSBackupJobBoolCompletion)completion
- (AnyPromise *)fetchAllRecords
{
OWSAssertDebug(completion);
OWSLogVerbose(@"");
if (self.isComplete) {
return;
return [AnyPromise promiseWithValue:OWSBackupErrorWithDescription(@"Backup export no longer active.")];
}
__weak OWSBackupExportJob *weakSelf = self;
AnyPromise *promise = [AnyPromise promiseWithResolverBlock:^(PMKResolver resolve) {
[OWSBackupAPI fetchAllRecordNamesWithRecipientId:self.recipientId
success:^(NSArray<NSString *> *recordNames) {
OWSBackupExportJob *strongSelf = weakSelf;
if (!strongSelf) {
return resolve(OWSBackupErrorWithDescription(@"Backup export no longer active."));
}
if (strongSelf.isComplete) {
return resolve(OWSBackupErrorWithDescription(@"Backup export no longer active."));
}
strongSelf.lastValidRecordNames = [NSSet setWithArray:recordNames];
resolve(@(1));
}
failure:^(NSError *error) {
resolve(error);
}];
}];
[promise retainUntilComplete];
return promise;
}
- (AnyPromise *)exportDatabase
{
OWSAssertDebug(self.backupIO);
OWSLogVerbose(@"");
if (self.isComplete) {
return [AnyPromise promiseWithValue:OWSBackupErrorWithDescription(@"Backup export no longer active.")];
}
__weak OWSBackupExportJob *weakSelf = self;
[OWSBackupAPI fetchAllRecordNamesWithRecipientId:self.recipientId
success:^(NSArray<NSString *> *recordNames) {
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
OWSBackupExportJob *strongSelf = weakSelf;
if (!strongSelf) {
return;
}
if (strongSelf.isComplete) {
return;
}
strongSelf.lastValidRecordNames = [NSSet setWithArray:recordNames];
completion(YES);
});
return [AnyPromise promiseWithResolverBlock:^(PMKResolver resolve) {
OWSBackupExportJob *strongSelf = weakSelf;
if (!strongSelf) {
return resolve(OWSBackupErrorWithDescription(@"Backup export no longer active."));
}
failure:^(NSError *error) {
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
completion(NO);
});
}];
if (![strongSelf performExportDatabase]) {
NSError *error = OWSBackupErrorWithDescription(@"Backup export failed.");
return resolve(error);
}
resolve(@(1));
}];
}
- (BOOL)exportDatabase
- (BOOL)performExportDatabase
{
OWSAssertDebug(self.backupIO);
@ -687,12 +691,14 @@ NS_ASSUME_NONNULL_BEGIN
return YES;
}
- (void)saveToCloudWithCompletion:(OWSBackupJobCompletion)completion
- (AnyPromise *)saveToCloud
{
OWSAssertDebug(completion);
OWSLogVerbose(@"");
if (self.isComplete) {
return [AnyPromise promiseWithValue:OWSBackupErrorWithDescription(@"Backup export no longer active.")];
}
self.savedDatabaseItems = [NSMutableArray new];
self.savedAttachmentItems = [NSMutableArray new];
@ -728,20 +734,6 @@ NS_ASSUME_NONNULL_BEGIN
}
OWSLogInfo(@"exporting %@: count: %zd, bytes: %llu.", @"all items", totalFileCount, totalFileSize);
[self saveNextFileToCloudWithCompletion:completion];
}
// This method uploads one file (the "next" file) each time it
// is called. Each successful file upload re-invokes this method
// until the last (the manifest file).
- (void)saveNextFileToCloudWithCompletion:(OWSBackupJobCompletion)completion
{
OWSAssertDebug(completion);
if (self.isComplete) {
return;
}
// Add one for the manifest
NSUInteger unsavedCount = (self.unsavedDatabaseItems.count + self.unsavedAttachmentExports.count + 1);
NSUInteger savedCount = (self.savedDatabaseItems.count + self.savedAttachmentItems.count);
@ -751,65 +743,79 @@ NS_ASSUME_NONNULL_BEGIN
@"Indicates that the backup export data is being uploaded.")
progress:@(progress)];
if ([self saveNextDatabaseFileToCloudWithCompletion:completion]) {
return;
}
if ([self saveNextAttachmentFileToCloudWithCompletion:completion]) {
return;
}
[self saveManifestFileToCloudWithCompletion:completion];
// Save attachment files _before_ anything else, since they
// are the only reusable backup records.
return [self saveAttachmentFilesToCloud]
.thenInBackground(^{
return [self saveDatabaseFilesToCloud];
})
.thenInBackground(^{
return [self saveManifestFileToCloud];
});
}
// This method returns YES IFF "work was done and there might be more work to do".
- (BOOL)saveNextDatabaseFileToCloudWithCompletion:(OWSBackupJobCompletion)completion
- (AnyPromise *)saveDatabaseFilesToCloud
{
OWSAssertDebug(completion);
__weak OWSBackupExportJob *weakSelf = self;
if (self.unsavedDatabaseItems.count < 1) {
return NO;
AnyPromise *promise = [AnyPromise promiseWithValue:@(1)];
// We need to preserve ordering of database shards.
for (OWSBackupExportItem *item in self.unsavedDatabaseItems) {
OWSAssertDebug(item.encryptedItem.filePath.length > 0);
promise = promise.thenInBackground(^{
return [AnyPromise promiseWithResolverBlock:^(PMKResolver resolve) {
if (self.isComplete) {
return resolve(OWSBackupErrorWithDescription(@"Backup export no longer active."));
}
[OWSBackupAPI saveEphemeralDatabaseFileToCloudWithRecipientId:self.recipientId
fileUrl:[NSURL fileURLWithPath:item.encryptedItem.filePath]
success:^(NSString *recordName) {
OWSBackupExportJob *strongSelf = weakSelf;
if (!strongSelf) {
return resolve(OWSBackupErrorWithDescription(@"Backup export no longer active."));
}
item.recordName = recordName;
[strongSelf.savedDatabaseItems addObject:item];
resolve(@(1));
}
failure:^(NSError *error) {
// Database files are critical so any error uploading them is unrecoverable.
OWSLogVerbose(@"error while saving file: %@", item.encryptedItem.filePath);
resolve(error);
}];
}];
});
}
// Pop next item from queue, preserving ordering.
OWSBackupExportItem *item = self.unsavedDatabaseItems.firstObject;
[self.unsavedDatabaseItems removeObjectAtIndex:0];
OWSAssertDebug(item.encryptedItem.filePath.length > 0);
[OWSBackupAPI saveEphemeralDatabaseFileToCloudWithRecipientId:self.recipientId
fileUrl:[NSURL fileURLWithPath:item.encryptedItem.filePath]
success:^(NSString *recordName) {
// Ensure that we continue to work off the main thread.
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
item.recordName = recordName;
[weakSelf.savedDatabaseItems addObject:item];
[weakSelf saveNextFileToCloudWithCompletion:completion];
});
}
failure:^(NSError *error) {
// Ensure that we continue to work off the main thread.
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
// Database files are critical so any error uploading them is unrecoverable.
OWSLogVerbose(@"error while saving file: %@", item.encryptedItem.filePath);
completion(error);
});
}];
return YES;
[self.unsavedDatabaseItems removeAllObjects];
return promise;
}
// This method returns YES IFF "work was done and there might be more work to do".
- (BOOL)saveNextAttachmentFileToCloudWithCompletion:(OWSBackupJobCompletion)completion
- (AnyPromise *)saveAttachmentFilesToCloud
{
OWSAssertDebug(completion);
AnyPromise *promise = [AnyPromise promiseWithValue:@(1)];
__weak OWSBackupExportJob *weakSelf = self;
if (self.unsavedAttachmentExports.count < 1) {
return NO;
for (OWSAttachmentExport *attachmentExport in self.unsavedAttachmentExports) {
promise = promise.thenInBackground(^{
return [AnyPromise promiseWithResolverBlock:^(PMKResolver resolve) {
if (self.isComplete) {
return resolve(OWSBackupErrorWithDescription(@"Backup export no longer active."));
}
[self saveAttachmentFileToCloud:attachmentExport resolve:resolve];
}];
});
}
[self.unsavedAttachmentExports removeAllObjects];
return promise;
}
// No need to preserve ordering of attachments.
OWSAttachmentExport *attachmentExport = self.unsavedAttachmentExports.lastObject;
[self.unsavedAttachmentExports removeLastObject];
- (void)saveAttachmentFileToCloud:(OWSAttachmentExport *)attachmentExport resolve:(PMKResolver)resolve
{
__weak OWSBackupExportJob *weakSelf = self;
if (self.lastValidRecordNames) {
// Wherever possible, we do incremental backups and re-use fragments of the last
@ -846,8 +852,7 @@ NS_ASSUME_NONNULL_BEGIN
OWSLogVerbose(@"recycled attachment: %@ as %@",
attachmentExport.attachmentFilePath,
attachmentExport.relativeFilePath);
[self saveNextFileToCloudWithCompletion:completion];
return YES;
return resolve(@(1));
}
}
@ -856,8 +861,7 @@ NS_ASSUME_NONNULL_BEGIN
// attachment to disk.
if (![attachmentExport prepareForUpload]) {
// Attachment files are non-critical so any error uploading them is recoverable.
[weakSelf saveNextFileToCloudWithCompletion:completion];
return YES;
return resolve(@(1));
}
OWSAssertDebug(attachmentExport.relativeFilePath.length > 0);
OWSAssertDebug(attachmentExport.encryptedItem);
@ -877,96 +881,81 @@ NS_ASSUME_NONNULL_BEGIN
return [NSURL fileURLWithPath:attachmentExport.encryptedItem.filePath];
}
success:^(NSString *recordName) {
// Ensure that we continue to work off the main thread.
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
OWSBackupExportJob *strongSelf = weakSelf;
if (!strongSelf) {
return;
}
OWSBackupExportJob *strongSelf = weakSelf;
if (!strongSelf) {
return resolve(OWSBackupErrorWithDescription(@"Backup export no longer active."));
}
if (![attachmentExport cleanUp]) {
OWSLogError(@"couldn't clean up attachment export.");
// Attachment files are non-critical so any error uploading them is recoverable.
}
if (![attachmentExport cleanUp]) {
OWSLogError(@"couldn't clean up attachment export.");
// Attachment files are non-critical so any error uploading them is recoverable.
}
OWSBackupExportItem *exportItem = [OWSBackupExportItem new];
exportItem.encryptedItem = attachmentExport.encryptedItem;
exportItem.recordName = recordName;
exportItem.attachmentExport = attachmentExport;
[strongSelf.savedAttachmentItems addObject:exportItem];
OWSBackupExportItem *exportItem = [OWSBackupExportItem new];
exportItem.encryptedItem = attachmentExport.encryptedItem;
exportItem.recordName = recordName;
exportItem.attachmentExport = attachmentExport;
[strongSelf.savedAttachmentItems addObject:exportItem];
// Immediately save the record metadata to facilitate export resume.
OWSBackupFragment *backupFragment = [OWSBackupFragment new];
backupFragment.recordName = recordName;
backupFragment.encryptionKey = exportItem.encryptedItem.encryptionKey;
backupFragment.relativeFilePath = attachmentExport.relativeFilePath;
backupFragment.attachmentId = attachmentExport.attachmentId;
backupFragment.uncompressedDataLength = exportItem.uncompressedDataLength;
[backupFragment save];
// Immediately save the record metadata to facilitate export resume.
OWSBackupFragment *backupFragment = [OWSBackupFragment new];
backupFragment.recordName = recordName;
backupFragment.encryptionKey = exportItem.encryptedItem.encryptionKey;
backupFragment.relativeFilePath = attachmentExport.relativeFilePath;
backupFragment.attachmentId = attachmentExport.attachmentId;
backupFragment.uncompressedDataLength = exportItem.uncompressedDataLength;
[backupFragment save];
OWSLogVerbose(@"saved attachment: %@ as %@",
attachmentExport.attachmentFilePath,
attachmentExport.relativeFilePath);
[strongSelf saveNextFileToCloudWithCompletion:completion];
});
OWSLogVerbose(
@"saved attachment: %@ as %@", attachmentExport.attachmentFilePath, attachmentExport.relativeFilePath);
return resolve(@(1));
}
failure:^(NSError *error) {
// Ensure that we continue to work off the main thread.
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
if (![attachmentExport cleanUp]) {
OWSLogError(@"couldn't clean up attachment export.");
// Attachment files are non-critical so any error uploading them is recoverable.
}
if (![attachmentExport cleanUp]) {
OWSLogError(@"couldn't clean up attachment export.");
// Attachment files are non-critical so any error uploading them is recoverable.
[weakSelf saveNextFileToCloudWithCompletion:completion];
});
}];
}
return YES;
// Attachment files are non-critical so any error uploading them is recoverable.
return resolve(@(1));
}];
}
- (void)saveManifestFileToCloudWithCompletion:(OWSBackupJobCompletion)completion
- (AnyPromise *)saveManifestFileToCloud
{
OWSAssertDebug(completion);
if (self.isComplete) {
return [AnyPromise promiseWithValue:OWSBackupErrorWithDescription(@"Backup export no longer active.")];
}
OWSBackupEncryptedItem *_Nullable encryptedItem = [self writeManifestFile];
if (!encryptedItem) {
completion(OWSErrorWithCodeDescription(OWSErrorCodeExportBackupFailed,
NSLocalizedString(@"BACKUP_EXPORT_ERROR_COULD_NOT_EXPORT",
@"Error indicating the backup export could not export the user's data.")));
return;
return [AnyPromise promiseWithValue:OWSBackupErrorWithDescription(@"Could not generate manifest.")];
}
OWSBackupExportItem *exportItem = [OWSBackupExportItem new];
exportItem.encryptedItem = encryptedItem;
__weak OWSBackupExportJob *weakSelf = self;
[OWSBackupAPI upsertManifestFileToCloudWithRecipientId:self.recipientId
fileUrl:[NSURL fileURLWithPath:encryptedItem.filePath]
success:^(NSString *recordName) {
// Ensure that we continue to work off the main thread.
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
return [AnyPromise promiseWithResolverBlock:^(PMKResolver resolve) {
[OWSBackupAPI upsertManifestFileToCloudWithRecipientId:self.recipientId
fileUrl:[NSURL fileURLWithPath:encryptedItem.filePath]
success:^(NSString *recordName) {
OWSBackupExportJob *strongSelf = weakSelf;
if (!strongSelf) {
return;
return resolve(OWSBackupErrorWithDescription(@"Backup export no longer active."));
}
exportItem.recordName = recordName;
strongSelf.manifestItem = exportItem;
// All files have been saved to the cloud.
completion(nil);
});
}
failure:^(NSError *error) {
// Ensure that we continue to work off the main thread.
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
resolve(@(1));
}
failure:^(NSError *error) {
// The manifest file is critical so any error uploading them is unrecoverable.
completion(error);
});
}];
resolve(error);
}];
}];
}
- (nullable OWSBackupEncryptedItem *)writeManifestFile
@ -1020,13 +1009,11 @@ NS_ASSUME_NONNULL_BEGIN
return result;
}
- (void)cleanUpWithCompletion:(OWSBackupJobCompletion)completion
- (AnyPromise *)cleanUp
{
OWSAssertDebug(completion);
if (self.isComplete) {
// Job was aborted.
return completion(nil);
return [AnyPromise promiseWithValue:OWSBackupErrorWithDescription(@"Backup export no longer active.")];
}
OWSLogVerbose(@"");
@ -1062,7 +1049,7 @@ NS_ASSUME_NONNULL_BEGIN
[self cleanUpMetadataCacheWithActiveRecordNames:activeRecordNames];
[self cleanUpCloudWithActiveRecordNames:activeRecordNames completion:completion];
return [self cleanUpCloudWithActiveRecordNames:activeRecordNames];
}
- (void)cleanUpMetadataCacheWithActiveRecordNames:(NSSet<NSString *> *)activeRecordNames
@ -1086,22 +1073,23 @@ NS_ASSUME_NONNULL_BEGIN
}];
}
- (void)cleanUpCloudWithActiveRecordNames:(NSSet<NSString *> *)activeRecordNames
completion:(OWSBackupJobCompletion)completion
- (AnyPromise *)cleanUpCloudWithActiveRecordNames:(NSSet<NSString *> *)activeRecordNames
{
OWSAssertDebug(activeRecordNames.count > 0);
OWSAssertDebug(completion);
if (self.isComplete) {
// Job was aborted.
return completion(nil);
return [AnyPromise promiseWithValue:OWSBackupErrorWithDescription(@"Backup export no longer active.")];
}
__weak OWSBackupExportJob *weakSelf = self;
[OWSBackupAPI fetchAllRecordNamesWithRecipientId:self.recipientId
success:^(NSArray<NSString *> *recordNames) {
// Ensure that we continue to work off the main thread.
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
return [AnyPromise promiseWithResolverBlock:^(PMKResolver resolve) {
[OWSBackupAPI fetchAllRecordNamesWithRecipientId:self.recipientId
success:^(NSArray<NSString *> *recordNames) {
OWSBackupExportJob *strongSelf = weakSelf;
if (!strongSelf) {
return resolve(OWSBackupErrorWithDescription(@"Backup export no longer active."));
}
NSMutableSet<NSString *> *obsoleteRecordNames = [NSMutableSet new];
[obsoleteRecordNames addObjectsFromArray:recordNames];
[obsoleteRecordNames minusSet:activeRecordNames];
@ -1113,16 +1101,16 @@ NS_ASSUME_NONNULL_BEGIN
[weakSelf deleteRecordsFromCloud:[obsoleteRecordNames.allObjects mutableCopy]
deletedCount:0
completion:completion];
});
}
failure:^(NSError *error) {
// Ensure that we continue to work off the main thread.
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
completion:^(NSError *_Nullable error) {
// Cloud cleanup is non-critical so any error is recoverable.
resolve(@(1));
}];
}
failure:^(NSError *error) {
// Cloud cleanup is non-critical so any error is recoverable.
completion(nil);
});
}];
resolve(@(1));
}];
}];
}
- (void)deleteRecordsFromCloud:(NSMutableArray<NSString *> *)obsoleteRecordNames
@ -1160,21 +1148,15 @@ NS_ASSUME_NONNULL_BEGIN
__weak OWSBackupExportJob *weakSelf = self;
[OWSBackupAPI deleteRecordsFromCloudWithRecordNames:batchRecordNames
success:^{
// Ensure that we continue to work off the main thread.
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
[weakSelf deleteRecordsFromCloud:obsoleteRecordNames
deletedCount:deletedCount + batchRecordNames.count
completion:completion];
});
[weakSelf deleteRecordsFromCloud:obsoleteRecordNames
deletedCount:deletedCount + batchRecordNames.count
completion:completion];
}
failure:^(NSError *error) {
// Ensure that we continue to work off the main thread.
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
// Cloud cleanup is non-critical so any error is recoverable.
[weakSelf deleteRecordsFromCloud:obsoleteRecordNames
deletedCount:deletedCount + batchRecordNames.count
completion:completion];
});
// Cloud cleanup is non-critical so any error is recoverable.
[weakSelf deleteRecordsFromCloud:obsoleteRecordNames
deletedCount:deletedCount + batchRecordNames.count
completion:completion];
}];
}