diff --git a/Signal/src/ViewControllers/ConversationView/ConversationViewController.m b/Signal/src/ViewControllers/ConversationView/ConversationViewController.m index 0aafdd920..d8df0518c 100644 --- a/Signal/src/ViewControllers/ConversationView/ConversationViewController.m +++ b/Signal/src/ViewControllers/ConversationView/ConversationViewController.m @@ -2884,7 +2884,7 @@ typedef enum : NSUInteger { OWSAssert(type); OWSAssert(filename); - DataSource *_Nullable dataSource = [DataSourcePath dataSourceWithURL:url]; + DataSource *_Nullable dataSource = [DataSourcePath dataSourceWithURL:url shouldDeleteOnDeallocation:NO]; if (!dataSource) { OWSFail(@"%@ attachment data was unexpectedly empty for picked document url: %@", self.logTag, url); @@ -3176,14 +3176,6 @@ typedef enum : NSUInteger { }]; } -- (NSURL *)videoTempFolder -{ - NSString *temporaryDirectory = NSTemporaryDirectory(); - NSString *videoDirPath = [temporaryDirectory stringByAppendingPathComponent:@"videos"]; - [OWSFileSystem ensureDirectoryExists:videoDirPath]; - return [NSURL fileURLWithPath:videoDirPath]; -} - - (void)sendQualityAdjustedAttachmentForVideo:(NSURL *)movieURL filename:(NSString *)filename skipApprovalDialog:(BOOL)skipApprovalDialog @@ -3194,7 +3186,8 @@ typedef enum : NSUInteger { presentFromViewController:self canCancel:YES backgroundBlock:^(ModalActivityIndicatorViewController *modalActivityIndicator) { - DataSource *dataSource = [DataSourcePath dataSourceWithURL:movieURL]; + DataSource *dataSource = + [DataSourcePath dataSourceWithURL:movieURL shouldDeleteOnDeallocation:NO]; dataSource.sourceFilename = filename; VideoCompressionResult *compressionResult = [SignalAttachment compressVideoAsMp4WithDataSource:dataSource @@ -3675,7 +3668,8 @@ typedef enum : NSUInteger { return; } - DataSource *_Nullable dataSource = [DataSourcePath dataSourceWithURL:self.audioRecorder.url]; + DataSource *_Nullable dataSource = + [DataSourcePath dataSourceWithURL:self.audioRecorder.url shouldDeleteOnDeallocation:YES]; self.audioRecorder = nil; if (!dataSource) { @@ -3687,8 +3681,6 @@ typedef enum : NSUInteger { NSString *filename = [NSLocalizedString(@"VOICE_MESSAGE_FILE_NAME", @"Filename for voice messages.") stringByAppendingPathExtension:@"m4a"]; [dataSource setSourceFilename:filename]; - // Remove temporary file when complete. - [dataSource setShouldDeleteOnDeallocation]; SignalAttachment *attachment = [SignalAttachment voiceMessageAttachmentWithDataSource:dataSource dataUTI:(NSString *)kUTTypeMPEG4Audio]; DDLogVerbose(@"%@ voice memo duration: %f, file size: %zd", self.logTag, durationSeconds, [dataSource dataLength]); @@ -3948,9 +3940,8 @@ typedef enum : NSUInteger { if (newGroupModel.groupImage) { NSData *data = UIImagePNGRepresentation(newGroupModel.groupImage); DataSource *_Nullable dataSource = [DataSourceValue dataSourceWithData:data fileExtension:@"png"]; - [self.messageSender enqueueAttachment:dataSource + [self.messageSender enqueueTemporaryAttachment:dataSource contentType:OWSMimeTypeImagePng - sourceFilename:nil inMessage:message success:^{ DDLogDebug(@"%@ Successfully sent group update with avatar", self.logTag); diff --git a/Signal/src/ViewControllers/DebugUI/DebugUIMessages.m b/Signal/src/ViewControllers/DebugUI/DebugUIMessages.m index d2609948c..7648b91c8 100644 --- a/Signal/src/ViewControllers/DebugUI/DebugUIMessages.m +++ b/Signal/src/ViewControllers/DebugUI/DebugUIMessages.m @@ -351,7 +351,7 @@ NS_ASSUME_NONNULL_BEGIN OWSMessageSender *messageSender = [Environment current].messageSender; NSString *filename = [filePath lastPathComponent]; NSString *utiType = [MIMETypeUtil utiTypeForFileExtension:filename.pathExtension]; - DataSource *_Nullable dataSource = [DataSourcePath dataSourceWithFilePath:filePath]; + DataSource *_Nullable dataSource = [DataSourcePath dataSourceWithFilePath:filePath shouldDeleteOnDeallocation:NO]; [dataSource setSourceFilename:filename]; SignalAttachment *attachment = [SignalAttachment attachmentWithDataSource:dataSource dataUTI:utiType imageQuality:TSImageQualityOriginal]; @@ -1701,7 +1701,7 @@ NS_ASSUME_NONNULL_BEGIN NSString *filename = [filePath lastPathComponent]; NSString *utiType = [MIMETypeUtil utiTypeForFileExtension:filename.pathExtension]; - DataSource *_Nullable dataSource = [DataSourcePath dataSourceWithFilePath:filePath]; + DataSource *_Nullable dataSource = [DataSourcePath dataSourceWithFilePath:filePath shouldDeleteOnDeallocation:NO]; [dataSource setSourceFilename:filename]; SignalAttachment *attachment = [SignalAttachment attachmentWithDataSource:dataSource dataUTI:utiType imageQuality:TSImageQualityOriginal]; @@ -4593,7 +4593,8 @@ typedef OWSContact * (^OWSContactBlock)(YapDatabaseReadWriteTransaction *transac OWSAssert(transaction); if (isAttachmentDownloaded) { - DataSource *dataSource = [DataSourcePath dataSourceWithFilePath:fakeAssetLoader.filePath]; + DataSource *dataSource = + [DataSourcePath dataSourceWithFilePath:fakeAssetLoader.filePath shouldDeleteOnDeallocation:NO]; NSString *filename = dataSource.sourceFilename; // To support "fake missing" attachments, we sometimes lie about the // length of the data. diff --git a/Signal/src/ViewControllers/DebugUI/DebugUIMisc.m b/Signal/src/ViewControllers/DebugUI/DebugUIMisc.m index 1b3229e41..fb6f36c86 100644 --- a/Signal/src/ViewControllers/DebugUI/DebugUIMisc.m +++ b/Signal/src/ViewControllers/DebugUI/DebugUIMisc.m @@ -233,7 +233,7 @@ NS_ASSUME_NONNULL_BEGIN OWSMessageSender *messageSender = [Environment current].messageSender; NSString *utiType = [MIMETypeUtil utiTypeForFileExtension:fileName.pathExtension]; - DataSource *_Nullable dataSource = [DataSourcePath dataSourceWithFilePath:filePath]; + DataSource *_Nullable dataSource = [DataSourcePath dataSourceWithFilePath:filePath shouldDeleteOnDeallocation:YES]; [dataSource setSourceFilename:fileName]; SignalAttachment *attachment = [SignalAttachment attachmentWithDataSource:dataSource dataUTI:utiType]; NSData *databasePassword = [OWSPrimaryStorage.sharedManager databasePassword]; @@ -262,7 +262,7 @@ NS_ASSUME_NONNULL_BEGIN OWSMessageSender *messageSender = [Environment current].messageSender; NSString *utiType = [MIMETypeUtil utiTypeForFileExtension:fileName.pathExtension]; - DataSource *_Nullable dataSource = [DataSourcePath dataSourceWithFilePath:filePath]; + DataSource *_Nullable dataSource = [DataSourcePath dataSourceWithFilePath:filePath shouldDeleteOnDeallocation:YES]; [dataSource setSourceFilename:fileName]; SignalAttachment *attachment = [SignalAttachment attachmentWithDataSource:dataSource dataUTI:utiType]; if (!attachment || [attachment hasError]) { diff --git a/Signal/src/ViewControllers/GifPicker/GifPickerViewController.swift b/Signal/src/ViewControllers/GifPicker/GifPickerViewController.swift index 537fc9064..b4bbf8f4c 100644 --- a/Signal/src/ViewControllers/GifPicker/GifPickerViewController.swift +++ b/Signal/src/ViewControllers/GifPicker/GifPickerViewController.swift @@ -370,7 +370,8 @@ class GifPickerViewController: OWSViewController, UISearchBarDelegate, UICollect } let filePath = asset.filePath - guard let dataSource = DataSourcePath.dataSource(withFilePath: filePath) else { + guard let dataSource = DataSourcePath.dataSource(withFilePath: filePath, + shouldDeleteOnDeallocation: false) else { owsFail("\(strongSelf.TAG) couldn't load asset.") return } diff --git a/Signal/src/ViewControllers/NewGroupViewController.m b/Signal/src/ViewControllers/NewGroupViewController.m index 901ea951e..26d7681f4 100644 --- a/Signal/src/ViewControllers/NewGroupViewController.m +++ b/Signal/src/ViewControllers/NewGroupViewController.m @@ -490,12 +490,11 @@ const NSUInteger kNewGroupViewControllerAvatarWidth = 68; NSData *data = UIImagePNGRepresentation(model.groupImage); DataSource *_Nullable dataSource = [DataSourceValue dataSourceWithData:data fileExtension:@"png"]; - [self.messageSender enqueueAttachment:dataSource - contentType:OWSMimeTypeImagePng - sourceFilename:nil - inMessage:message - success:successHandler - failure:failureHandler]; + [self.messageSender enqueueTemporaryAttachment:dataSource + contentType:OWSMimeTypeImagePng + inMessage:message + success:successHandler + failure:failureHandler]; } else { [self.messageSender enqueueMessage:message success:successHandler failure:failureHandler]; } diff --git a/SignalMessaging/attachments/SignalAttachment.swift b/SignalMessaging/attachments/SignalAttachment.swift index 147b18ae2..1382e5385 100644 --- a/SignalMessaging/attachments/SignalAttachment.swift +++ b/SignalMessaging/attachments/SignalAttachment.swift @@ -980,7 +980,8 @@ public class SignalAttachment: NSObject { let baseFilename = dataSource.sourceFilename let mp4Filename = baseFilename?.filenameWithoutExtension.appendingFileExtension("mp4") - guard let dataSource = DataSourcePath.dataSource(with: exportURL) else { + guard let dataSource = DataSourcePath.dataSource(with: exportURL, + shouldDeleteOnDeallocation: true) else { owsFail("Failed to build data source for exported video URL") let attachment = SignalAttachment(dataSource: DataSourceValue.emptyDataSource(), dataUTI: dataUTI) attachment.error = .couldNotConvertToMpeg4 @@ -988,7 +989,6 @@ public class SignalAttachment: NSObject { return } - dataSource.setShouldDeleteOnDeallocation() dataSource.sourceFilename = mp4Filename let attachment = SignalAttachment(dataSource: dataSource, dataUTI: kUTTypeMPEG4 as String) diff --git a/SignalMessaging/contacts/OWSContactsSyncing.m b/SignalMessaging/contacts/OWSContactsSyncing.m index f2285ab09..dbd5f4fd0 100644 --- a/SignalMessaging/contacts/OWSContactsSyncing.m +++ b/SignalMessaging/contacts/OWSContactsSyncing.m @@ -126,7 +126,7 @@ NSString *const kOWSPrimaryStorageOWSContactsSyncingLastMessageKey __block NSData *messageData; __block NSData *lastMessageData; - [self.editingDatabaseConnection readWithBlock:^(YapDatabaseReadTransaction *_Nonnull transaction) { + [self.editingDatabaseConnection readWithBlock:^(YapDatabaseReadTransaction *transaction) { messageData = [syncContactsMessage buildPlainTextAttachmentDataWithTransaction:transaction]; lastMessageData = [transaction objectForKey:kOWSPrimaryStorageOWSContactsSyncingLastMessageKey inCollection:kOWSPrimaryStorageOWSContactsSyncingCollection]; diff --git a/SignalServiceKit/src/Messages/Interactions/TSOutgoingMessage.m b/SignalServiceKit/src/Messages/Interactions/TSOutgoingMessage.m index 24259c7b7..8f1f96a06 100644 --- a/SignalServiceKit/src/Messages/Interactions/TSOutgoingMessage.m +++ b/SignalServiceKit/src/Messages/Interactions/TSOutgoingMessage.m @@ -354,6 +354,41 @@ NSString *NSStringForOutgoingMessageRecipientState(OWSOutgoingMessageRecipientSt return self; } +- (void)dealloc +{ + [self removeTemporaryAttachments]; +} + +// Each message has the responsibility for eagerly cleaning up its attachments. +// Normally this is done in [TSMessage removeWithTransaction], but that doesn't +// apply for "transient", unsaved messages (i.e. shouldBeSaved == NO). These +// messages should clean up their attachments upon deallocation. +- (void)removeTemporaryAttachments +{ + if (self.shouldBeSaved) { + // Message is not transient; no need to clean up attachments. + return; + } + NSArray *_Nullable attachmentIds = self.attachmentIds; + if (attachmentIds.count < 1) { + return; + } + [self.dbReadWriteConnection asyncReadWriteWithBlock:^(YapDatabaseReadWriteTransaction *transaction) { + for (NSString *attachmentId in attachmentIds) { + // We need to fetch each attachment, since [TSAttachment removeWithTransaction:] does important work. + TSAttachment *_Nullable attachment = + [TSAttachment fetchObjectWithUniqueID:attachmentId transaction:transaction]; + if (!attachment) { + OWSCFail(@"%@ couldn't load interaction's attachment for deletion.", TSOutgoingMessage.logTag); + continue; + } + [attachment removeWithTransaction:transaction]; + }; + }]; +} + +#pragma mark - + - (TSOutgoingMessageState)messageState { TSOutgoingMessageState newMessageState = diff --git a/SignalServiceKit/src/Messages/OWSMessageManager.m b/SignalServiceKit/src/Messages/OWSMessageManager.m index 7becd7cf5..db16d84bd 100644 --- a/SignalServiceKit/src/Messages/OWSMessageManager.m +++ b/SignalServiceKit/src/Messages/OWSMessageManager.m @@ -825,9 +825,8 @@ NS_ASSUME_NONNULL_BEGIN if (gThread.groupModel.groupImage) { NSData *data = UIImagePNGRepresentation(gThread.groupModel.groupImage); DataSource *_Nullable dataSource = [DataSourceValue dataSourceWithData:data fileExtension:@"png"]; - [self.messageSender enqueueAttachment:dataSource + [self.messageSender enqueueTemporaryAttachment:dataSource contentType:OWSMimeTypeImagePng - sourceFilename:nil inMessage:message success:^{ DDLogDebug(@"%@ Successfully sent group update with avatar", self.logTag); diff --git a/SignalServiceKit/src/Util/DataSource.h b/SignalServiceKit/src/Util/DataSource.h index c65d693c8..74ba0fc25 100755 --- a/SignalServiceKit/src/Util/DataSource.h +++ b/SignalServiceKit/src/Util/DataSource.h @@ -29,10 +29,6 @@ NS_ASSUME_NONNULL_BEGIN // Returns YES on success. - (BOOL)writeToPath:(NSString *)dstFilePath; -// If called, this data source will try to delete its on-disk contents -// when it is deallocated. -- (void)setShouldDeleteOnDeallocation; - - (BOOL)isValidImage; @end @@ -57,9 +53,10 @@ NS_ASSUME_NONNULL_BEGIN @interface DataSourcePath : DataSource -+ (nullable DataSource *)dataSourceWithURL:(NSURL *)fileUrl; ++ (nullable DataSource *)dataSourceWithURL:(NSURL *)fileUrl shouldDeleteOnDeallocation:(BOOL)shouldDeleteOnDeallocation; -+ (nullable DataSource *)dataSourceWithFilePath:(NSString *)filePath; ++ (nullable DataSource *)dataSourceWithFilePath:(NSString *)filePath + shouldDeleteOnDeallocation:(BOOL)shouldDeleteOnDeallocation; @end diff --git a/SignalServiceKit/src/Util/DataSource.m b/SignalServiceKit/src/Util/DataSource.m index df21acf9f..fcebd4511 100755 --- a/SignalServiceKit/src/Util/DataSource.m +++ b/SignalServiceKit/src/Util/DataSource.m @@ -60,11 +60,6 @@ NS_ASSUME_NONNULL_BEGIN return NO; } -- (void)setShouldDeleteOnDeallocation -{ - self.shouldDeleteOnDeallocation = YES; -} - - (BOOL)isValidImage { NSString *_Nullable dataPath = [self dataPathIfOnDisk]; @@ -102,7 +97,7 @@ NS_ASSUME_NONNULL_BEGIN @property (nonatomic) NSString *fileExtension; // This property is lazy-populated. -@property (nonatomic) NSString *cachedFilePath; +@property (nonatomic, nullable) NSString *cachedFilePath; @end @@ -113,7 +108,7 @@ NS_ASSUME_NONNULL_BEGIN - (void)dealloc { if (self.shouldDeleteOnDeallocation) { - NSString *filePath = self.cachedFilePath; + NSString *_Nullable filePath = self.cachedFilePath; if (filePath) { dispatch_async(dispatch_get_main_queue(), ^{ NSError *error; @@ -126,7 +121,8 @@ NS_ASSUME_NONNULL_BEGIN } } -+ (nullable DataSource *)dataSourceWithData:(NSData *)data fileExtension:(NSString *)fileExtension ++ (nullable DataSource *)dataSourceWithData:(NSData *)data + fileExtension:(NSString *)fileExtension { OWSAssert(data); @@ -137,12 +133,12 @@ NS_ASSUME_NONNULL_BEGIN DataSourceValue *instance = [DataSourceValue new]; instance.dataValue = data; instance.fileExtension = fileExtension; - // Always try to clean up temp files created by this instance. - [instance setShouldDeleteOnDeallocation]; + instance.shouldDeleteOnDeallocation = YES; return instance; } -+ (nullable DataSource *)dataSourceWithData:(NSData *)data utiType:(NSString *)utiType ++ (nullable DataSource *)dataSourceWithData:(NSData *)data + utiType:(NSString *)utiType { NSString *fileExtension = [MIMETypeUtil fileExtensionForUTIType:utiType]; return [self dataSourceWithData:data fileExtension:fileExtension]; @@ -269,7 +265,7 @@ NS_ASSUME_NONNULL_BEGIN } } -+ (nullable DataSource *)dataSourceWithURL:(NSURL *)fileUrl ++ (nullable DataSource *)dataSourceWithURL:(NSURL *)fileUrl shouldDeleteOnDeallocation:(BOOL)shouldDeleteOnDeallocation { OWSAssert(fileUrl); @@ -278,10 +274,12 @@ NS_ASSUME_NONNULL_BEGIN } DataSourcePath *instance = [DataSourcePath new]; instance.filePath = fileUrl.path; + instance.shouldDeleteOnDeallocation = shouldDeleteOnDeallocation; return instance; } + (nullable DataSource *)dataSourceWithFilePath:(NSString *)filePath + shouldDeleteOnDeallocation:(BOOL)shouldDeleteOnDeallocation { OWSAssert(filePath); @@ -291,7 +289,7 @@ NS_ASSUME_NONNULL_BEGIN DataSourcePath *instance = [DataSourcePath new]; instance.filePath = filePath; - OWSAssert(!instance.shouldDeleteOnDeallocation); + instance.shouldDeleteOnDeallocation = shouldDeleteOnDeallocation; return instance; } diff --git a/SignalShareExtension/ShareViewController.swift b/SignalShareExtension/ShareViewController.swift index 12a65e588..932aa22e3 100644 --- a/SignalShareExtension/ShareViewController.swift +++ b/SignalShareExtension/ShareViewController.swift @@ -640,9 +640,11 @@ public class ShareViewController: UIViewController, ShareViewDelegate, SAEFailed // // NOTE: SharingThreadPickerViewController will try to unpack them // and send them as normal text messages if possible. - return DataSourcePath.dataSource(with: url) + return DataSourcePath.dataSource(with: url, + shouldDeleteOnDeallocation: false) } else { - guard let dataSource = DataSourcePath.dataSource(with: url) else { + guard let dataSource = DataSourcePath.dataSource(with: url, + shouldDeleteOnDeallocation: false) else { return nil }