|
|
@ -295,8 +295,7 @@ NS_ASSUME_NONNULL_BEGIN
|
|
|
|
@property (nonatomic, nullable) OWSBackupExportItem *manifestItem;
|
|
|
|
@property (nonatomic, nullable) OWSBackupExportItem *manifestItem;
|
|
|
|
|
|
|
|
|
|
|
|
// If we are replacing an existing backup, we use some of its contents for continuity.
|
|
|
|
// 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 *> *lastValidRecordNames;
|
|
|
|
@property (nonatomic, nullable) NSSet<NSString *> *lastRecordNames;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@end
|
|
|
|
@end
|
|
|
|
|
|
|
|
|
|
|
@ -346,7 +345,7 @@ NS_ASSUME_NONNULL_BEGIN
|
|
|
|
if (self.isComplete) {
|
|
|
|
if (self.isComplete) {
|
|
|
|
return;
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
[self tryToFetchManifestWithCompletion:^(BOOL tryToFetchManifestSuccess) {
|
|
|
|
[self fetchAllRecordsWithCompletion:^(BOOL tryToFetchManifestSuccess) {
|
|
|
|
if (!tryToFetchManifestSuccess) {
|
|
|
|
if (!tryToFetchManifestSuccess) {
|
|
|
|
[self failWithErrorDescription:
|
|
|
|
[self failWithErrorDescription:
|
|
|
|
NSLocalizedString(@"BACKUP_EXPORT_ERROR_COULD_NOT_EXPORT",
|
|
|
|
NSLocalizedString(@"BACKUP_EXPORT_ERROR_COULD_NOT_EXPORT",
|
|
|
@ -374,7 +373,7 @@ NS_ASSUME_NONNULL_BEGIN
|
|
|
|
[weakSelf failWithError:saveError];
|
|
|
|
[weakSelf failWithError:saveError];
|
|
|
|
return;
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
[self cleanUpCloudWithCompletion:^(NSError *_Nullable cleanUpError) {
|
|
|
|
[self cleanUpWithCompletion:^(NSError *_Nullable cleanUpError) {
|
|
|
|
if (cleanUpError) {
|
|
|
|
if (cleanUpError) {
|
|
|
|
[weakSelf failWithError:cleanUpError];
|
|
|
|
[weakSelf failWithError:cleanUpError];
|
|
|
|
return;
|
|
|
|
return;
|
|
|
@ -422,69 +421,6 @@ NS_ASSUME_NONNULL_BEGIN
|
|
|
|
}];
|
|
|
|
}];
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
- (void)tryToFetchManifestWithCompletion:(OWSBackupJobBoolCompletion)completion
|
|
|
|
|
|
|
|
{
|
|
|
|
|
|
|
|
OWSAssert(completion);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if (self.isComplete) {
|
|
|
|
|
|
|
|
return;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
DDLogVerbose(@"%@ %s", self.logTag, __PRETTY_FUNCTION__);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
[self updateProgressWithDescription:NSLocalizedString(@"BACKUP_IMPORT_PHASE_CHECK_BACKUP",
|
|
|
|
|
|
|
|
@"Indicates that the backup import is checking for an existing backup.")
|
|
|
|
|
|
|
|
progress:nil];
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
__weak OWSBackupExportJob *weakSelf = self;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
[OWSBackupAPI checkForManifestInCloudWithSuccess:^(BOOL value) {
|
|
|
|
|
|
|
|
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
|
|
|
|
|
|
|
|
if (value) {
|
|
|
|
|
|
|
|
[weakSelf fetchManifestWithCompletion:completion];
|
|
|
|
|
|
|
|
} else {
|
|
|
|
|
|
|
|
// There is no existing manifest; continue.
|
|
|
|
|
|
|
|
completion(YES);
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
failure:^(NSError *error) {
|
|
|
|
|
|
|
|
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
|
|
|
|
|
|
|
|
completion(NO);
|
|
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
}];
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
- (void)fetchManifestWithCompletion:(OWSBackupJobBoolCompletion)completion
|
|
|
|
|
|
|
|
{
|
|
|
|
|
|
|
|
OWSAssert(completion);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if (self.isComplete) {
|
|
|
|
|
|
|
|
return;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
DDLogVerbose(@"%@ %s", self.logTag, __PRETTY_FUNCTION__);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
__weak OWSBackupExportJob *weakSelf = self;
|
|
|
|
|
|
|
|
[weakSelf downloadAndProcessManifestWithSuccess:^(OWSBackupManifestContents *manifest) {
|
|
|
|
|
|
|
|
OWSBackupExportJob *strongSelf = weakSelf;
|
|
|
|
|
|
|
|
if (!strongSelf) {
|
|
|
|
|
|
|
|
return;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
if (strongSelf.isComplete) {
|
|
|
|
|
|
|
|
return;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
OWSCAssert(manifest.databaseItems.count > 0);
|
|
|
|
|
|
|
|
OWSCAssert(manifest.attachmentsItems);
|
|
|
|
|
|
|
|
[strongSelf processLastManifest:manifest];
|
|
|
|
|
|
|
|
[strongSelf fetchAllRecordsWithCompletion:completion];
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
failure:^(NSError *manifestError) {
|
|
|
|
|
|
|
|
completion(NO);
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
backupIO:self.backupIO];
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
- (void)fetchAllRecordsWithCompletion:(OWSBackupJobBoolCompletion)completion
|
|
|
|
- (void)fetchAllRecordsWithCompletion:(OWSBackupJobBoolCompletion)completion
|
|
|
|
{
|
|
|
|
{
|
|
|
|
OWSAssert(completion);
|
|
|
|
OWSAssert(completion);
|
|
|
@ -505,7 +441,7 @@ NS_ASSUME_NONNULL_BEGIN
|
|
|
|
if (strongSelf.isComplete) {
|
|
|
|
if (strongSelf.isComplete) {
|
|
|
|
return;
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
strongSelf.lastRecordNames = [NSSet setWithArray:recordNames];
|
|
|
|
strongSelf.lastValidRecordNames = [NSSet setWithArray:recordNames];
|
|
|
|
completion(YES);
|
|
|
|
completion(YES);
|
|
|
|
});
|
|
|
|
});
|
|
|
|
}
|
|
|
|
}
|
|
|
@ -516,17 +452,6 @@ NS_ASSUME_NONNULL_BEGIN
|
|
|
|
}];
|
|
|
|
}];
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
- (void)processLastManifest:(OWSBackupManifestContents *)manifest
|
|
|
|
|
|
|
|
{
|
|
|
|
|
|
|
|
OWSAssert(manifest);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
NSMutableDictionary<NSString *, OWSBackupManifestItem *> *lastManifestItemMap = [NSMutableDictionary new];
|
|
|
|
|
|
|
|
for (OWSBackupManifestItem *manifestItem in manifest.attachmentsItems) {
|
|
|
|
|
|
|
|
lastManifestItemMap[manifestItem.recordName] = manifestItem;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
self.lastManifestItemMap = [lastManifestItemMap copy];
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
- (BOOL)exportDatabase
|
|
|
|
- (BOOL)exportDatabase
|
|
|
|
{
|
|
|
|
{
|
|
|
|
OWSAssert(self.backupIO);
|
|
|
|
OWSAssert(self.backupIO);
|
|
|
@ -806,7 +731,7 @@ NS_ASSUME_NONNULL_BEGIN
|
|
|
|
OWSAttachmentExport *attachmentExport = self.unsavedAttachmentExports.lastObject;
|
|
|
|
OWSAttachmentExport *attachmentExport = self.unsavedAttachmentExports.lastObject;
|
|
|
|
[self.unsavedAttachmentExports removeLastObject];
|
|
|
|
[self.unsavedAttachmentExports removeLastObject];
|
|
|
|
|
|
|
|
|
|
|
|
if (self.lastManifestItemMap && self.lastRecordNames) {
|
|
|
|
if (self.lastValidRecordNames) {
|
|
|
|
// Wherever possible, we do incremental backups and re-use fragments of the last backup.
|
|
|
|
// Wherever possible, we do incremental backups and re-use fragments of the last backup.
|
|
|
|
// Recycling fragments doesn't just reduce redundant network activity,
|
|
|
|
// Recycling fragments doesn't just reduce redundant network activity,
|
|
|
|
// it allows us to skip the local export work, i.e. encryption.
|
|
|
|
// it allows us to skip the local export work, i.e. encryption.
|
|
|
@ -818,8 +743,9 @@ NS_ASSUME_NONNULL_BEGIN
|
|
|
|
// this record's metadata.
|
|
|
|
// this record's metadata.
|
|
|
|
// * That this record does in fact exist in our CloudKit database.
|
|
|
|
// * That this record does in fact exist in our CloudKit database.
|
|
|
|
NSString *lastRecordName = [OWSBackupAPI recordNameForPersistentFileWithFileId:attachmentExport.attachmentId];
|
|
|
|
NSString *lastRecordName = [OWSBackupAPI recordNameForPersistentFileWithFileId:attachmentExport.attachmentId];
|
|
|
|
OWSBackupManifestItem *_Nullable lastManifestItem = self.lastManifestItemMap[lastRecordName];
|
|
|
|
OWSBackupManifestItem *_Nullable lastManifestItem =
|
|
|
|
if (lastManifestItem && [self.lastRecordNames containsObject:lastRecordName]) {
|
|
|
|
[OWSBackupManifestItem fetchObjectWithUniqueID:lastRecordName];
|
|
|
|
|
|
|
|
if (lastManifestItem && [self.lastValidRecordNames containsObject:lastRecordName]) {
|
|
|
|
OWSAssert(lastManifestItem.encryptionKey.length > 0);
|
|
|
|
OWSAssert(lastManifestItem.encryptionKey.length > 0);
|
|
|
|
OWSAssert(lastManifestItem.relativeFilePath.length > 0);
|
|
|
|
OWSAssert(lastManifestItem.relativeFilePath.length > 0);
|
|
|
|
|
|
|
|
|
|
|
@ -887,6 +813,14 @@ NS_ASSUME_NONNULL_BEGIN
|
|
|
|
exportItem.attachmentExport = attachmentExport;
|
|
|
|
exportItem.attachmentExport = attachmentExport;
|
|
|
|
[strongSelf.savedAttachmentItems addObject:exportItem];
|
|
|
|
[strongSelf.savedAttachmentItems addObject:exportItem];
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// Immediately save the record metadata to facilitate export resume.
|
|
|
|
|
|
|
|
OWSBackupManifestItem *backupRestoreMetadata = [OWSBackupManifestItem new];
|
|
|
|
|
|
|
|
backupRestoreMetadata.recordName = recordName;
|
|
|
|
|
|
|
|
backupRestoreMetadata.encryptionKey = exportItem.encryptedItem.encryptionKey;
|
|
|
|
|
|
|
|
backupRestoreMetadata.relativeFilePath = attachmentExport.relativeFilePath;
|
|
|
|
|
|
|
|
backupRestoreMetadata.uncompressedDataLength = exportItem.uncompressedDataLength;
|
|
|
|
|
|
|
|
[backupRestoreMetadata save];
|
|
|
|
|
|
|
|
|
|
|
|
DDLogVerbose(@"%@ saved attachment: %@ as %@",
|
|
|
|
DDLogVerbose(@"%@ saved attachment: %@ as %@",
|
|
|
|
self.logTag,
|
|
|
|
self.logTag,
|
|
|
|
attachmentExport.attachmentFilePath,
|
|
|
|
attachmentExport.attachmentFilePath,
|
|
|
@ -999,10 +933,15 @@ NS_ASSUME_NONNULL_BEGIN
|
|
|
|
return result;
|
|
|
|
return result;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
- (void)cleanUpCloudWithCompletion:(OWSBackupJobCompletion)completion
|
|
|
|
- (void)cleanUpWithCompletion:(OWSBackupJobCompletion)completion
|
|
|
|
{
|
|
|
|
{
|
|
|
|
OWSAssert(completion);
|
|
|
|
OWSAssert(completion);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if (self.isComplete) {
|
|
|
|
|
|
|
|
// Job was aborted.
|
|
|
|
|
|
|
|
return completion(nil);
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
DDLogVerbose(@"%@ %s", self.logTag, __PRETTY_FUNCTION__);
|
|
|
|
DDLogVerbose(@"%@ %s", self.logTag, __PRETTY_FUNCTION__);
|
|
|
|
|
|
|
|
|
|
|
|
[self updateProgressWithDescription:NSLocalizedString(@"BACKUP_EXPORT_PHASE_CLEAN_UP",
|
|
|
|
[self updateProgressWithDescription:NSLocalizedString(@"BACKUP_EXPORT_PHASE_CLEAN_UP",
|
|
|
@ -1034,6 +973,44 @@ NS_ASSUME_NONNULL_BEGIN
|
|
|
|
NSArray<NSString *> *restoringRecordNames = [OWSBackup.sharedManager attachmentRecordNamesForLazyRestore];
|
|
|
|
NSArray<NSString *> *restoringRecordNames = [OWSBackup.sharedManager attachmentRecordNamesForLazyRestore];
|
|
|
|
[activeRecordNames addObjectsFromArray:restoringRecordNames];
|
|
|
|
[activeRecordNames addObjectsFromArray:restoringRecordNames];
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
[self cleanUpMetadataCacheWithActiveRecordNames:activeRecordNames];
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
[self cleanUpCloudWithActiveRecordNames:activeRecordNames completion:completion];
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
- (void)cleanUpMetadataCacheWithActiveRecordNames:(NSSet<NSString *> *)activeRecordNames
|
|
|
|
|
|
|
|
{
|
|
|
|
|
|
|
|
OWSAssert(activeRecordNames.count > 0);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if (self.isComplete) {
|
|
|
|
|
|
|
|
// Job was aborted.
|
|
|
|
|
|
|
|
return;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// After every successful backup export, we can (and should) cull metadata
|
|
|
|
|
|
|
|
// for any backup fragment (i.e. CloudKit record) that wasn't involved in
|
|
|
|
|
|
|
|
// the latest backup export.
|
|
|
|
|
|
|
|
[self.primaryStorage.newDatabaseConnection readWriteWithBlock:^(YapDatabaseReadWriteTransaction *transaction) {
|
|
|
|
|
|
|
|
NSMutableSet<NSString *> *obsoleteRecordNames = [NSMutableSet new];
|
|
|
|
|
|
|
|
[obsoleteRecordNames addObjectsFromArray:[transaction allKeysInCollection:[OWSBackupManifestItem collection]]];
|
|
|
|
|
|
|
|
[obsoleteRecordNames minusSet:activeRecordNames];
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
[transaction removeObjectsForKeys:obsoleteRecordNames.allObjects
|
|
|
|
|
|
|
|
inCollection:[OWSBackupManifestItem collection]];
|
|
|
|
|
|
|
|
}];
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
- (void)cleanUpCloudWithActiveRecordNames:(NSSet<NSString *> *)activeRecordNames
|
|
|
|
|
|
|
|
completion:(OWSBackupJobCompletion)completion
|
|
|
|
|
|
|
|
{
|
|
|
|
|
|
|
|
OWSAssert(activeRecordNames.count > 0);
|
|
|
|
|
|
|
|
OWSAssert(completion);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if (self.isComplete) {
|
|
|
|
|
|
|
|
// Job was aborted.
|
|
|
|
|
|
|
|
return completion(nil);
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
__weak OWSBackupExportJob *weakSelf = self;
|
|
|
|
__weak OWSBackupExportJob *weakSelf = self;
|
|
|
|
[OWSBackupAPI fetchAllRecordNamesWithSuccess:^(NSArray<NSString *> *recordNames) {
|
|
|
|
[OWSBackupAPI fetchAllRecordNamesWithSuccess:^(NSArray<NSString *> *recordNames) {
|
|
|
|
// Ensure that we continue to work off the main thread.
|
|
|
|
// Ensure that we continue to work off the main thread.
|
|
|
|