diff --git a/Signal/src/ViewControllers/DebugUI/DebugUIDiskUsage.m b/Signal/src/ViewControllers/DebugUI/DebugUIDiskUsage.m index 115ebbf70..9d74bbfbc 100644 --- a/Signal/src/ViewControllers/DebugUI/DebugUIDiskUsage.m +++ b/Signal/src/ViewControllers/DebugUI/DebugUIDiskUsage.m @@ -5,6 +5,7 @@ #import "DebugUIDiskUsage.h" #import "OWSTableViewController.h" #import "Signal-Swift.h" +#import #import #import #import @@ -38,11 +39,11 @@ NS_ASSUME_NONNULL_BEGIN items:@[ [OWSTableItem itemWithTitle:@"Audit & Log" actionBlock:^{ - [DebugUIDiskUsage auditWithoutCleanup]; + [OWSOrphanedDataCleaner auditAsync]; }], [OWSTableItem itemWithTitle:@"Audit & Clean Up" actionBlock:^{ - [DebugUIDiskUsage auditWithCleanup]; + [OWSOrphanedDataCleaner auditAndCleanupAsync]; }], [OWSTableItem itemWithTitle:@"Save All Attachments" actionBlock:^{ @@ -55,173 +56,6 @@ NS_ASSUME_NONNULL_BEGIN ]]; } -+ (void)auditWithoutCleanup -{ - [self auditAndCleanup:NO]; -} - -+ (void)auditWithCleanup -{ - [self auditAndCleanup:YES]; -} - -+ (void)auditAndCleanup:(BOOL)shouldCleanup -{ - NSString *attachmentsFolder = [TSAttachmentStream attachmentsFolder]; - DDLogError(@"attachmentsFolder: %@", attachmentsFolder); - - __block int fileCount = 0; - __block long long totalFileSize = 0; - NSMutableSet *diskFilePaths = [NSMutableSet new]; - __unsafe_unretained __block void (^visitAttachmentFilesRecursable)(NSString *); - void (^visitAttachmentFiles)(NSString *); - visitAttachmentFiles = ^(NSString *dirPath) { - NSError *error; - NSArray *fileNames = - [[NSFileManager defaultManager] contentsOfDirectoryAtPath:dirPath error:&error]; - if (error) { - OWSFail(@"contentsOfDirectoryAtPath error: %@", error); - return; - } - for (NSString *fileName in fileNames) { - NSString *filePath = [dirPath stringByAppendingPathComponent:fileName]; - BOOL isDirectory; - [[NSFileManager defaultManager] fileExistsAtPath:filePath isDirectory:&isDirectory]; - if (isDirectory) { - visitAttachmentFilesRecursable(filePath); - } else { - NSNumber *fileSize = - [[NSFileManager defaultManager] attributesOfItemAtPath:filePath error:&error][NSFileSize]; - if (error) { - OWSFail(@"attributesOfItemAtPath: %@ error: %@", filePath, error); - continue; - } - totalFileSize += fileSize.longLongValue; - fileCount++; - [diskFilePaths addObject:filePath]; - } - } - }; - visitAttachmentFilesRecursable = visitAttachmentFiles; - visitAttachmentFiles(attachmentsFolder); - - __block int attachmentStreamCount = 0; - NSMutableSet *attachmentFilePaths = [NSMutableSet new]; - NSMutableSet *attachmentIds = [NSMutableSet new]; - TSStorageManager *storageManager = [TSStorageManager sharedManager]; - [storageManager.newDatabaseConnection readWithBlock:^(YapDatabaseReadTransaction *_Nonnull transaction) { - [transaction enumerateKeysAndObjectsInCollection:TSAttachmentStream.collection - usingBlock:^(NSString *key, TSAttachment *attachment, BOOL *stop) { - [attachmentIds addObject:attachment.uniqueId]; - if (![attachment isKindOfClass:[TSAttachmentStream class]]) { - return; - } - TSAttachmentStream *attachmentStream - = (TSAttachmentStream *)attachment; - attachmentStreamCount++; - NSString *_Nullable filePath = [attachmentStream filePath]; - OWSAssert(filePath); - [attachmentFilePaths addObject:filePath]; - }]; - }]; - - DDLogError(@"fileCount: %d", fileCount); - DDLogError(@"totalFileSize: %lld", totalFileSize); - DDLogError(@"attachmentStreams: %d", attachmentStreamCount); - DDLogError(@"attachmentStreams with file paths: %zd", attachmentFilePaths.count); - - NSMutableSet *orphanDiskFilePaths = [diskFilePaths mutableCopy]; - [orphanDiskFilePaths minusSet:attachmentFilePaths]; - NSMutableSet *missingAttachmentFilePaths = [attachmentFilePaths mutableCopy]; - [missingAttachmentFilePaths minusSet:diskFilePaths]; - - DDLogError(@"orphan disk file paths: %zd", orphanDiskFilePaths.count); - DDLogError(@"missing attachment file paths: %zd", missingAttachmentFilePaths.count); - - [self printPaths:orphanDiskFilePaths.allObjects label:@"orphan disk file paths"]; - [self printPaths:missingAttachmentFilePaths.allObjects label:@"missing attachment file paths"]; - - NSMutableSet *threadIds = [NSMutableSet new]; - [storageManager.newDatabaseConnection readWithBlock:^(YapDatabaseReadTransaction *_Nonnull transaction) { - [transaction enumerateKeysInCollection:TSThread.collection - usingBlock:^(NSString *_Nonnull key, BOOL *_Nonnull stop) { - [threadIds addObject:key]; - }]; - }]; - - NSMutableSet *orphanInteractions = [NSMutableSet new]; - NSMutableSet *messageAttachmentIds = [NSMutableSet new]; - [storageManager.newDatabaseConnection readWithBlock:^(YapDatabaseReadTransaction *_Nonnull transaction) { - [transaction enumerateKeysAndObjectsInCollection:TSMessage.collection - usingBlock:^(NSString *key, TSInteraction *interaction, BOOL *stop) { - if (![threadIds containsObject:interaction.uniqueThreadId]) { - [orphanInteractions addObject:interaction]; - } - - if (![interaction isKindOfClass:[TSMessage class]]) { - return; - } - TSMessage *message = (TSMessage *)interaction; - if (message.attachmentIds.count > 0) { - [messageAttachmentIds addObjectsFromArray:message.attachmentIds]; - } - }]; - }]; - - DDLogError(@"attachmentIds: %zd", attachmentIds.count); - DDLogError(@"messageAttachmentIds: %zd", messageAttachmentIds.count); - - NSMutableSet *orphanAttachmentIds = [attachmentIds mutableCopy]; - [orphanAttachmentIds minusSet:messageAttachmentIds]; - NSMutableSet *missingAttachmentIds = [messageAttachmentIds mutableCopy]; - [missingAttachmentIds minusSet:attachmentIds]; - - DDLogError(@"orphan attachmentIds: %zd", orphanAttachmentIds.count); - DDLogError(@"missing attachmentIds: %zd", missingAttachmentIds.count); - - DDLogError(@"orphan interactions: %zd", orphanInteractions.count); - - if (shouldCleanup) { - [storageManager.newDatabaseConnection - readWriteWithBlock:^(YapDatabaseReadWriteTransaction *_Nonnull transaction) { - for (TSInteraction *interaction in orphanInteractions) { - [interaction removeWithTransaction:transaction]; - } - for (NSString *attachmentId in orphanAttachmentIds) { - TSAttachment *attachment = [TSAttachment fetchObjectWithUniqueID:attachmentId]; - OWSAssert(attachment); - if (![attachment isKindOfClass:[TSAttachmentStream class]]) { - continue; - } - TSAttachmentStream *attachmentStream = (TSAttachmentStream *)attachment; - // Don't delete attachments which were created in the last N minutes. - const NSTimeInterval kMinimumOrphanAttachmentAge = 2 * 60.f; - if (fabs([attachmentStream.creationTimestamp timeIntervalSinceNow]) < kMinimumOrphanAttachmentAge) { - DDLogInfo(@"Skipping orphan attachment due to age: %f", - fabs([attachmentStream.creationTimestamp timeIntervalSinceNow])); - continue; - } - [attachmentStream removeWithTransaction:transaction]; - } - }]; - - for (NSString *filePath in orphanDiskFilePaths) { - NSError *error; - [[NSFileManager defaultManager] removeItemAtPath:filePath error:&error]; - if (error) { - OWSFail(@"Could not remove orphan file at: %@", filePath); - } - } - } -} - -+ (void)printPaths:(NSArray *)paths label:(NSString *)label -{ - for (NSString *path in [paths sortedArrayUsingSelector:@selector(compare:)]) { - DDLogError(@"%@: %@", label, path); - } -} - + (void)saveAllAttachments { TSStorageManager *storageManager = [TSStorageManager sharedManager]; diff --git a/Signal/src/environment/VersionMigrations.m b/Signal/src/environment/VersionMigrations.m index e9a4587a5..21b2d8bba 100644 --- a/Signal/src/environment/VersionMigrations.m +++ b/Signal/src/environment/VersionMigrations.m @@ -80,16 +80,16 @@ [self clearBloomFilterCache]; } - if ([self isVersion:previousVersion atLeast:@"2.0.0" andLessThan:@"2.4.1"] && [TSAccountManager isRegistered]) { - // Cleaning orphaned data can take a while, so let's run it in the background. - // This means this migration is not resiliant to failures - we'll only run it once - // regardless of its success. - dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ - DDLogInfo(@"OWSMigration: beginning removing orphaned data."); - [[OWSOrphanedDataCleaner new] removeOrphanedData]; - DDLogInfo(@"OWSMigration: completed removing orphaned data."); - }); - } +#ifdef DEBUG + // A bug in orphan cleanup could be disastrous so let's only + // run it in DEBUG builds for a few releases. + // + // TODO: Release to production once we have analytics. + // TODO: Orphan cleanup is somewhat expensive - not least in doing a bunch + // of disk access. We might want to only run it "once per version" + // or something like that in production. + [OWSOrphanedDataCleaner auditAndCleanupAsync]; +#endif [[[OWSDatabaseMigrationRunner alloc] initWithStorageManager:[TSStorageManager sharedManager]] runAllOutstanding]; }