Recycle backup fragments.

This commit is contained in:
Matthew Chen 2018-03-17 17:44:54 -03:00
parent 5de11d7355
commit 439d7e62e6
4 changed files with 81 additions and 22 deletions

View File

@ -296,6 +296,7 @@ NS_ASSUME_NONNULL_BEGIN
// If we are replacing an existing backup, we use some of its contents for continuity.
@property (nonatomic, nullable) NSDictionary<NSString *, OWSBackupManifestItem *> *lastManifestItemMap;
@property (nonatomic, nullable) NSSet<NSString *> *lastRecordNames;
@end
@ -315,11 +316,15 @@ NS_ASSUME_NONNULL_BEGIN
__weak OWSBackupExportJob *weakSelf = self;
[OWSBackupAPI checkCloudKitAccessWithCompletion:^(BOOL hasAccess) {
if (hasAccess) {
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
if (hasAccess) {
[weakSelf start];
});
}
} else {
[weakSelf failWithErrorDescription:
NSLocalizedString(@"BACKUP_EXPORT_ERROR_COULD_NOT_EXPORT",
@"Error indicating the a backup export could not export the user's data.")];
}
});
}];
}
@ -466,13 +471,13 @@ NS_ASSUME_NONNULL_BEGIN
if (!strongSelf) {
return;
}
if (self.isComplete) {
if (strongSelf.isComplete) {
return;
}
OWSCAssert(manifest.databaseItems.count > 0);
OWSCAssert(manifest.attachmentsItems);
[strongSelf processLastManifest:manifest];
completion(YES);
[strongSelf fetchAllRecordsWithCompletion:completion];
}
failure:^(NSError *manifestError) {
completion(NO);
@ -480,6 +485,37 @@ NS_ASSUME_NONNULL_BEGIN
backupIO:self.backupIO];
}
- (void)fetchAllRecordsWithCompletion:(OWSBackupJobBoolCompletion)completion
{
OWSAssert(completion);
if (self.isComplete) {
return;
}
DDLogVerbose(@"%@ %s", self.logTag, __PRETTY_FUNCTION__);
__weak OWSBackupExportJob *weakSelf = self;
[OWSBackupAPI fetchAllRecordNamesWithSuccess:^(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.lastRecordNames = [NSSet setWithArray:recordNames];
completion(YES);
});
}
failure:^(NSError *error) {
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
completion(NO);
});
}];
}
- (void)processLastManifest:(OWSBackupManifestContents *)manifest
{
OWSAssert(manifest);
@ -746,9 +782,12 @@ NS_ASSUME_NONNULL_BEGIN
});
}
failure:^(NSError *error) {
// Database files are critical so any error uploading them is unrecoverable.
DDLogVerbose(@"%@ error while saving file: %@", weakSelf.logTag, item.encryptedItem.filePath);
completion(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.
DDLogVerbose(@"%@ error while saving file: %@", weakSelf.logTag, item.encryptedItem.filePath);
completion(error);
});
}];
return YES;
}
@ -767,15 +806,20 @@ NS_ASSUME_NONNULL_BEGIN
OWSAttachmentExport *attachmentExport = self.unsavedAttachmentExports.lastObject;
[self.unsavedAttachmentExports removeLastObject];
if (self.lastManifestItemMap) {
if (self.lastManifestItemMap && self.lastRecordNames) {
// Wherever possible, we do incremental backups and re-use fragments of the last backup.
// Recycling fragments doesn't just reduce redundant network activity,
// it allows us to skip the local export work, i.e. encryption.
// To do so, we must preserve the metadata for these fragments.
//
// We check two things:
//
// * That the "last known backup manifest" contains an item from which we can recover
// this record's metadata.
// * That this record does in fact exist in our CloudKit database.
NSString *lastRecordName = [OWSBackupAPI recordNameForPersistentFileWithFileId:attachmentExport.attachmentId];
OWSBackupManifestItem *_Nullable lastManifestItem = self.lastManifestItemMap[lastRecordName];
if (lastManifestItem) {
if (lastManifestItem && [self.lastRecordNames containsObject:lastRecordName]) {
OWSAssert(lastManifestItem.encryptionKey.length > 0);
OWSAssert(lastManifestItem.relativeFilePath.length > 0);
@ -900,8 +944,11 @@ NS_ASSUME_NONNULL_BEGIN
});
}
failure:^(NSError *error) {
// The manifest file is critical so any error uploading them is unrecoverable.
completion(error);
// Ensure that we continue to work off the main thread.
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
// The manifest file is critical so any error uploading them is unrecoverable.
completion(error);
});
}];
}

View File

@ -47,11 +47,15 @@ NSString *const kOWSBackup_ImportDatabaseKeySpec = @"kOWSBackup_ImportDatabaseKe
__weak OWSBackupImportJob *weakSelf = self;
[OWSBackupAPI checkCloudKitAccessWithCompletion:^(BOOL hasAccess) {
if (hasAccess) {
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
if (hasAccess) {
[weakSelf start];
});
}
} else {
[weakSelf failWithErrorDescription:
NSLocalizedString(@"BACKUP_IMPORT_ERROR_COULD_NOT_IMPORT",
@"Error indicating the a backup import could not import the user's data.")];
}
});
}];
}
@ -233,7 +237,10 @@ NSString *const kOWSBackup_ImportDatabaseKeySpec = @"kOWSBackup_ImportDatabaseKe
});
}
failure:^(NSError *error) {
completion(error);
// Ensure that we continue to work off the main thread.
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
completion(error);
});
}];
}

View File

@ -185,9 +185,11 @@ NSString *const kOWSBackup_KeychainService = @"kOWSBackup_KeychainService";
});
}
failure:^(NSError *error) {
// The manifest file is critical so any error downloading it is unrecoverable.
OWSProdLogAndFail(@"%@ Could not download manifest.", weakSelf.logTag);
failure(error);
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
// The manifest file is critical so any error downloading it is unrecoverable.
OWSProdLogAndFail(@"%@ Could not download manifest.", weakSelf.logTag);
failure(error);
});
}];
}

View File

@ -184,6 +184,9 @@
/* Error indicating the a backup import could not import the user's data. */
"BACKUP_IMPORT_ERROR_COULD_NOT_IMPORT" = "Backup could not be imported.";
/* Indicates that the backup import is checking for an existing backup. */
"BACKUP_IMPORT_PHASE_CHECK_BACKUP" = "Checking Backup State";
/* Indicates that the backup import is being configured. */
"BACKUP_IMPORT_PHASE_CONFIGURATION" = "Configuring Backup";