Fix bugs in new db representation, add batch record deletion, improve memory management.

pull/1/head
Matthew Chen 7 years ago
parent fed524ba16
commit 2ebd8668b4

@ -90,11 +90,6 @@
self.title = NSLocalizedString(@"SETTINGS_NAV_BAR_TITLE", @"Title for settings activity"); self.title = NSLocalizedString(@"SETTINGS_NAV_BAR_TITLE", @"Title for settings activity");
[self updateTableContents]; [self updateTableContents];
dispatch_async(dispatch_get_main_queue(), ^{
[self showBackup];
// [self showDebugUI];
});
} }
- (void)viewWillAppear:(BOOL)animated - (void)viewWillAppear:(BOOL)animated

@ -426,10 +426,6 @@ typedef NS_ENUM(NSInteger, CellState) { kArchiveState, kInboxState };
} }
[self checkIfEmptyView]; [self checkIfEmptyView];
dispatch_async(dispatch_get_main_queue(), ^{
[self settingsButtonPressed:nil];
});
} }
- (void)viewWillDisappear:(BOOL)animated - (void)viewWillDisappear:(BOOL)animated

@ -225,28 +225,30 @@ import CloudKit
// MARK: - Delete // MARK: - Delete
@objc @objc
public class func deleteRecordFromCloud(recordName: String, public class func deleteRecordsFromCloud(recordNames: [String],
success: @escaping (()) -> Void, success: @escaping (()) -> Void,
failure: @escaping (Error) -> Void) { failure: @escaping (Error) -> Void) {
deleteRecordFromCloud(recordName: recordName, deleteRecordsFromCloud(recordNames: recordNames,
remainingRetries: maxRetries, remainingRetries: maxRetries,
success: success, success: success,
failure: failure) failure: failure)
} }
private class func deleteRecordFromCloud(recordName: String, private class func deleteRecordsFromCloud(recordNames: [String],
remainingRetries: Int, remainingRetries: Int,
success: @escaping (()) -> Void, success: @escaping (()) -> Void,
failure: @escaping (Error) -> Void) { failure: @escaping (Error) -> Void) {
let recordID = CKRecordID(recordName: recordName) var recordIDs = [CKRecordID]()
for recordName in recordNames {
database().delete(withRecordID: recordID) { recordIDs.append(CKRecordID(recordName: recordName))
(_, error) in }
let deleteOperation = CKModifyRecordsOperation(recordsToSave: nil, recordIDsToDelete: recordIDs)
deleteOperation.modifyRecordsCompletionBlock = { (records, recordIds, error) in
let outcome = outcomeForCloudKitError(error: error, let outcome = outcomeForCloudKitError(error: error,
remainingRetries: remainingRetries, remainingRetries: remainingRetries,
label: "Delete Record") label: "Delete Records")
switch outcome { switch outcome {
case .success: case .success:
success() success()
@ -254,23 +256,24 @@ import CloudKit
failure(outcomeError) failure(outcomeError)
case .failureRetryAfterDelay(let retryDelay): case .failureRetryAfterDelay(let retryDelay):
DispatchQueue.global().asyncAfter(deadline: DispatchTime.now() + retryDelay, execute: { DispatchQueue.global().asyncAfter(deadline: DispatchTime.now() + retryDelay, execute: {
deleteRecordFromCloud(recordName: recordName, deleteRecordsFromCloud(recordNames: recordNames,
remainingRetries: remainingRetries - 1, remainingRetries: remainingRetries - 1,
success: success, success: success,
failure: failure) failure: failure)
}) })
case .failureRetryWithoutDelay: case .failureRetryWithoutDelay:
DispatchQueue.global().async { DispatchQueue.global().async {
deleteRecordFromCloud(recordName: recordName, deleteRecordsFromCloud(recordNames: recordNames,
remainingRetries: remainingRetries - 1, remainingRetries: remainingRetries - 1,
success: success, success: success,
failure: failure) failure: failure)
} }
case .unknownItem: case .unknownItem:
owsFail("\(self.logTag) unexpected CloudKit response.") owsFail("\(self.logTag) unexpected CloudKit response.")
failure(invalidServiceResponseError()) failure(invalidServiceResponseError())
} }
} }
database().add(deleteOperation)
} }
// MARK: - Exists? // MARK: - Exists?

@ -124,7 +124,9 @@ NS_ASSUME_NONNULL_BEGIN
static const int kMaxDBSnapshotSize = 1000; static const int kMaxDBSnapshotSize = 1000;
if (self.cachedItemCount > kMaxDBSnapshotSize) { if (self.cachedItemCount > kMaxDBSnapshotSize) {
return [self flush]; @autoreleasepool {
return [self flush];
}
} }
return YES; return YES;
@ -137,26 +139,30 @@ NS_ASSUME_NONNULL_BEGIN
return YES; return YES;
} }
NSData *_Nullable uncompressedData = [self.backupSnapshotBuilder build].data; // Try to release allocated buffers ASAP.
NSUInteger uncompressedDataLength = uncompressedData.length; @autoreleasepool {
self.backupSnapshotBuilder = nil;
self.cachedItemCount = 0;
if (!uncompressedData) {
OWSProdLogAndFail(@"%@ couldn't convert database snapshot to data.", self.logTag);
return NO;
}
NSData *compressedData = [self.backupIO compressData:uncompressedData]; NSData *_Nullable uncompressedData = [self.backupSnapshotBuilder build].data;
NSUInteger uncompressedDataLength = uncompressedData.length;
self.backupSnapshotBuilder = nil;
self.cachedItemCount = 0;
if (!uncompressedData) {
OWSProdLogAndFail(@"%@ couldn't convert database snapshot to data.", self.logTag);
return NO;
}
OWSBackupEncryptedItem *_Nullable encryptedItem = [self.backupIO encryptDataAsTempFile:compressedData]; NSData *compressedData = [self.backupIO compressData:uncompressedData];
if (!encryptedItem) {
OWSProdLogAndFail(@"%@ couldn't encrypt database snapshot.", self.logTag);
return NO;
}
OWSBackupExportItem *exportItem = [[OWSBackupExportItem alloc] initWithEncryptedItem:encryptedItem]; OWSBackupEncryptedItem *_Nullable encryptedItem = [self.backupIO encryptDataAsTempFile:compressedData];
exportItem.uncompressedDataLength = @(uncompressedDataLength); if (!encryptedItem) {
[self.exportItems addObject:exportItem]; OWSProdLogAndFail(@"%@ couldn't encrypt database snapshot.", self.logTag);
return NO;
}
OWSBackupExportItem *exportItem = [[OWSBackupExportItem alloc] initWithEncryptedItem:encryptedItem];
exportItem.uncompressedDataLength = @(uncompressedDataLength);
[self.exportItems addObject:exportItem];
}
return YES; return YES;
} }
@ -500,9 +506,11 @@ NS_ASSUME_NONNULL_BEGIN
return NO; return NO;
} }
if (![exportStream flush]) { @autoreleasepool {
OWSProdLogAndFail(@"%@ Could not flush database snapshots.", self.logTag); if (![exportStream flush]) {
return NO; OWSProdLogAndFail(@"%@ Could not flush database snapshots.", self.logTag);
return NO;
}
} }
self.unsavedDatabaseItems = [exportStream.exportItems mutableCopy]; self.unsavedDatabaseItems = [exportStream.exportItems mutableCopy];
@ -629,15 +637,17 @@ NS_ASSUME_NONNULL_BEGIN
OWSAttachmentExport *attachmentExport = self.unsavedAttachmentExports.lastObject; OWSAttachmentExport *attachmentExport = self.unsavedAttachmentExports.lastObject;
[self.unsavedAttachmentExports removeLastObject]; [self.unsavedAttachmentExports removeLastObject];
// OWSAttachmentExport is used to lazily write an encrypted copy of the @autoreleasepool {
// attachment to disk. // OWSAttachmentExport is used to lazily write an encrypted copy of the
if (![attachmentExport prepareForUpload]) { // attachment to disk.
// Attachment files are non-critical so any error uploading them is recoverable. if (![attachmentExport prepareForUpload]) {
[weakSelf saveNextFileToCloud:completion]; // Attachment files are non-critical so any error uploading them is recoverable.
return YES; [weakSelf saveNextFileToCloud:completion];
return YES;
}
OWSAssert(attachmentExport.relativeFilePath.length > 0);
OWSAssert(attachmentExport.encryptedItem);
} }
OWSAssert(attachmentExport.relativeFilePath.length > 0);
OWSAssert(attachmentExport.encryptedItem);
[OWSBackupAPI savePersistentFileOnceToCloudWithFileId:attachmentExport.attachmentId [OWSBackupAPI savePersistentFileOnceToCloudWithFileId:attachmentExport.attachmentId
fileUrlBlock:^{ fileUrlBlock:^{
@ -856,16 +866,21 @@ NS_ASSUME_NONNULL_BEGIN
@"Indicates that the cloud is being cleaned up.") @"Indicates that the cloud is being cleaned up.")
progress:@(progress)]; progress:@(progress)];
NSString *recordName = obsoleteRecordNames.lastObject; static const NSUInteger kMaxBatchSize = 100;
[obsoleteRecordNames removeLastObject]; NSMutableArray<NSString *> *batchRecordNames = [NSMutableArray new];
while (obsoleteRecordNames.count > 0 && batchRecordNames.count < kMaxBatchSize) {
NSString *recordName = obsoleteRecordNames.lastObject;
[obsoleteRecordNames removeLastObject];
[batchRecordNames addObject:recordName];
}
__weak OWSBackupExportJob *weakSelf = self; __weak OWSBackupExportJob *weakSelf = self;
[OWSBackupAPI deleteRecordFromCloudWithRecordName:recordName [OWSBackupAPI deleteRecordsFromCloudWithRecordNames:batchRecordNames
success:^{ success:^{
// Ensure that we continue to work off the main thread. // Ensure that we continue to work off the main thread.
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
[weakSelf deleteRecordsFromCloud:obsoleteRecordNames [weakSelf deleteRecordsFromCloud:obsoleteRecordNames
deletedCount:deletedCount + 1 deletedCount:deletedCount + batchRecordNames.count
completion:completion]; completion:completion];
}); });
} }
@ -874,7 +889,7 @@ NS_ASSUME_NONNULL_BEGIN
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
// Cloud cleanup is non-critical so any error is recoverable. // Cloud cleanup is non-critical so any error is recoverable.
[weakSelf deleteRecordsFromCloud:obsoleteRecordNames [weakSelf deleteRecordsFromCloud:obsoleteRecordNames
deletedCount:deletedCount + 1 deletedCount:deletedCount + batchRecordNames.count
completion:completion]; completion:completion];
}); });
}]; }];

@ -56,13 +56,16 @@ static const NSUInteger kOWSBackupKeyLength = 32;
OWSAssert(srcFilePath.length > 0); OWSAssert(srcFilePath.length > 0);
OWSAssert(encryptionKey.length > 0); OWSAssert(encryptionKey.length > 0);
// TODO: Encrypt the file without loading it into memory. @autoreleasepool {
NSData *_Nullable srcData = [NSData dataWithContentsOfFile:srcFilePath];
if (!srcData) { // TODO: Encrypt the file without loading it into memory.
OWSProdLogAndFail(@"%@ could not load file into memory", self.logTag); NSData *_Nullable srcData = [NSData dataWithContentsOfFile:srcFilePath];
return nil; if (srcData.length < 1) {
OWSProdLogAndFail(@"%@ could not load file into memory for encryption.", self.logTag);
return nil;
}
return [self encryptDataAsTempFile:srcData encryptionKey:encryptionKey];
} }
return [self encryptDataAsTempFile:srcData encryptionKey:encryptionKey];
} }
- (nullable OWSBackupEncryptedItem *)encryptDataAsTempFile:(NSData *)srcData - (nullable OWSBackupEncryptedItem *)encryptDataAsTempFile:(NSData *)srcData
@ -74,25 +77,30 @@ static const NSUInteger kOWSBackupKeyLength = 32;
return [self encryptDataAsTempFile:srcData encryptionKey:encryptionKey]; return [self encryptDataAsTempFile:srcData encryptionKey:encryptionKey];
} }
- (nullable OWSBackupEncryptedItem *)encryptDataAsTempFile:(NSData *)srcData encryptionKey:(NSData *)encryptionKey - (nullable OWSBackupEncryptedItem *)encryptDataAsTempFile:(NSData *)unencryptedData
encryptionKey:(NSData *)encryptionKey
{ {
OWSAssert(srcData); OWSAssert(unencryptedData);
OWSAssert(encryptionKey.length > 0); OWSAssert(encryptionKey.length > 0);
// TODO: Encrypt the data using key; @autoreleasepool {
NSString *dstFilePath = [self.jobTempDirPath stringByAppendingPathComponent:[NSUUID UUID].UUIDString]; // TODO: Encrypt the data using key;
NSError *error; NSData *encryptedData = unencryptedData;
BOOL success = [srcData writeToFile:dstFilePath options:NSDataWritingAtomic error:&error];
if (!success || error) { NSString *dstFilePath = [self.jobTempDirPath stringByAppendingPathComponent:[NSUUID UUID].UUIDString];
OWSProdLogAndFail(@"%@ error writing encrypted data: %@", self.logTag, error); NSError *error;
return nil; BOOL success = [encryptedData writeToFile:dstFilePath options:NSDataWritingAtomic error:&error];
if (!success || error) {
OWSProdLogAndFail(@"%@ error writing encrypted data: %@", self.logTag, error);
return nil;
}
[OWSFileSystem protectFileOrFolderAtPath:dstFilePath];
OWSBackupEncryptedItem *item = [OWSBackupEncryptedItem new];
item.filePath = dstFilePath;
item.encryptionKey = encryptionKey;
return item;
} }
[OWSFileSystem protectFileOrFolderAtPath:dstFilePath];
OWSBackupEncryptedItem *item = [OWSBackupEncryptedItem new];
item.filePath = dstFilePath;
item.encryptionKey = encryptionKey;
return item;
} }
#pragma mark - Decrypt #pragma mark - Decrypt
@ -104,22 +112,25 @@ static const NSUInteger kOWSBackupKeyLength = 32;
OWSAssert(srcFilePath.length > 0); OWSAssert(srcFilePath.length > 0);
OWSAssert(encryptionKey.length > 0); OWSAssert(encryptionKey.length > 0);
// TODO: Decrypt the file without loading it into memory. @autoreleasepool {
NSData *data = [self decryptFileAsData:srcFilePath encryptionKey:encryptionKey];
if (!data) { // TODO: Decrypt the file without loading it into memory.
return NO; NSData *data = [self decryptFileAsData:srcFilePath encryptionKey:encryptionKey];
}
NSError *error; if (data.length < 1) {
BOOL success = [data writeToFile:dstFilePath options:NSDataWritingAtomic error:&error]; return NO;
if (!success || error) { }
OWSProdLogAndFail(@"%@ error writing decrypted data: %@", self.logTag, error);
return NO;
}
[OWSFileSystem protectFileOrFolderAtPath:dstFilePath];
return YES; NSError *error;
BOOL success = [data writeToFile:dstFilePath options:NSDataWritingAtomic error:&error];
if (!success || error) {
OWSProdLogAndFail(@"%@ error writing decrypted data: %@", self.logTag, error);
return NO;
}
[OWSFileSystem protectFileOrFolderAtPath:dstFilePath];
return YES;
}
} }
- (nullable NSData *)decryptFileAsData:(NSString *)srcFilePath encryptionKey:(NSData *)encryptionKey - (nullable NSData *)decryptFileAsData:(NSString *)srcFilePath encryptionKey:(NSData *)encryptionKey
@ -127,30 +138,36 @@ static const NSUInteger kOWSBackupKeyLength = 32;
OWSAssert(srcFilePath.length > 0); OWSAssert(srcFilePath.length > 0);
OWSAssert(encryptionKey.length > 0); OWSAssert(encryptionKey.length > 0);
if (![NSFileManager.defaultManager fileExistsAtPath:srcFilePath]) { @autoreleasepool {
DDLogError(@"%@ missing downloaded file.", self.logTag);
return nil;
}
// TODO: Decrypt the file without loading it into memory. if (![NSFileManager.defaultManager fileExistsAtPath:srcFilePath]) {
NSData *_Nullable srcData = [NSData dataWithContentsOfFile:srcFilePath]; DDLogError(@"%@ missing downloaded file.", self.logTag);
if (!srcData) { return nil;
OWSProdLogAndFail(@"%@ could not load file into memory", self.logTag); }
return nil;
} NSData *_Nullable srcData = [NSData dataWithContentsOfFile:srcFilePath];
if (srcData.length < 1) {
OWSProdLogAndFail(@"%@ could not load file into memory for decryption.", self.logTag);
return nil;
}
NSData *_Nullable dstData = [self decryptDataAsData:srcData encryptionKey:encryptionKey]; NSData *_Nullable dstData = [self decryptDataAsData:srcData encryptionKey:encryptionKey];
return dstData; return dstData;
}
} }
- (nullable NSData *)decryptDataAsData:(NSData *)srcData encryptionKey:(NSData *)encryptionKey - (nullable NSData *)decryptDataAsData:(NSData *)encryptedData encryptionKey:(NSData *)encryptionKey
{ {
OWSAssert(srcData); OWSAssert(encryptedData);
OWSAssert(encryptionKey.length > 0); OWSAssert(encryptionKey.length > 0);
// TODO: Decrypt the data using key; @autoreleasepool {
// TODO: Decrypt the data using key;
NSData *unencryptedData = encryptedData;
return srcData; return unencryptedData;
}
} }
#pragma mark - Compression #pragma mark - Compression
@ -159,67 +176,76 @@ static const NSUInteger kOWSBackupKeyLength = 32;
{ {
OWSAssert(srcData); OWSAssert(srcData);
if (!srcData) { @autoreleasepool {
OWSProdLogAndFail(@"%@ missing unencrypted data.", self.logTag);
return nil; if (!srcData) {
} OWSProdLogAndFail(@"%@ missing unencrypted data.", self.logTag);
return nil;
size_t srcLength = [srcData length]; }
const uint8_t *srcBuffer = (const uint8_t *)[srcData bytes];
if (!srcBuffer) { size_t srcLength = [srcData length];
return nil; const uint8_t *srcBuffer = (const uint8_t *)[srcData bytes];
} if (!srcBuffer) {
// This assumes that dst will always be smaller than src. return nil;
// }
// We slightly pad the buffer size to account for the worst case. // This assumes that dst will always be smaller than src.
uint8_t *dstBuffer = malloc(sizeof(uint8_t) * srcLength) + 64 * 1024; //
if (!dstBuffer) { // We slightly pad the buffer size to account for the worst case.
return nil; size_t dstBufferLength = srcLength + 64 * 1024;
uint8_t *dstBuffer = malloc(sizeof(uint8_t) * dstBufferLength);
if (!dstBuffer) {
return nil;
}
// TODO: Should we use COMPRESSION_LZFSE?
size_t dstLength
= compression_encode_buffer(dstBuffer, dstBufferLength, srcBuffer, srcLength, NULL, COMPRESSION_LZFSE);
NSData *compressedData = [NSData dataWithBytesNoCopy:dstBuffer length:dstLength freeWhenDone:YES];
DDLogVerbose(@"%@ compressed %zd -> %zd = %0.2f",
self.logTag,
srcLength,
dstLength,
(srcLength > 0 ? (dstLength / (CGFloat)srcLength) : 0));
return compressedData;
} }
// TODO: Should we use COMPRESSION_LZFSE?
size_t dstLength = compression_encode_buffer(dstBuffer, srcLength, srcBuffer, srcLength, NULL, COMPRESSION_LZFSE);
NSData *compressedData = [NSData dataWithBytes:dstBuffer length:dstLength];
DDLogVerbose(@"%@ compressed %zd -> %zd = %0.2f",
self.logTag,
srcLength,
dstLength,
(srcLength > 0 ? (dstLength / (CGFloat)srcLength) : 0));
free(dstBuffer);
return compressedData;
} }
- (nullable NSData *)decompressData:(NSData *)srcData uncompressedDataLength:(NSUInteger)uncompressedDataLength - (nullable NSData *)decompressData:(NSData *)srcData uncompressedDataLength:(NSUInteger)uncompressedDataLength
{ {
OWSAssert(srcData); OWSAssert(srcData);
if (!srcData) { @autoreleasepool {
OWSProdLogAndFail(@"%@ missing unencrypted data.", self.logTag);
return nil; if (!srcData) {
} OWSProdLogAndFail(@"%@ missing unencrypted data.", self.logTag);
return nil;
size_t srcLength = [srcData length]; }
const uint8_t *srcBuffer = (const uint8_t *)[srcData bytes];
if (!srcBuffer) { size_t srcLength = [srcData length];
return nil; const uint8_t *srcBuffer = (const uint8_t *)[srcData bytes];
} if (!srcBuffer) {
// We pad the buffer to be defensive. return nil;
uint8_t *dstBuffer = malloc(sizeof(uint8_t) * (uncompressedDataLength + 1024)); }
if (!dstBuffer) { // We pad the buffer to be defensive.
return nil; size_t dstBufferLength = uncompressedDataLength + 1024;
uint8_t *dstBuffer = malloc(sizeof(uint8_t) * dstBufferLength);
if (!dstBuffer) {
return nil;
}
// TODO: Should we use COMPRESSION_LZFSE?
size_t dstLength
= compression_decode_buffer(dstBuffer, dstBufferLength, srcBuffer, srcLength, NULL, COMPRESSION_LZFSE);
NSData *decompressedData = [NSData dataWithBytesNoCopy:dstBuffer length:dstLength freeWhenDone:YES];
OWSAssert(decompressedData.length == uncompressedDataLength);
DDLogVerbose(@"%@ decompressed %zd -> %zd = %0.2f",
self.logTag,
srcLength,
dstLength,
(dstLength > 0 ? (srcLength / (CGFloat)dstLength) : 0));
return decompressedData;
} }
// TODO: Should we use COMPRESSION_LZFSE?
size_t dstLength = compression_decode_buffer(dstBuffer, srcLength, srcBuffer, srcLength, NULL, COMPRESSION_LZFSE);
NSData *decompressedData = [NSData dataWithBytes:dstBuffer length:dstLength];
OWSAssert(decompressedData.length == uncompressedDataLength);
DDLogVerbose(@"%@ decompressed %zd -> %zd = %0.2f",
self.logTag,
srcLength,
dstLength,
(dstLength > 0 ? (srcLength / (CGFloat)dstLength) : 0));
free(dstBuffer);
return decompressedData;
} }
@end @end

@ -256,7 +256,7 @@ NSString *const kOWSBackup_ImportDatabaseKeySpec = @"kOWSBackup_ImportDatabaseKe
OWSAssert(json); OWSAssert(json);
OWSAssert(key.length); OWSAssert(key.length);
if (![json isKindOfClass:[NSArray class]]) { if (![json isKindOfClass:[NSDictionary class]]) {
OWSProdLogAndFail(@"%@ manifest has invalid data: %@.", self.logTag, key); OWSProdLogAndFail(@"%@ manifest has invalid data: %@.", self.logTag, key);
return nil; return nil;
} }
@ -408,12 +408,14 @@ NSString *const kOWSBackup_ImportDatabaseKeySpec = @"kOWSBackup_ImportDatabaseKe
DDLogError(@"%@ skipping redundant file restore: %@.", self.logTag, dstFilePath); DDLogError(@"%@ skipping redundant file restore: %@.", self.logTag, dstFilePath);
continue; continue;
} }
if (![self.backupIO decryptFileAsFile:item.downloadFilePath @autoreleasepool {
dstFilePath:dstFilePath if (![self.backupIO decryptFileAsFile:item.downloadFilePath
encryptionKey:item.encryptionKey]) { dstFilePath:dstFilePath
DDLogError(@"%@ attachment could not be restored.", self.logTag); encryptionKey:item.encryptionKey]) {
// Attachment-related errors are recoverable and can be ignored. DDLogError(@"%@ attachment could not be restored.", self.logTag);
continue; // Attachment-related errors are recoverable and can be ignored.
continue;
}
} }
DDLogError(@"%@ restored file: %@.", self.logTag, item.relativeFilePath); DDLogError(@"%@ restored file: %@.", self.logTag, item.relativeFilePath);
@ -497,60 +499,62 @@ NSString *const kOWSBackup_ImportDatabaseKeySpec = @"kOWSBackup_ImportDatabaseKe
@"Indicates that the backup database is being restored.") @"Indicates that the backup database is being restored.")
progress:@(count / (CGFloat)self.databaseItems.count)]; progress:@(count / (CGFloat)self.databaseItems.count)];
NSData *_Nullable compressedData = @autoreleasepool {
[self.backupIO decryptFileAsData:item.downloadFilePath encryptionKey:item.encryptionKey]; NSData *_Nullable compressedData =
if (!compressedData) { [self.backupIO decryptFileAsData:item.downloadFilePath encryptionKey:item.encryptionKey];
// Database-related errors are unrecoverable. if (!compressedData) {
aborted = YES;
return completion(NO);
}
NSData *_Nullable uncompressedData =
[self.backupIO decompressData:compressedData
uncompressedDataLength:item.uncompressedDataLength.unsignedIntValue];
if (!uncompressedData) {
// Database-related errors are unrecoverable.
aborted = YES;
return completion(NO);
}
OWSSignalServiceProtosBackupSnapshot *_Nullable entities =
[OWSSignalServiceProtosBackupSnapshot parseFromData:uncompressedData];
if (!entities || entities.entity.count < 1) {
DDLogError(@"%@ missing entities.", self.logTag);
// Database-related errors are unrecoverable.
aborted = YES;
return completion(NO);
}
for (OWSSignalServiceProtosBackupSnapshotBackupEntity *entity in entities.entity) {
NSData *_Nullable entityData = entity.entityData;
if (entityData.length < 1) {
DDLogError(@"%@ missing entity data.", self.logTag);
// Database-related errors are unrecoverable. // Database-related errors are unrecoverable.
aborted = YES; aborted = YES;
return completion(NO); return completion(NO);
} }
NSData *_Nullable uncompressedData =
[self.backupIO decompressData:compressedData
uncompressedDataLength:item.uncompressedDataLength.unsignedIntValue];
if (!uncompressedData) {
// Database-related errors are unrecoverable.
aborted = YES;
return completion(NO);
}
OWSSignalServiceProtosBackupSnapshot *_Nullable entities =
[OWSSignalServiceProtosBackupSnapshot parseFromData:uncompressedData];
if (!entities || entities.entity.count < 1) {
DDLogError(@"%@ missing entities.", self.logTag);
// Database-related errors are unrecoverable.
aborted = YES;
return completion(NO);
}
for (OWSSignalServiceProtosBackupSnapshotBackupEntity *entity in entities.entity) {
NSData *_Nullable entityData = entity.entityData;
if (entityData.length < 1) {
DDLogError(@"%@ missing entity data.", self.logTag);
// Database-related errors are unrecoverable.
aborted = YES;
return completion(NO);
}
__block TSYapDatabaseObject *object = nil; __block TSYapDatabaseObject *object = nil;
@try { @try {
NSKeyedUnarchiver *unarchiver = [[NSKeyedUnarchiver alloc] initForReadingWithData:entityData]; NSKeyedUnarchiver *unarchiver = [[NSKeyedUnarchiver alloc] initForReadingWithData:entityData];
object = [unarchiver decodeObjectForKey:@"root"]; object = [unarchiver decodeObjectForKey:@"root"];
if (![object isKindOfClass:[object class]]) { if (![object isKindOfClass:[object class]]) {
DDLogError(@"%@ invalid decoded entity: %@.", self.logTag, [object class]); DDLogError(@"%@ invalid decoded entity: %@.", self.logTag, [object class]);
// Database-related errors are unrecoverable.
aborted = YES;
return completion(NO);
}
} @catch (NSException *exception) {
DDLogError(@"%@ could not decode entity.", self.logTag);
// Database-related errors are unrecoverable. // Database-related errors are unrecoverable.
aborted = YES; aborted = YES;
return completion(NO); return completion(NO);
} }
} @catch (NSException *exception) {
DDLogError(@"%@ could not decode entity.", self.logTag);
// Database-related errors are unrecoverable.
aborted = YES;
return completion(NO);
}
[object saveWithTransaction:transaction]; [object saveWithTransaction:transaction];
copiedEntities++; copiedEntities++;
NSString *collection = [object.class collection]; NSString *collection = [object.class collection];
NSUInteger restoredEntityCount = restoredEntityCounts[collection].unsignedIntValue; NSUInteger restoredEntityCount = restoredEntityCounts[collection].unsignedIntValue;
restoredEntityCounts[collection] = @(restoredEntityCount + 1); restoredEntityCounts[collection] = @(restoredEntityCount + 1);
}
} }
} }
}]; }];

Loading…
Cancel
Save