Convert backup logic to use promises.
This commit is contained in:
parent
a9120906fa
commit
6d8fa78023
|
@ -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) {
|
||||
|
|
|
@ -26,6 +26,8 @@ NSString *NSStringForBackupImportState(OWSBackupState state);
|
|||
|
||||
NSArray<NSString *> *MiscCollectionsToBackup(void);
|
||||
|
||||
NSError *OWSBackupErrorWithDescription(NSString *description);
|
||||
|
||||
@class AnyPromise;
|
||||
@class OWSBackupIO;
|
||||
@class TSAttachmentPointer;
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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];
|
||||
}];
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in New Issue