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 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")
label: "Delete Records")
switch outcome {
case .success:
success()
@ -254,14 +256,14 @@ import CloudKit
failure(outcomeError)
case .failureRetryAfterDelay(let retryDelay):
DispatchQueue.global().asyncAfter(deadline: DispatchTime.now() + retryDelay, execute: {
deleteRecordFromCloud(recordName: recordName,
deleteRecordsFromCloud(recordNames: recordNames,
remainingRetries: remainingRetries - 1,
success: success,
failure: failure)
})
case .failureRetryWithoutDelay:
DispatchQueue.global().async {
deleteRecordFromCloud(recordName: recordName,
deleteRecordsFromCloud(recordNames: recordNames,
remainingRetries: remainingRetries - 1,
success: success,
failure: failure)
@ -271,6 +273,7 @@ import CloudKit
failure(invalidServiceResponseError())
}
}
database().add(deleteOperation)
}
// MARK: - Exists?

@ -124,8 +124,10 @@ NS_ASSUME_NONNULL_BEGIN
static const int kMaxDBSnapshotSize = 1000;
if (self.cachedItemCount > kMaxDBSnapshotSize) {
@autoreleasepool {
return [self flush];
}
}
return YES;
}
@ -137,6 +139,9 @@ NS_ASSUME_NONNULL_BEGIN
return YES;
}
// Try to release allocated buffers ASAP.
@autoreleasepool {
NSData *_Nullable uncompressedData = [self.backupSnapshotBuilder build].data;
NSUInteger uncompressedDataLength = uncompressedData.length;
self.backupSnapshotBuilder = nil;
@ -157,6 +162,7 @@ NS_ASSUME_NONNULL_BEGIN
OWSBackupExportItem *exportItem = [[OWSBackupExportItem alloc] initWithEncryptedItem:encryptedItem];
exportItem.uncompressedDataLength = @(uncompressedDataLength);
[self.exportItems addObject:exportItem];
}
return YES;
}
@ -500,10 +506,12 @@ NS_ASSUME_NONNULL_BEGIN
return NO;
}
@autoreleasepool {
if (![exportStream flush]) {
OWSProdLogAndFail(@"%@ Could not flush database snapshots.", self.logTag);
return NO;
}
}
self.unsavedDatabaseItems = [exportStream.exportItems mutableCopy];
@ -629,6 +637,7 @@ NS_ASSUME_NONNULL_BEGIN
OWSAttachmentExport *attachmentExport = self.unsavedAttachmentExports.lastObject;
[self.unsavedAttachmentExports removeLastObject];
@autoreleasepool {
// OWSAttachmentExport is used to lazily write an encrypted copy of the
// attachment to disk.
if (![attachmentExport prepareForUpload]) {
@ -638,6 +647,7 @@ NS_ASSUME_NONNULL_BEGIN
}
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)];
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,14 +56,17 @@ static const NSUInteger kOWSBackupKeyLength = 32;
OWSAssert(srcFilePath.length > 0);
OWSAssert(encryptionKey.length > 0);
@autoreleasepool {
// 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);
if (srcData.length < 1) {
OWSProdLogAndFail(@"%@ could not load file into memory for encryption.", self.logTag);
return nil;
}
return [self encryptDataAsTempFile:srcData encryptionKey:encryptionKey];
}
}
- (nullable OWSBackupEncryptedItem *)encryptDataAsTempFile:(NSData *)srcData
{
@ -74,16 +77,20 @@ 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);
@autoreleasepool {
// TODO: Encrypt the data using key;
NSData *encryptedData = unencryptedData;
NSString *dstFilePath = [self.jobTempDirPath stringByAppendingPathComponent:[NSUUID UUID].UUIDString];
NSError *error;
BOOL success = [srcData writeToFile:dstFilePath options:NSDataWritingAtomic error:&error];
BOOL success = [encryptedData writeToFile:dstFilePath options:NSDataWritingAtomic error:&error];
if (!success || error) {
OWSProdLogAndFail(@"%@ error writing encrypted data: %@", self.logTag, error);
return nil;
@ -94,6 +101,7 @@ static const NSUInteger kOWSBackupKeyLength = 32;
item.encryptionKey = encryptionKey;
return item;
}
}
#pragma mark - Decrypt
@ -104,10 +112,12 @@ static const NSUInteger kOWSBackupKeyLength = 32;
OWSAssert(srcFilePath.length > 0);
OWSAssert(encryptionKey.length > 0);
@autoreleasepool {
// TODO: Decrypt the file without loading it into memory.
NSData *data = [self decryptFileAsData:srcFilePath encryptionKey:encryptionKey];
if (!data) {
if (data.length < 1) {
return NO;
}
@ -121,36 +131,43 @@ static const NSUInteger kOWSBackupKeyLength = 32;
return YES;
}
}
- (nullable NSData *)decryptFileAsData:(NSString *)srcFilePath encryptionKey:(NSData *)encryptionKey
{
OWSAssert(srcFilePath.length > 0);
OWSAssert(encryptionKey.length > 0);
@autoreleasepool {
if (![NSFileManager.defaultManager fileExistsAtPath:srcFilePath]) {
DDLogError(@"%@ missing downloaded file.", self.logTag);
return nil;
}
// 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);
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);
@autoreleasepool {
// TODO: Decrypt the data using key;
NSData *unencryptedData = encryptedData;
return srcData;
return unencryptedData;
}
}
#pragma mark - Compression
@ -159,6 +176,8 @@ static const NSUInteger kOWSBackupKeyLength = 32;
{
OWSAssert(srcData);
@autoreleasepool {
if (!srcData) {
OWSProdLogAndFail(@"%@ missing unencrypted data.", self.logTag);
return nil;
@ -172,27 +191,32 @@ static const NSUInteger kOWSBackupKeyLength = 32;
// 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;
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, srcLength, srcBuffer, srcLength, NULL, COMPRESSION_LZFSE);
NSData *compressedData = [NSData dataWithBytes:dstBuffer length:dstLength];
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));
free(dstBuffer);
return compressedData;
}
}
- (nullable NSData *)decompressData:(NSData *)srcData uncompressedDataLength:(NSUInteger)uncompressedDataLength
{
OWSAssert(srcData);
@autoreleasepool {
if (!srcData) {
OWSProdLogAndFail(@"%@ missing unencrypted data.", self.logTag);
return nil;
@ -204,23 +228,25 @@ static const NSUInteger kOWSBackupKeyLength = 32;
return nil;
}
// We pad the buffer to be defensive.
uint8_t *dstBuffer = malloc(sizeof(uint8_t) * (uncompressedDataLength + 1024));
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, srcLength, srcBuffer, srcLength, NULL, COMPRESSION_LZFSE);
NSData *decompressedData = [NSData dataWithBytes:dstBuffer length:dstLength];
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));
free(dstBuffer);
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,6 +408,7 @@ NSString *const kOWSBackup_ImportDatabaseKeySpec = @"kOWSBackup_ImportDatabaseKe
DDLogError(@"%@ skipping redundant file restore: %@.", self.logTag, dstFilePath);
continue;
}
@autoreleasepool {
if (![self.backupIO decryptFileAsFile:item.downloadFilePath
dstFilePath:dstFilePath
encryptionKey:item.encryptionKey]) {
@ -415,6 +416,7 @@ NSString *const kOWSBackup_ImportDatabaseKeySpec = @"kOWSBackup_ImportDatabaseKe
// Attachment-related errors are recoverable and can be ignored.
continue;
}
}
DDLogError(@"%@ restored file: %@.", self.logTag, item.relativeFilePath);
}
@ -497,6 +499,7 @@ NSString *const kOWSBackup_ImportDatabaseKeySpec = @"kOWSBackup_ImportDatabaseKe
@"Indicates that the backup database is being restored.")
progress:@(count / (CGFloat)self.databaseItems.count)];
@autoreleasepool {
NSData *_Nullable compressedData =
[self.backupIO decryptFileAsData:item.downloadFilePath encryptionKey:item.encryptionKey];
if (!compressedData) {
@ -553,6 +556,7 @@ NSString *const kOWSBackup_ImportDatabaseKeySpec = @"kOWSBackup_ImportDatabaseKe
restoredEntityCounts[collection] = @(restoredEntityCount + 1);
}
}
}
}];
if (self.isComplete || aborted) {

Loading…
Cancel
Save