Fix bugs in new db representation, add batch record deletion, improve memory management.
This commit is contained in:
parent
fed524ba16
commit
2ebd8668b4
|
@ -90,11 +90,6 @@
|
|||
self.title = NSLocalizedString(@"SETTINGS_NAV_BAR_TITLE", @"Title for settings activity");
|
||||
|
||||
[self updateTableContents];
|
||||
|
||||
dispatch_async(dispatch_get_main_queue(), ^{
|
||||
[self showBackup];
|
||||
// [self showDebugUI];
|
||||
});
|
||||
}
|
||||
|
||||
- (void)viewWillAppear:(BOOL)animated
|
||||
|
|
|
@ -426,10 +426,6 @@ typedef NS_ENUM(NSInteger, CellState) { kArchiveState, kInboxState };
|
|||
}
|
||||
|
||||
[self checkIfEmptyView];
|
||||
|
||||
dispatch_async(dispatch_get_main_queue(), ^{
|
||||
[self settingsButtonPressed:nil];
|
||||
});
|
||||
}
|
||||
|
||||
- (void)viewWillDisappear:(BOOL)animated
|
||||
|
|
|
@ -225,28 +225,30 @@ import CloudKit
|
|||
// MARK: - Delete
|
||||
|
||||
@objc
|
||||
public class func deleteRecordFromCloud(recordName: String,
|
||||
public class func deleteRecordsFromCloud(recordNames: [String],
|
||||
success: @escaping (()) -> Void,
|
||||
failure: @escaping (Error) -> Void) {
|
||||
deleteRecordFromCloud(recordName: recordName,
|
||||
deleteRecordsFromCloud(recordNames: recordNames,
|
||||
remainingRetries: maxRetries,
|
||||
success: success,
|
||||
failure: failure)
|
||||
}
|
||||
|
||||
private class func deleteRecordFromCloud(recordName: String,
|
||||
private class func deleteRecordsFromCloud(recordNames: [String],
|
||||
remainingRetries: Int,
|
||||
success: @escaping (()) -> Void,
|
||||
failure: @escaping (Error) -> Void) {
|
||||
|
||||
let recordID = CKRecordID(recordName: recordName)
|
||||
|
||||
database().delete(withRecordID: recordID) {
|
||||
(_, error) in
|
||||
var recordIDs = [CKRecordID]()
|
||||
for recordName in recordNames {
|
||||
recordIDs.append(CKRecordID(recordName: recordName))
|
||||
}
|
||||
let deleteOperation = CKModifyRecordsOperation(recordsToSave: nil, recordIDsToDelete: recordIDs)
|
||||
deleteOperation.modifyRecordsCompletionBlock = { (records, recordIds, error) in
|
||||
|
||||
let outcome = outcomeForCloudKitError(error: error,
|
||||
remainingRetries: remainingRetries,
|
||||
label: "Delete Record")
|
||||
remainingRetries: remainingRetries,
|
||||
label: "Delete Records")
|
||||
switch outcome {
|
||||
case .success:
|
||||
success()
|
||||
|
@ -254,23 +256,24 @@ import CloudKit
|
|||
failure(outcomeError)
|
||||
case .failureRetryAfterDelay(let retryDelay):
|
||||
DispatchQueue.global().asyncAfter(deadline: DispatchTime.now() + retryDelay, execute: {
|
||||
deleteRecordFromCloud(recordName: recordName,
|
||||
remainingRetries: remainingRetries - 1,
|
||||
success: success,
|
||||
failure: failure)
|
||||
deleteRecordsFromCloud(recordNames: recordNames,
|
||||
remainingRetries: remainingRetries - 1,
|
||||
success: success,
|
||||
failure: failure)
|
||||
})
|
||||
case .failureRetryWithoutDelay:
|
||||
DispatchQueue.global().async {
|
||||
deleteRecordFromCloud(recordName: recordName,
|
||||
remainingRetries: remainingRetries - 1,
|
||||
success: success,
|
||||
failure: failure)
|
||||
deleteRecordsFromCloud(recordNames: recordNames,
|
||||
remainingRetries: remainingRetries - 1,
|
||||
success: success,
|
||||
failure: failure)
|
||||
}
|
||||
case .unknownItem:
|
||||
owsFail("\(self.logTag) unexpected CloudKit response.")
|
||||
failure(invalidServiceResponseError())
|
||||
}
|
||||
}
|
||||
database().add(deleteOperation)
|
||||
}
|
||||
|
||||
// MARK: - Exists?
|
||||
|
|
|
@ -124,7 +124,9 @@ NS_ASSUME_NONNULL_BEGIN
|
|||
|
||||
static const int kMaxDBSnapshotSize = 1000;
|
||||
if (self.cachedItemCount > kMaxDBSnapshotSize) {
|
||||
return [self flush];
|
||||
@autoreleasepool {
|
||||
return [self flush];
|
||||
}
|
||||
}
|
||||
|
||||
return YES;
|
||||
|
@ -137,27 +139,31 @@ NS_ASSUME_NONNULL_BEGIN
|
|||
return YES;
|
||||
}
|
||||
|
||||
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;
|
||||
// Try to release allocated buffers ASAP.
|
||||
@autoreleasepool {
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
NSData *compressedData = [self.backupIO compressData:uncompressedData];
|
||||
|
||||
OWSBackupEncryptedItem *_Nullable encryptedItem = [self.backupIO encryptDataAsTempFile:compressedData];
|
||||
if (!encryptedItem) {
|
||||
OWSProdLogAndFail(@"%@ couldn't encrypt database snapshot.", self.logTag);
|
||||
return NO;
|
||||
}
|
||||
|
||||
OWSBackupExportItem *exportItem = [[OWSBackupExportItem alloc] initWithEncryptedItem:encryptedItem];
|
||||
exportItem.uncompressedDataLength = @(uncompressedDataLength);
|
||||
[self.exportItems addObject:exportItem];
|
||||
}
|
||||
|
||||
NSData *compressedData = [self.backupIO compressData:uncompressedData];
|
||||
|
||||
OWSBackupEncryptedItem *_Nullable encryptedItem = [self.backupIO encryptDataAsTempFile:compressedData];
|
||||
if (!encryptedItem) {
|
||||
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;
|
||||
}
|
||||
|
||||
|
@ -500,9 +506,11 @@ NS_ASSUME_NONNULL_BEGIN
|
|||
return NO;
|
||||
}
|
||||
|
||||
if (![exportStream flush]) {
|
||||
OWSProdLogAndFail(@"%@ Could not flush database snapshots.", self.logTag);
|
||||
return NO;
|
||||
@autoreleasepool {
|
||||
if (![exportStream flush]) {
|
||||
OWSProdLogAndFail(@"%@ Could not flush database snapshots.", self.logTag);
|
||||
return NO;
|
||||
}
|
||||
}
|
||||
|
||||
self.unsavedDatabaseItems = [exportStream.exportItems mutableCopy];
|
||||
|
@ -629,15 +637,17 @@ NS_ASSUME_NONNULL_BEGIN
|
|||
OWSAttachmentExport *attachmentExport = self.unsavedAttachmentExports.lastObject;
|
||||
[self.unsavedAttachmentExports removeLastObject];
|
||||
|
||||
// OWSAttachmentExport is used to lazily write an encrypted copy of the
|
||||
// attachment to disk.
|
||||
if (![attachmentExport prepareForUpload]) {
|
||||
// Attachment files are non-critical so any error uploading them is recoverable.
|
||||
[weakSelf saveNextFileToCloud:completion];
|
||||
return YES;
|
||||
@autoreleasepool {
|
||||
// OWSAttachmentExport is used to lazily write an encrypted copy of the
|
||||
// attachment to disk.
|
||||
if (![attachmentExport prepareForUpload]) {
|
||||
// Attachment files are non-critical so any error uploading them is recoverable.
|
||||
[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
|
||||
fileUrlBlock:^{
|
||||
|
@ -856,16 +866,21 @@ NS_ASSUME_NONNULL_BEGIN
|
|||
@"Indicates that the cloud is being cleaned up.")
|
||||
progress:@(progress)];
|
||||
|
||||
NSString *recordName = obsoleteRecordNames.lastObject;
|
||||
[obsoleteRecordNames removeLastObject];
|
||||
static const NSUInteger kMaxBatchSize = 100;
|
||||
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;
|
||||
[OWSBackupAPI deleteRecordFromCloudWithRecordName:recordName
|
||||
[OWSBackupAPI deleteRecordsFromCloudWithRecordNames:batchRecordNames
|
||||
success:^{
|
||||
// Ensure that we continue to work off the main thread.
|
||||
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
|
||||
[weakSelf deleteRecordsFromCloud:obsoleteRecordNames
|
||||
deletedCount:deletedCount + 1
|
||||
deletedCount:deletedCount + batchRecordNames.count
|
||||
completion:completion];
|
||||
});
|
||||
}
|
||||
|
@ -874,7 +889,7 @@ NS_ASSUME_NONNULL_BEGIN
|
|||
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
|
||||
// Cloud cleanup is non-critical so any error is recoverable.
|
||||
[weakSelf deleteRecordsFromCloud:obsoleteRecordNames
|
||||
deletedCount:deletedCount + 1
|
||||
deletedCount:deletedCount + batchRecordNames.count
|
||||
completion:completion];
|
||||
});
|
||||
}];
|
||||
|
|
|
@ -56,13 +56,16 @@ static const NSUInteger kOWSBackupKeyLength = 32;
|
|||
OWSAssert(srcFilePath.length > 0);
|
||||
OWSAssert(encryptionKey.length > 0);
|
||||
|
||||
// TODO: Encrypt the file without loading it into memory.
|
||||
NSData *_Nullable srcData = [NSData dataWithContentsOfFile:srcFilePath];
|
||||
if (!srcData) {
|
||||
OWSProdLogAndFail(@"%@ could not load file into memory", self.logTag);
|
||||
return nil;
|
||||
@autoreleasepool {
|
||||
|
||||
// TODO: Encrypt the file without loading it into memory.
|
||||
NSData *_Nullable srcData = [NSData dataWithContentsOfFile:srcFilePath];
|
||||
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
|
||||
|
@ -74,25 +77,30 @@ static const NSUInteger kOWSBackupKeyLength = 32;
|
|||
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);
|
||||
|
||||
// TODO: Encrypt the data using key;
|
||||
@autoreleasepool {
|
||||
|
||||
NSString *dstFilePath = [self.jobTempDirPath stringByAppendingPathComponent:[NSUUID UUID].UUIDString];
|
||||
NSError *error;
|
||||
BOOL success = [srcData writeToFile:dstFilePath options:NSDataWritingAtomic error:&error];
|
||||
if (!success || error) {
|
||||
OWSProdLogAndFail(@"%@ error writing encrypted data: %@", self.logTag, error);
|
||||
return nil;
|
||||
// TODO: Encrypt the data using key;
|
||||
NSData *encryptedData = unencryptedData;
|
||||
|
||||
NSString *dstFilePath = [self.jobTempDirPath stringByAppendingPathComponent:[NSUUID UUID].UUIDString];
|
||||
NSError *error;
|
||||
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
|
||||
|
@ -104,22 +112,25 @@ static const NSUInteger kOWSBackupKeyLength = 32;
|
|||
OWSAssert(srcFilePath.length > 0);
|
||||
OWSAssert(encryptionKey.length > 0);
|
||||
|
||||
// TODO: Decrypt the file without loading it into memory.
|
||||
NSData *data = [self decryptFileAsData:srcFilePath encryptionKey:encryptionKey];
|
||||
@autoreleasepool {
|
||||
|
||||
if (!data) {
|
||||
return NO;
|
||||
// TODO: Decrypt the file without loading it into memory.
|
||||
NSData *data = [self decryptFileAsData:srcFilePath encryptionKey:encryptionKey];
|
||||
|
||||
if (data.length < 1) {
|
||||
return NO;
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
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
|
||||
|
@ -127,30 +138,36 @@ static const NSUInteger kOWSBackupKeyLength = 32;
|
|||
OWSAssert(srcFilePath.length > 0);
|
||||
OWSAssert(encryptionKey.length > 0);
|
||||
|
||||
if (![NSFileManager.defaultManager fileExistsAtPath:srcFilePath]) {
|
||||
DDLogError(@"%@ missing downloaded file.", self.logTag);
|
||||
return nil;
|
||||
}
|
||||
@autoreleasepool {
|
||||
|
||||
// TODO: Decrypt the file without loading it into memory.
|
||||
NSData *_Nullable srcData = [NSData dataWithContentsOfFile:srcFilePath];
|
||||
if (!srcData) {
|
||||
OWSProdLogAndFail(@"%@ could not load file into memory", self.logTag);
|
||||
return nil;
|
||||
}
|
||||
if (![NSFileManager.defaultManager fileExistsAtPath:srcFilePath]) {
|
||||
DDLogError(@"%@ missing downloaded file.", self.logTag);
|
||||
return nil;
|
||||
}
|
||||
|
||||
NSData *_Nullable dstData = [self decryptDataAsData:srcData encryptionKey:encryptionKey];
|
||||
return dstData;
|
||||
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];
|
||||
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);
|
||||
|
||||
// TODO: Decrypt the data using key;
|
||||
@autoreleasepool {
|
||||
|
||||
return srcData;
|
||||
// TODO: Decrypt the data using key;
|
||||
NSData *unencryptedData = encryptedData;
|
||||
|
||||
return unencryptedData;
|
||||
}
|
||||
}
|
||||
|
||||
#pragma mark - Compression
|
||||
|
@ -159,67 +176,76 @@ static const NSUInteger kOWSBackupKeyLength = 32;
|
|||
{
|
||||
OWSAssert(srcData);
|
||||
|
||||
if (!srcData) {
|
||||
OWSProdLogAndFail(@"%@ missing unencrypted data.", self.logTag);
|
||||
return nil;
|
||||
}
|
||||
@autoreleasepool {
|
||||
|
||||
size_t srcLength = [srcData length];
|
||||
const uint8_t *srcBuffer = (const uint8_t *)[srcData bytes];
|
||||
if (!srcBuffer) {
|
||||
return nil;
|
||||
}
|
||||
// This assumes that dst will always be smaller than src.
|
||||
//
|
||||
// We slightly pad the buffer size to account for the worst case.
|
||||
uint8_t *dstBuffer = malloc(sizeof(uint8_t) * srcLength) + 64 * 1024;
|
||||
if (!dstBuffer) {
|
||||
return nil;
|
||||
}
|
||||
// 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);
|
||||
if (!srcData) {
|
||||
OWSProdLogAndFail(@"%@ missing unencrypted data.", self.logTag);
|
||||
return nil;
|
||||
}
|
||||
|
||||
return compressedData;
|
||||
size_t srcLength = [srcData length];
|
||||
const uint8_t *srcBuffer = (const uint8_t *)[srcData bytes];
|
||||
if (!srcBuffer) {
|
||||
return nil;
|
||||
}
|
||||
// This assumes that dst will always be smaller than src.
|
||||
//
|
||||
// We slightly pad the buffer size to account for the worst case.
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
- (nullable NSData *)decompressData:(NSData *)srcData uncompressedDataLength:(NSUInteger)uncompressedDataLength
|
||||
{
|
||||
OWSAssert(srcData);
|
||||
|
||||
if (!srcData) {
|
||||
OWSProdLogAndFail(@"%@ missing unencrypted data.", self.logTag);
|
||||
return nil;
|
||||
}
|
||||
@autoreleasepool {
|
||||
|
||||
size_t srcLength = [srcData length];
|
||||
const uint8_t *srcBuffer = (const uint8_t *)[srcData bytes];
|
||||
if (!srcBuffer) {
|
||||
return nil;
|
||||
}
|
||||
// We pad the buffer to be defensive.
|
||||
uint8_t *dstBuffer = malloc(sizeof(uint8_t) * (uncompressedDataLength + 1024));
|
||||
if (!dstBuffer) {
|
||||
return nil;
|
||||
}
|
||||
// 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);
|
||||
if (!srcData) {
|
||||
OWSProdLogAndFail(@"%@ missing unencrypted data.", self.logTag);
|
||||
return nil;
|
||||
}
|
||||
|
||||
return decompressedData;
|
||||
size_t srcLength = [srcData length];
|
||||
const uint8_t *srcBuffer = (const uint8_t *)[srcData bytes];
|
||||
if (!srcBuffer) {
|
||||
return nil;
|
||||
}
|
||||
// We pad the buffer to be defensive.
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
@end
|
||||
|
|
|
@ -256,7 +256,7 @@ NSString *const kOWSBackup_ImportDatabaseKeySpec = @"kOWSBackup_ImportDatabaseKe
|
|||
OWSAssert(json);
|
||||
OWSAssert(key.length);
|
||||
|
||||
if (![json isKindOfClass:[NSArray class]]) {
|
||||
if (![json isKindOfClass:[NSDictionary class]]) {
|
||||
OWSProdLogAndFail(@"%@ manifest has invalid data: %@.", self.logTag, key);
|
||||
return nil;
|
||||
}
|
||||
|
@ -408,12 +408,14 @@ NSString *const kOWSBackup_ImportDatabaseKeySpec = @"kOWSBackup_ImportDatabaseKe
|
|||
DDLogError(@"%@ skipping redundant file restore: %@.", self.logTag, dstFilePath);
|
||||
continue;
|
||||
}
|
||||
if (![self.backupIO decryptFileAsFile:item.downloadFilePath
|
||||
dstFilePath:dstFilePath
|
||||
encryptionKey:item.encryptionKey]) {
|
||||
DDLogError(@"%@ attachment could not be restored.", self.logTag);
|
||||
// Attachment-related errors are recoverable and can be ignored.
|
||||
continue;
|
||||
@autoreleasepool {
|
||||
if (![self.backupIO decryptFileAsFile:item.downloadFilePath
|
||||
dstFilePath:dstFilePath
|
||||
encryptionKey:item.encryptionKey]) {
|
||||
DDLogError(@"%@ attachment could not be restored.", self.logTag);
|
||||
// Attachment-related errors are recoverable and can be ignored.
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
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.")
|
||||
progress:@(count / (CGFloat)self.databaseItems.count)];
|
||||
|
||||
NSData *_Nullable compressedData =
|
||||
[self.backupIO decryptFileAsData:item.downloadFilePath encryptionKey:item.encryptionKey];
|
||||
if (!compressedData) {
|
||||
// Database-related errors are unrecoverable.
|
||||
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);
|
||||
@autoreleasepool {
|
||||
NSData *_Nullable compressedData =
|
||||
[self.backupIO decryptFileAsData:item.downloadFilePath encryptionKey:item.encryptionKey];
|
||||
if (!compressedData) {
|
||||
// Database-related errors are unrecoverable.
|
||||
aborted = YES;
|
||||
return completion(NO);
|
||||
}
|
||||
|
||||
__block TSYapDatabaseObject *object = nil;
|
||||
@try {
|
||||
NSKeyedUnarchiver *unarchiver = [[NSKeyedUnarchiver alloc] initForReadingWithData:entityData];
|
||||
object = [unarchiver decodeObjectForKey:@"root"];
|
||||
if (![object isKindOfClass:[object class]]) {
|
||||
DDLogError(@"%@ invalid decoded entity: %@.", self.logTag, [object class]);
|
||||
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);
|
||||
}
|
||||
} @catch (NSException *exception) {
|
||||
DDLogError(@"%@ could not decode entity.", self.logTag);
|
||||
// Database-related errors are unrecoverable.
|
||||
aborted = YES;
|
||||
return completion(NO);
|
||||
}
|
||||
|
||||
[object saveWithTransaction:transaction];
|
||||
copiedEntities++;
|
||||
NSString *collection = [object.class collection];
|
||||
NSUInteger restoredEntityCount = restoredEntityCounts[collection].unsignedIntValue;
|
||||
restoredEntityCounts[collection] = @(restoredEntityCount + 1);
|
||||
__block TSYapDatabaseObject *object = nil;
|
||||
@try {
|
||||
NSKeyedUnarchiver *unarchiver = [[NSKeyedUnarchiver alloc] initForReadingWithData:entityData];
|
||||
object = [unarchiver decodeObjectForKey:@"root"];
|
||||
if (![object isKindOfClass:[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.
|
||||
aborted = YES;
|
||||
return completion(NO);
|
||||
}
|
||||
|
||||
[object saveWithTransaction:transaction];
|
||||
copiedEntities++;
|
||||
NSString *collection = [object.class collection];
|
||||
NSUInteger restoredEntityCount = restoredEntityCounts[collection].unsignedIntValue;
|
||||
restoredEntityCounts[collection] = @(restoredEntityCount + 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
}];
|
||||
|
|
Loading…
Reference in New Issue