From 439d7e62e6fc4750caf5a15aba2c7b5631735c88 Mon Sep 17 00:00:00 2001 From: Matthew Chen Date: Sat, 17 Mar 2018 17:44:54 -0300 Subject: [PATCH] Recycle backup fragments. --- Signal/src/util/OWSBackupExportJob.m | 75 +++++++++++++++---- Signal/src/util/OWSBackupImportJob.m | 17 +++-- Signal/src/util/OWSBackupJob.m | 8 +- .../translations/en.lproj/Localizable.strings | 3 + 4 files changed, 81 insertions(+), 22 deletions(-) diff --git a/Signal/src/util/OWSBackupExportJob.m b/Signal/src/util/OWSBackupExportJob.m index 3905ca788..35abe3489 100644 --- a/Signal/src/util/OWSBackupExportJob.m +++ b/Signal/src/util/OWSBackupExportJob.m @@ -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 *lastManifestItemMap; +@property (nonatomic, nullable) NSSet *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 *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); + }); }]; } diff --git a/Signal/src/util/OWSBackupImportJob.m b/Signal/src/util/OWSBackupImportJob.m index 965a2021a..98a3afbe9 100644 --- a/Signal/src/util/OWSBackupImportJob.m +++ b/Signal/src/util/OWSBackupImportJob.m @@ -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); + }); }]; } diff --git a/Signal/src/util/OWSBackupJob.m b/Signal/src/util/OWSBackupJob.m index e1fbbba02..fef0b393e 100644 --- a/Signal/src/util/OWSBackupJob.m +++ b/Signal/src/util/OWSBackupJob.m @@ -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); + }); }]; } diff --git a/Signal/translations/en.lproj/Localizable.strings b/Signal/translations/en.lproj/Localizable.strings index ac9081e6e..8c60adeb6 100644 --- a/Signal/translations/en.lproj/Localizable.strings +++ b/Signal/translations/en.lproj/Localizable.strings @@ -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";