|
|
|
@ -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.")];
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
OWSLogVerbose(@"");
|
|
|
|
|
|
|
|
|
|
__weak OWSBackupExportJob *weakSelf = self;
|
|
|
|
|
[OWSBackupAPI fetchAllRecordNamesWithRecipientId:self.recipientId
|
|
|
|
|
success:^(NSArray<NSString *> *recordNames) {
|
|
|
|
|
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
|
|
|
|
|
AnyPromise *promise = [AnyPromise promiseWithResolverBlock:^(PMKResolver resolve) {
|
|
|
|
|
[OWSBackupAPI fetchAllRecordNamesWithRecipientId:self.recipientId
|
|
|
|
|
success:^(NSArray<NSString *> *recordNames) {
|
|
|
|
|
OWSBackupExportJob *strongSelf = weakSelf;
|
|
|
|
|
if (!strongSelf) {
|
|
|
|
|
return;
|
|
|
|
|
return resolve(OWSBackupErrorWithDescription(@"Backup export no longer active."));
|
|
|
|
|
}
|
|
|
|
|
if (strongSelf.isComplete) {
|
|
|
|
|
return;
|
|
|
|
|
return resolve(OWSBackupErrorWithDescription(@"Backup export no longer active."));
|
|
|
|
|
}
|
|
|
|
|
strongSelf.lastValidRecordNames = [NSSet setWithArray:recordNames];
|
|
|
|
|
completion(YES);
|
|
|
|
|
});
|
|
|
|
|
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;
|
|
|
|
|
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;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Pop next item from queue, preserving ordering.
|
|
|
|
|
OWSBackupExportItem *item = self.unsavedDatabaseItems.firstObject;
|
|
|
|
|
[self.unsavedDatabaseItems removeObjectAtIndex:0];
|
|
|
|
|
AnyPromise *promise = [AnyPromise promiseWithValue:@(1)];
|
|
|
|
|
|
|
|
|
|
OWSAssertDebug(item.encryptedItem.filePath.length > 0);
|
|
|
|
|
// We need to preserve ordering of database shards.
|
|
|
|
|
for (OWSBackupExportItem *item in self.unsavedDatabaseItems) {
|
|
|
|
|
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;
|
|
|
|
|
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);
|
|
|
|
|
}];
|
|
|
|
|
}];
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
[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];
|
|
|
|
|
|
|
|
|
|
// 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];
|
|
|
|
|
});
|
|
|
|
|
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];
|
|
|
|
|
|
|
|
|
|
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];
|
|
|
|
|
}];
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|