Fix incremental backup exports.

pull/1/head
Matthew Chen 7 years ago
parent d70aa4418f
commit fe8259bf0c

@ -73,6 +73,10 @@ NS_ASSUME_NONNULL_BEGIN
actionBlock:^{ actionBlock:^{
[DebugUIBackup clearBackupMetadataCache]; [DebugUIBackup clearBackupMetadataCache];
}]]; }]];
[items addObject:[OWSTableItem itemWithTitle:@"Log Backup Metadata Cache"
actionBlock:^{
[DebugUIBackup logBackupMetadataCache];
}]];
return [OWSTableSection sectionWithTitle:self.name items:items]; return [OWSTableSection sectionWithTitle:self.name items:items];
} }
@ -191,14 +195,14 @@ NS_ASSUME_NONNULL_BEGIN
+ (void)clearAllCloudKitRecords + (void)clearAllCloudKitRecords
{ {
OWSLogInfo(@"clearAllCloudKitRecords."); OWSLogInfo(@"");
[OWSBackup.sharedManager clearAllCloudKitRecords]; [OWSBackup.sharedManager clearAllCloudKitRecords];
} }
+ (void)clearBackupMetadataCache + (void)clearBackupMetadataCache
{ {
OWSLogInfo(@"ClearBackupMetadataCache."); OWSLogInfo(@"");
[OWSPrimaryStorage.sharedManager.newDatabaseConnection [OWSPrimaryStorage.sharedManager.newDatabaseConnection
readWriteWithBlock:^(YapDatabaseReadWriteTransaction *transaction) { readWriteWithBlock:^(YapDatabaseReadWriteTransaction *transaction) {
@ -206,6 +210,11 @@ NS_ASSUME_NONNULL_BEGIN
}]; }];
} }
+ (void)logBackupMetadataCache
{
[self.backup logBackupMetadataCache:OWSPrimaryStorage.sharedManager.newDatabaseConnection];
}
@end @end
NS_ASSUME_NONNULL_END NS_ASSUME_NONNULL_END

@ -32,6 +32,7 @@ NSError *OWSBackupErrorWithDescription(NSString *description);
@class OWSBackupIO; @class OWSBackupIO;
@class TSAttachmentPointer; @class TSAttachmentPointer;
@class TSThread; @class TSThread;
@class YapDatabaseConnection;
@interface OWSBackup : NSObject @interface OWSBackup : NSObject
@ -91,6 +92,8 @@ NSError *OWSBackupErrorWithDescription(NSString *description);
- (void)logBackupRecords; - (void)logBackupRecords;
- (void)clearAllCloudKitRecords; - (void)clearAllCloudKitRecords;
- (void)logBackupMetadataCache:(YapDatabaseConnection *)dbConnection;
#pragma mark - Lazy Restore #pragma mark - Lazy Restore
- (NSArray<NSString *> *)attachmentRecordNamesForLazyRestore; - (NSArray<NSString *> *)attachmentRecordNamesForLazyRestore;

@ -866,6 +866,27 @@ NSError *OWSBackupErrorWithDescription(NSString *description)
return [AnyPromise promiseWithValue:@(1)]; return [AnyPromise promiseWithValue:@(1)];
} }
- (void)logBackupMetadataCache:(YapDatabaseConnection *)dbConnection
{
OWSLogInfo(@"");
[dbConnection readWithBlock:^(YapDatabaseReadTransaction *transaction) {
[transaction enumerateKeysAndObjectsInCollection:[OWSBackupFragment collection]
usingBlock:^(NSString *key, OWSBackupFragment *fragment, BOOL *stop) {
OWSLogVerbose(@"fragment: %@, %@, %lu, %@, %@, %@, %@",
key,
fragment.recordName,
(unsigned long)fragment.encryptionKey.length,
fragment.relativeFilePath,
fragment.attachmentId,
fragment.downloadFilePath,
fragment.uncompressedDataLength);
}];
OWSLogVerbose(@"Number of fragments: %lu",
(unsigned long)[transaction numberOfKeysInCollection:[OWSBackupFragment collection]]);
}];
}
#pragma mark - Notifications #pragma mark - Notifications
- (void)postDidChangeNotification - (void)postDidChangeNotification

@ -315,6 +315,8 @@ NS_ASSUME_NONNULL_BEGIN
// 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) NSSet<NSString *> *lastValidRecordNames; @property (nonatomic, nullable) NSSet<NSString *> *lastValidRecordNames;
@property (nonatomic, nullable) YapDatabaseConnection *dbConnection;
@end @end
#pragma mark - #pragma mark -
@ -354,6 +356,8 @@ NS_ASSUME_NONNULL_BEGIN
[self updateProgressWithDescription:nil progress:nil]; [self updateProgressWithDescription:nil progress:nil];
self.dbConnection = self.primaryStorage.newDatabaseConnection;
[[self.backup ensureCloudKitAccess] [[self.backup ensureCloudKitAccess]
.thenInBackground(^{ .thenInBackground(^{
[self updateProgressWithDescription:NSLocalizedString(@"BACKUP_EXPORT_PHASE_CONFIGURATION", [self updateProgressWithDescription:NSLocalizedString(@"BACKUP_EXPORT_PHASE_CONFIGURATION",
@ -379,6 +383,8 @@ NS_ASSUME_NONNULL_BEGIN
return [self cleanUp]; return [self cleanUp];
}) })
.thenInBackground(^{ .thenInBackground(^{
[self.backup logBackupMetadataCache:self.dbConnection];
[self succeed]; [self succeed];
}) })
.catch(^(NSError *error) { .catch(^(NSError *error) {
@ -479,12 +485,6 @@ NS_ASSUME_NONNULL_BEGIN
@"Indicates that the database data is being exported.") @"Indicates that the database data is being exported.")
progress:nil]; progress:nil];
YapDatabaseConnection *_Nullable dbConnection = self.primaryStorage.newDatabaseConnection;
if (!dbConnection) {
OWSFailDebug(@"Could not create dbConnection.");
return NO;
}
OWSDBExportStream *exportStream = [[OWSDBExportStream alloc] initWithBackupIO:self.backupIO]; OWSDBExportStream *exportStream = [[OWSDBExportStream alloc] initWithBackupIO:self.backupIO];
__block BOOL aborted = NO; __block BOOL aborted = NO;
@ -548,7 +548,7 @@ NS_ASSUME_NONNULL_BEGIN
__block NSUInteger copiedMigrations = 0; __block NSUInteger copiedMigrations = 0;
__block NSUInteger copiedMisc = 0; __block NSUInteger copiedMisc = 0;
self.unsavedAttachmentExports = [NSMutableArray new]; self.unsavedAttachmentExports = [NSMutableArray new];
[dbConnection readWithBlock:^(YapDatabaseReadTransaction *transaction) { [self.dbConnection readWithBlock:^(YapDatabaseReadTransaction *transaction) {
copiedThreads = exportEntities(transaction, copiedThreads = exportEntities(transaction,
[TSThread collection], [TSThread collection],
[TSThread class], [TSThread class],
@ -570,10 +570,9 @@ NS_ASSUME_NONNULL_BEGIN
} }
TSAttachmentStream *attachmentStream = object; TSAttachmentStream *attachmentStream = object;
NSString *_Nullable filePath = attachmentStream.originalFilePath; NSString *_Nullable filePath = attachmentStream.originalFilePath;
if (!filePath) { if (!filePath || ![NSFileManager.defaultManager fileExistsAtPath:filePath]) {
OWSLogError(@"attachment is missing file."); OWSFailDebug(@"attachment is missing file.");
return NO; return NO;
OWSAssertDebug(attachmentStream.uniqueId.length > 0);
} }
// OWSAttachmentExport is used to lazily write an encrypted copy of the // OWSAttachmentExport is used to lazily write an encrypted copy of the
@ -866,13 +865,17 @@ NS_ASSUME_NONNULL_BEGIN
[self.savedAttachmentItems addObject:exportItem]; [self.savedAttachmentItems addObject:exportItem];
// Immediately save the record metadata to facilitate export resume. // Immediately save the record metadata to facilitate export resume.
OWSBackupFragment *backupFragment = [OWSBackupFragment new]; OWSBackupFragment *backupFragment = [[OWSBackupFragment alloc] initWithUniqueId:recordName];
backupFragment.recordName = recordName; backupFragment.recordName = recordName;
backupFragment.encryptionKey = exportItem.encryptedItem.encryptionKey; backupFragment.encryptionKey = exportItem.encryptedItem.encryptionKey;
backupFragment.relativeFilePath = attachmentExport.relativeFilePath; backupFragment.relativeFilePath = attachmentExport.relativeFilePath;
backupFragment.attachmentId = attachmentExport.attachmentId; backupFragment.attachmentId = attachmentExport.attachmentId;
backupFragment.uncompressedDataLength = exportItem.uncompressedDataLength; backupFragment.uncompressedDataLength = exportItem.uncompressedDataLength;
[backupFragment save]; [self.dbConnection readWriteWithBlock:^(YapDatabaseReadWriteTransaction *transaction) {
[backupFragment saveWithTransaction:transaction];
}];
[self.backup logBackupMetadataCache:self.dbConnection];
OWSLogVerbose( OWSLogVerbose(
@"saved attachment: %@ as %@", attachmentExport.attachmentFilePath, attachmentExport.relativeFilePath); @"saved attachment: %@ as %@", attachmentExport.attachmentFilePath, attachmentExport.relativeFilePath);
@ -1064,9 +1067,11 @@ NS_ASSUME_NONNULL_BEGIN
// After every successful backup export, we can (and should) cull metadata // After every successful backup export, we can (and should) cull metadata
// for any backup fragment (i.e. CloudKit record) that wasn't involved in // for any backup fragment (i.e. CloudKit record) that wasn't involved in
// the latest backup export. // the latest backup export.
[self.primaryStorage.newDatabaseConnection readWriteWithBlock:^(YapDatabaseReadWriteTransaction *transaction) { [self.dbConnection readWriteWithBlock:^(YapDatabaseReadWriteTransaction *transaction) {
NSArray<NSString *> *allRecordNames = [transaction allKeysInCollection:[OWSBackupFragment collection]];
NSMutableSet<NSString *> *obsoleteRecordNames = [NSMutableSet new]; NSMutableSet<NSString *> *obsoleteRecordNames = [NSMutableSet new];
[obsoleteRecordNames addObjectsFromArray:[transaction allKeysInCollection:[OWSBackupFragment collection]]]; [obsoleteRecordNames addObjectsFromArray:allRecordNames];
[obsoleteRecordNames minusSet:activeRecordNames]; [obsoleteRecordNames minusSet:activeRecordNames];
[transaction removeObjectsForKeys:obsoleteRecordNames.allObjects inCollection:[OWSBackupFragment collection]]; [transaction removeObjectsForKeys:obsoleteRecordNames.allObjects inCollection:[OWSBackupFragment collection]];

@ -76,6 +76,10 @@ static const compression_algorithm SignalCompressionAlgorithm = COMPRESSION_LZMA
OWSAssertDebug(encryptionKey.length > 0); OWSAssertDebug(encryptionKey.length > 0);
@autoreleasepool { @autoreleasepool {
if (![[NSFileManager defaultManager] fileExistsAtPath:srcFilePath]) {
OWSFailDebug(@"Missing source file.");
return nil;
}
// TODO: Encrypt the file without loading it into memory. // TODO: Encrypt the file without loading it into memory.
NSData *_Nullable srcData = [NSData dataWithContentsOfFile:srcFilePath]; NSData *_Nullable srcData = [NSData dataWithContentsOfFile:srcFilePath];

@ -29,6 +29,8 @@ NSString *const kOWSBackup_ImportDatabaseKeySpec = @"kOWSBackup_ImportDatabaseKe
@property (nonatomic) OWSBackupManifestContents *manifest; @property (nonatomic) OWSBackupManifestContents *manifest;
@property (nonatomic, nullable) YapDatabaseConnection *dbConnection;
@end @end
#pragma mark - #pragma mark -
@ -92,6 +94,8 @@ NSString *const kOWSBackup_ImportDatabaseKeySpec = @"kOWSBackup_ImportDatabaseKe
self.backgroundTask = [OWSBackgroundTask backgroundTaskWithLabelStr:__PRETTY_FUNCTION__]; self.backgroundTask = [OWSBackgroundTask backgroundTaskWithLabelStr:__PRETTY_FUNCTION__];
self.dbConnection = self.primaryStorage.newDatabaseConnection;
[self updateProgressWithDescription:nil progress:nil]; [self updateProgressWithDescription:nil progress:nil];
[[self.backup ensureCloudKitAccess] [[self.backup ensureCloudKitAccess]
@ -150,7 +154,7 @@ NSString *const kOWSBackup_ImportDatabaseKeySpec = @"kOWSBackup_ImportDatabaseKe
[allItems addObjectsFromArray:self.attachmentsItems]; [allItems addObjectsFromArray:self.attachmentsItems];
// Record metadata for all items, so that we can re-use them in incremental backups after the restore. // Record metadata for all items, so that we can re-use them in incremental backups after the restore.
[self.primaryStorage.newDatabaseConnection readWriteWithBlock:^(YapDatabaseReadWriteTransaction *transaction) { [self.dbConnection readWriteWithBlock:^(YapDatabaseReadWriteTransaction *transaction) {
for (OWSBackupFragment *item in allItems) { for (OWSBackupFragment *item in allItems) {
[item saveWithTransaction:transaction]; [item saveWithTransaction:transaction];
} }
@ -324,8 +328,7 @@ NSString *const kOWSBackup_ImportDatabaseKeySpec = @"kOWSBackup_ImportDatabaseKe
} }
__block NSUInteger count = 0; __block NSUInteger count = 0;
YapDatabaseConnection *dbConnection = self.primaryStorage.newDatabaseConnection; [self.dbConnection readWriteWithBlock:^(YapDatabaseReadWriteTransaction *transaction) {
[dbConnection readWriteWithBlock:^(YapDatabaseReadWriteTransaction *transaction) {
for (OWSBackupFragment *item in self.attachmentsItems) { for (OWSBackupFragment *item in self.attachmentsItems) {
if (self.isComplete) { if (self.isComplete) {
return; return;
@ -379,12 +382,6 @@ NSString *const kOWSBackup_ImportDatabaseKeySpec = @"kOWSBackup_ImportDatabaseKe
return [AnyPromise promiseWithValue:OWSBackupErrorWithDescription(@"Backup import no longer active.")]; return [AnyPromise promiseWithValue:OWSBackupErrorWithDescription(@"Backup import no longer active.")];
} }
YapDatabaseConnection *_Nullable dbConnection = self.primaryStorage.newDatabaseConnection;
if (!dbConnection) {
OWSFailDebug(@"Could not create dbConnection.");
return [AnyPromise promiseWithValue:OWSBackupErrorWithDescription(@"Could not create dbConnection.")];
}
// Order matters here. // Order matters here.
NSArray<NSString *> *collectionsToRestore = @[ NSArray<NSString *> *collectionsToRestore = @[
[TSThread collection], [TSThread collection],
@ -397,7 +394,7 @@ NSString *const kOWSBackup_ImportDatabaseKeySpec = @"kOWSBackup_ImportDatabaseKe
NSMutableDictionary<NSString *, NSNumber *> *restoredEntityCounts = [NSMutableDictionary new]; NSMutableDictionary<NSString *, NSNumber *> *restoredEntityCounts = [NSMutableDictionary new];
__block unsigned long long copiedEntities = 0; __block unsigned long long copiedEntities = 0;
__block BOOL aborted = NO; __block BOOL aborted = NO;
[dbConnection readWriteWithBlock:^(YapDatabaseReadWriteTransaction *transaction) { [self.dbConnection readWriteWithBlock:^(YapDatabaseReadWriteTransaction *transaction) {
for (NSString *collection in collectionsToRestore) { for (NSString *collection in collectionsToRestore) {
if ([collection isEqualToString:[OWSDatabaseMigration collection]]) { if ([collection isEqualToString:[OWSDatabaseMigration collection]]) {
// It's okay if there are existing migrations; we'll clear those // It's okay if there are existing migrations; we'll clear those

@ -300,7 +300,7 @@ NSString *const kOWSBackup_KeychainService = @"kOWSBackup_KeychainService";
return nil; return nil;
} }
OWSBackupFragment *item = [OWSBackupFragment new]; OWSBackupFragment *item = [[OWSBackupFragment alloc] initWithUniqueId:recordName];
item.recordName = recordName; item.recordName = recordName;
item.encryptionKey = encryptionKey; item.encryptionKey = encryptionKey;
item.relativeFilePath = relativeFilePath; item.relativeFilePath = relativeFilePath;

@ -186,7 +186,6 @@ typedef void (^AttachmentDownloadFailure)(NSError *error);
failure:(void (^)(NSError *error))failureHandler failure:(void (^)(NSError *error))failureHandler
{ {
OWSAssertDebug(attachmentStreamsParam); OWSAssertDebug(attachmentStreamsParam);
OWSAssertDebug(attachmentPointers.count > 0);
// To avoid deadlocks, synchronize on self outside of the transaction. // To avoid deadlocks, synchronize on self outside of the transaction.
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{

@ -183,7 +183,10 @@ NS_ASSUME_NONNULL_BEGIN
if (!self.lazyRestoreFragmentId) { if (!self.lazyRestoreFragmentId) {
return nil; return nil;
} }
return [OWSBackupFragment fetchObjectWithUniqueID:self.lazyRestoreFragmentId]; OWSBackupFragment *_Nullable backupFragment =
[OWSBackupFragment fetchObjectWithUniqueID:self.lazyRestoreFragmentId];
OWSAssertDebug(backupFragment);
return backupFragment;
} }
#pragma mark - Update With... Methods #pragma mark - Update With... Methods

@ -37,6 +37,9 @@ NS_ASSUME_NONNULL_BEGIN
// This property is only set if the manifest item is compressed. // This property is only set if the manifest item is compressed.
@property (nonatomic, nullable) NSNumber *uncompressedDataLength; @property (nonatomic, nullable) NSNumber *uncompressedDataLength;
- (instancetype)init NS_UNAVAILABLE;
- (instancetype)initWithUniqueId:(NSString *)uniqueId NS_DESIGNATED_INITIALIZER;
@end @end
NS_ASSUME_NONNULL_END NS_ASSUME_NONNULL_END

@ -8,14 +8,14 @@ NS_ASSUME_NONNULL_BEGIN
@implementation OWSBackupFragment @implementation OWSBackupFragment
- (void)saveWithTransaction:(YapDatabaseReadWriteTransaction *)transaction - (instancetype)initWithUniqueId:(NSString *)uniqueId
{ {
OWSAssertDebug(self.recordName.length > 0); self = [super initWithUniqueId:uniqueId];
if (!self) {
if (!self.uniqueId) { return self;
self.uniqueId = self.recordName;
} }
[super saveWithTransaction:transaction];
return self;
} }
@end @end

Loading…
Cancel
Save