Run orphan cleanup on startup.

// FREEBIE
pull/1/head
Matthew Chen 8 years ago
parent 4b7924cc72
commit 69ba2811db

@ -5,6 +5,7 @@
#import "DebugUIDiskUsage.h"
#import "OWSTableViewController.h"
#import "Signal-Swift.h"
#import <SignalServiceKit/OWSOrphanedDataCleaner.h>
#import <SignalServiceKit/TSDatabaseView.h>
#import <SignalServiceKit/TSInteraction.h>
#import <SignalServiceKit/TSStorageManager.h>
@ -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<NSString *> *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<NSString *> *attachmentFilePaths = [NSMutableSet new];
NSMutableSet<NSString *> *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<NSString *> *orphanDiskFilePaths = [diskFilePaths mutableCopy];
[orphanDiskFilePaths minusSet:attachmentFilePaths];
NSMutableSet<NSString *> *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<TSInteraction *> *orphanInteractions = [NSMutableSet new];
NSMutableSet<NSString *> *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<NSString *> *orphanAttachmentIds = [attachmentIds mutableCopy];
[orphanAttachmentIds minusSet:messageAttachmentIds];
NSMutableSet<NSString *> *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<NSString *> *)paths label:(NSString *)label
{
for (NSString *path in [paths sortedArrayUsingSelector:@selector(compare:)]) {
DDLogError(@"%@: %@", label, path);
}
}
+ (void)saveAllAttachments
{
TSStorageManager *storageManager = [TSStorageManager sharedManager];

@ -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];
}

Loading…
Cancel
Save