|
|
|
@ -1,16 +1,11 @@
|
|
|
|
|
//
|
|
|
|
|
// Copyright (c) 2019 Open Whisper Systems. All rights reserved.
|
|
|
|
|
//
|
|
|
|
|
|
|
|
|
|
#import "TSAttachmentStream.h"
|
|
|
|
|
#import "MIMETypeUtil.h"
|
|
|
|
|
#import "NSData+Image.h"
|
|
|
|
|
#import "OWSFileSystem.h"
|
|
|
|
|
#import "TSAttachmentPointer.h"
|
|
|
|
|
#import <AVFoundation/AVFoundation.h>
|
|
|
|
|
#import <SignalCoreKit/Threading.h>
|
|
|
|
|
#import <SignalUtilitiesKit/SignalUtilitiesKit-Swift.h>
|
|
|
|
|
#import <YapDatabase/YapDatabase.h>
|
|
|
|
|
#import <SessionUtilitiesKit/SessionUtilitiesKit.h>
|
|
|
|
|
#import <SessionMessagingKit/SessionMessagingKit-Swift.h>
|
|
|
|
|
|
|
|
|
|
NS_ASSUME_NONNULL_BEGIN
|
|
|
|
|
|
|
|
|
@ -149,21 +144,17 @@ typedef void (^OWSLoadedThumbnailSuccess)(OWSLoadedThumbnail *loadedThumbnail);
|
|
|
|
|
sourceFilename:self.sourceFilename
|
|
|
|
|
inFolder:attachmentsFolder];
|
|
|
|
|
if (!filePath) {
|
|
|
|
|
OWSFailDebug(@"Could not generate path for attachment.");
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
if (![filePath hasPrefix:attachmentsFolder]) {
|
|
|
|
|
OWSFailDebug(@"Attachment paths should all be in the attachments folder.");
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
NSString *localRelativeFilePath = [filePath substringFromIndex:attachmentsFolder.length];
|
|
|
|
|
if (localRelativeFilePath.length < 1) {
|
|
|
|
|
OWSFailDebug(@"Empty local relative attachment paths.");
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
self.localRelativeFilePath = localRelativeFilePath;
|
|
|
|
|
OWSAssertDebug(self.originalFilePath);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#pragma mark - File Management
|
|
|
|
@ -173,7 +164,6 @@ typedef void (^OWSLoadedThumbnailSuccess)(OWSLoadedThumbnail *loadedThumbnail);
|
|
|
|
|
*error = nil;
|
|
|
|
|
NSString *_Nullable filePath = self.originalFilePath;
|
|
|
|
|
if (!filePath) {
|
|
|
|
|
OWSFailDebug(@"Missing path for attachment.");
|
|
|
|
|
return nil;
|
|
|
|
|
}
|
|
|
|
|
return [NSData dataWithContentsOfFile:filePath options:0 error:error];
|
|
|
|
@ -181,28 +171,20 @@ typedef void (^OWSLoadedThumbnailSuccess)(OWSLoadedThumbnail *loadedThumbnail);
|
|
|
|
|
|
|
|
|
|
- (BOOL)writeData:(NSData *)data error:(NSError **)error
|
|
|
|
|
{
|
|
|
|
|
OWSAssertDebug(data);
|
|
|
|
|
|
|
|
|
|
*error = nil;
|
|
|
|
|
NSString *_Nullable filePath = self.originalFilePath;
|
|
|
|
|
if (!filePath) {
|
|
|
|
|
OWSFailDebug(@"Missing path for attachment.");
|
|
|
|
|
return NO;
|
|
|
|
|
}
|
|
|
|
|
OWSLogDebug(@"Writing attachment to file: %@", filePath);
|
|
|
|
|
return [data writeToFile:filePath options:0 error:error];
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
- (BOOL)writeDataSource:(DataSource *)dataSource
|
|
|
|
|
{
|
|
|
|
|
OWSAssertDebug(dataSource);
|
|
|
|
|
|
|
|
|
|
NSString *_Nullable filePath = self.originalFilePath;
|
|
|
|
|
if (!filePath) {
|
|
|
|
|
OWSFailDebug(@"Missing path for attachment.");
|
|
|
|
|
return NO;
|
|
|
|
|
}
|
|
|
|
|
OWSLogDebug(@"Writing attachment to file: %@", filePath);
|
|
|
|
|
return [dataSource writeToPath:filePath];
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
@ -218,8 +200,6 @@ typedef void (^OWSLoadedThumbnailSuccess)(OWSLoadedThumbnail *loadedThumbnail);
|
|
|
|
|
|
|
|
|
|
+ (nullable NSError *)migrateToSharedData
|
|
|
|
|
{
|
|
|
|
|
OWSLogInfo(@"");
|
|
|
|
|
|
|
|
|
|
return [OWSFileSystem moveAppFilePath:self.legacyAttachmentsDirPath
|
|
|
|
|
sharedDataFilePath:self.sharedDataAttachmentsDirPath];
|
|
|
|
|
}
|
|
|
|
@ -239,7 +219,6 @@ typedef void (^OWSLoadedThumbnailSuccess)(OWSLoadedThumbnail *loadedThumbnail);
|
|
|
|
|
- (nullable NSString *)originalFilePath
|
|
|
|
|
{
|
|
|
|
|
if (!self.localRelativeFilePath) {
|
|
|
|
|
OWSFailDebug(@"Attachment missing local file path.");
|
|
|
|
|
return nil;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
@ -250,7 +229,6 @@ typedef void (^OWSLoadedThumbnailSuccess)(OWSLoadedThumbnail *loadedThumbnail);
|
|
|
|
|
{
|
|
|
|
|
NSString *filePath = self.originalFilePath;
|
|
|
|
|
if (!filePath) {
|
|
|
|
|
OWSFailDebug(@"Attachment missing local file path.");
|
|
|
|
|
return nil;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
@ -268,7 +246,6 @@ typedef void (^OWSLoadedThumbnailSuccess)(OWSLoadedThumbnail *loadedThumbnail);
|
|
|
|
|
- (NSString *)thumbnailsDirPath
|
|
|
|
|
{
|
|
|
|
|
if (!self.localRelativeFilePath) {
|
|
|
|
|
OWSFailDebug(@"Attachment missing local file path.");
|
|
|
|
|
return nil;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
@ -288,7 +265,6 @@ typedef void (^OWSLoadedThumbnailSuccess)(OWSLoadedThumbnail *loadedThumbnail);
|
|
|
|
|
{
|
|
|
|
|
NSString *_Nullable filePath = self.originalFilePath;
|
|
|
|
|
if (!filePath) {
|
|
|
|
|
OWSFailDebug(@"Missing path for attachment.");
|
|
|
|
|
return nil;
|
|
|
|
|
}
|
|
|
|
|
return [NSURL fileURLWithPath:filePath];
|
|
|
|
@ -300,30 +276,19 @@ typedef void (^OWSLoadedThumbnailSuccess)(OWSLoadedThumbnail *loadedThumbnail);
|
|
|
|
|
|
|
|
|
|
NSString *thumbnailsDirPath = self.thumbnailsDirPath;
|
|
|
|
|
if ([[NSFileManager defaultManager] fileExistsAtPath:thumbnailsDirPath]) {
|
|
|
|
|
BOOL success = [[NSFileManager defaultManager] removeItemAtPath:thumbnailsDirPath error:&error];
|
|
|
|
|
if (error || !success) {
|
|
|
|
|
OWSLogError(@"remove thumbnails dir failed with: %@", error);
|
|
|
|
|
}
|
|
|
|
|
[[NSFileManager defaultManager] removeItemAtPath:thumbnailsDirPath error:&error];
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
NSString *_Nullable legacyThumbnailPath = self.legacyThumbnailPath;
|
|
|
|
|
if (legacyThumbnailPath) {
|
|
|
|
|
BOOL success = [[NSFileManager defaultManager] removeItemAtPath:legacyThumbnailPath error:&error];
|
|
|
|
|
|
|
|
|
|
if (error || !success) {
|
|
|
|
|
OWSLogError(@"remove legacy thumbnail failed with: %@", error);
|
|
|
|
|
}
|
|
|
|
|
[[NSFileManager defaultManager] removeItemAtPath:legacyThumbnailPath error:&error];
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
NSString *_Nullable filePath = self.originalFilePath;
|
|
|
|
|
if (!filePath) {
|
|
|
|
|
OWSFailDebug(@"Missing path for attachment.");
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
BOOL success = [[NSFileManager defaultManager] removeItemAtPath:filePath error:&error];
|
|
|
|
|
if (error || !success) {
|
|
|
|
|
OWSLogError(@"remove file failed with: %@", error);
|
|
|
|
|
}
|
|
|
|
|
[[NSFileManager defaultManager] removeItemAtPath:filePath error:&error];
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
- (void)removeWithTransaction:(YapDatabaseReadWriteTransaction *)transaction
|
|
|
|
@ -353,13 +318,10 @@ typedef void (^OWSLoadedThumbnailSuccess)(OWSLoadedThumbnail *loadedThumbnail);
|
|
|
|
|
|
|
|
|
|
- (BOOL)isValidImage
|
|
|
|
|
{
|
|
|
|
|
OWSAssertDebug(self.isImage || self.isAnimated);
|
|
|
|
|
|
|
|
|
|
BOOL result;
|
|
|
|
|
BOOL didUpdateCache = NO;
|
|
|
|
|
@synchronized(self) {
|
|
|
|
|
if (!self.isValidImageCached) {
|
|
|
|
|
OWSLogVerbose(@"Updating isValidImageCached.");
|
|
|
|
|
self.isValidImageCached = @([NSData ows_isValidImageAtPath:self.originalFilePath
|
|
|
|
|
mimeType:self.contentType]);
|
|
|
|
|
didUpdateCache = YES;
|
|
|
|
@ -378,13 +340,10 @@ typedef void (^OWSLoadedThumbnailSuccess)(OWSLoadedThumbnail *loadedThumbnail);
|
|
|
|
|
|
|
|
|
|
- (BOOL)isValidVideo
|
|
|
|
|
{
|
|
|
|
|
OWSAssertDebug(self.isVideo);
|
|
|
|
|
|
|
|
|
|
BOOL result;
|
|
|
|
|
BOOL didUpdateCache = NO;
|
|
|
|
|
@synchronized(self) {
|
|
|
|
|
if (!self.isValidVideoCached) {
|
|
|
|
|
OWSLogVerbose(@"Updating isValidVideoCached.");
|
|
|
|
|
self.isValidVideoCached = @([OWSMediaUtils isValidVideoWithPath:self.originalFilePath]);
|
|
|
|
|
didUpdateCache = YES;
|
|
|
|
|
}
|
|
|
|
@ -423,16 +382,13 @@ typedef void (^OWSLoadedThumbnailSuccess)(OWSLoadedThumbnail *loadedThumbnail);
|
|
|
|
|
- (nullable NSData *)validStillImageData
|
|
|
|
|
{
|
|
|
|
|
if ([self isVideo]) {
|
|
|
|
|
OWSFailDebug(@"isVideo was unexpectedly true");
|
|
|
|
|
return nil;
|
|
|
|
|
}
|
|
|
|
|
if ([self isAnimated]) {
|
|
|
|
|
OWSFailDebug(@"isAnimated was unexpectedly true");
|
|
|
|
|
return nil;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (![NSData ows_isValidImageAtPath:self.originalFilePath mimeType:self.contentType]) {
|
|
|
|
|
OWSFailDebug(@"skipping invalid image");
|
|
|
|
|
return nil;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
@ -452,7 +408,6 @@ typedef void (^OWSLoadedThumbnailSuccess)(OWSLoadedThumbnail *loadedThumbnail);
|
|
|
|
|
maxDimension:ThumbnailDimensionPointsLarge()
|
|
|
|
|
error:&error];
|
|
|
|
|
if (error || !image) {
|
|
|
|
|
OWSLogError(@"Could not create video still: %@.", error);
|
|
|
|
|
return nil;
|
|
|
|
|
}
|
|
|
|
|
return image;
|
|
|
|
@ -468,15 +423,11 @@ typedef void (^OWSLoadedThumbnailSuccess)(OWSLoadedThumbnail *loadedThumbnail);
|
|
|
|
|
[fileManager contentsOfDirectoryAtURL:fileURL includingPropertiesForKeys:nil options:0 error:&error];
|
|
|
|
|
|
|
|
|
|
if (error) {
|
|
|
|
|
OWSFailDebug(@"failed to get contents of attachments folder: %@ with error: %@", self.attachmentsFolder, error);
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
for (NSURL *url in contents) {
|
|
|
|
|
[fileManager removeItemAtURL:url error:&error];
|
|
|
|
|
if (error) {
|
|
|
|
|
OWSFailDebug(@"failed to remove item at path: %@ with error: %@", url, error);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
@ -529,8 +480,6 @@ typedef void (^OWSLoadedThumbnailSuccess)(OWSLoadedThumbnail *loadedThumbnail);
|
|
|
|
|
|
|
|
|
|
- (CGSize)cachedMediaSize
|
|
|
|
|
{
|
|
|
|
|
OWSAssertDebug(self.shouldHaveImageSize);
|
|
|
|
|
|
|
|
|
|
@synchronized(self) {
|
|
|
|
|
if (self.cachedImageWidth && self.cachedImageHeight) {
|
|
|
|
|
return CGSizeMake(self.cachedImageWidth.floatValue, self.cachedImageHeight.floatValue);
|
|
|
|
@ -544,8 +493,6 @@ typedef void (^OWSLoadedThumbnailSuccess)(OWSLoadedThumbnail *loadedThumbnail);
|
|
|
|
|
|
|
|
|
|
- (void)applyChangeAsyncToLatestCopyWithChangeBlock:(void (^)(TSAttachmentStream *))changeBlock
|
|
|
|
|
{
|
|
|
|
|
OWSAssertDebug(changeBlock);
|
|
|
|
|
|
|
|
|
|
[LKStorage writeWithBlock:^(YapDatabaseReadWriteTransaction *transaction) {
|
|
|
|
|
NSString *collection = [TSAttachmentStream collection];
|
|
|
|
|
TSAttachmentStream *latestInstance = [transaction objectForKey:self.uniqueId inCollection:collection];
|
|
|
|
@ -555,9 +502,8 @@ typedef void (^OWSLoadedThumbnailSuccess)(OWSLoadedThumbnail *loadedThumbnail);
|
|
|
|
|
// _very_ rare.
|
|
|
|
|
//
|
|
|
|
|
// An exception is incoming group avatar updates which we don't ever save.
|
|
|
|
|
OWSLogWarn(@"Attachment not yet saved.");
|
|
|
|
|
} else if (![latestInstance isKindOfClass:[TSAttachmentStream class]]) {
|
|
|
|
|
OWSFailDebug(@"Attachment has unexpected type: %@", latestInstance.class);
|
|
|
|
|
// Shouldn't occur
|
|
|
|
|
} else {
|
|
|
|
|
changeBlock(latestInstance);
|
|
|
|
|
|
|
|
|
@ -570,9 +516,6 @@ typedef void (^OWSLoadedThumbnailSuccess)(OWSLoadedThumbnail *loadedThumbnail);
|
|
|
|
|
|
|
|
|
|
- (CGFloat)calculateAudioDurationSeconds
|
|
|
|
|
{
|
|
|
|
|
OWSAssertIsOnMainThread();
|
|
|
|
|
OWSAssertDebug([self isAudio]);
|
|
|
|
|
|
|
|
|
|
NSError *error;
|
|
|
|
|
AVAudioPlayer *audioPlayer = [[AVAudioPlayer alloc] initWithContentsOfURL:self.originalMediaURL error:&error];
|
|
|
|
|
if (error && [error.domain isEqualToString:NSOSStatusErrorDomain]
|
|
|
|
@ -583,15 +526,12 @@ typedef void (^OWSLoadedThumbnailSuccess)(OWSLoadedThumbnail *loadedThumbnail);
|
|
|
|
|
if (!error) {
|
|
|
|
|
return (CGFloat)[audioPlayer duration];
|
|
|
|
|
} else {
|
|
|
|
|
OWSLogError(@"Could not find audio duration: %@", self.originalMediaURL);
|
|
|
|
|
return 0;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
- (CGFloat)audioDurationSeconds
|
|
|
|
|
{
|
|
|
|
|
OWSAssertIsOnMainThread();
|
|
|
|
|
|
|
|
|
|
if (self.cachedAudioDurationSeconds) {
|
|
|
|
|
return self.cachedAudioDurationSeconds.floatValue;
|
|
|
|
|
}
|
|
|
|
@ -686,7 +626,6 @@ typedef void (^OWSLoadedThumbnailSuccess)(OWSLoadedThumbnail *loadedThumbnail);
|
|
|
|
|
if ([[NSFileManager defaultManager] fileExistsAtPath:thumbnailPath]) {
|
|
|
|
|
UIImage *_Nullable image = [UIImage imageWithContentsOfFile:thumbnailPath];
|
|
|
|
|
if (!image) {
|
|
|
|
|
OWSFailDebug(@"couldn't load image.");
|
|
|
|
|
// Any time we return nil from this method we have to call the failure handler
|
|
|
|
|
// or else the caller waits for an async thumbnail
|
|
|
|
|
failure();
|
|
|
|
@ -699,7 +638,6 @@ typedef void (^OWSLoadedThumbnailSuccess)(OWSLoadedThumbnail *loadedThumbnail);
|
|
|
|
|
thumbnailDimensionPoints:thumbnailDimensionPoints
|
|
|
|
|
success:success
|
|
|
|
|
failure:^(NSError *error) {
|
|
|
|
|
OWSLogError(@"Failed to create thumbnail: %@", error);
|
|
|
|
|
failure();
|
|
|
|
|
}];
|
|
|
|
|
return nil;
|
|
|
|
@ -737,7 +675,6 @@ typedef void (^OWSLoadedThumbnailSuccess)(OWSLoadedThumbnail *loadedThumbnail);
|
|
|
|
|
{
|
|
|
|
|
OWSLoadedThumbnail *_Nullable loadedThumbnail = [self loadedThumbnailSmallSync];
|
|
|
|
|
if (!loadedThumbnail) {
|
|
|
|
|
OWSLogInfo(@"Couldn't load small thumbnail sync.");
|
|
|
|
|
return nil;
|
|
|
|
|
}
|
|
|
|
|
return loadedThumbnail.image;
|
|
|
|
@ -747,13 +684,11 @@ typedef void (^OWSLoadedThumbnailSuccess)(OWSLoadedThumbnail *loadedThumbnail);
|
|
|
|
|
{
|
|
|
|
|
OWSLoadedThumbnail *_Nullable loadedThumbnail = [self loadedThumbnailSmallSync];
|
|
|
|
|
if (!loadedThumbnail) {
|
|
|
|
|
OWSLogInfo(@"Couldn't load small thumbnail sync.");
|
|
|
|
|
return nil;
|
|
|
|
|
}
|
|
|
|
|
NSError *error;
|
|
|
|
|
NSData *_Nullable data = [loadedThumbnail dataAndReturnError:&error];
|
|
|
|
|
if (error || !data) {
|
|
|
|
|
OWSFailDebug(@"Couldn't load thumbnail data: %@", error);
|
|
|
|
|
return nil;
|
|
|
|
|
}
|
|
|
|
|
return data;
|
|
|
|
@ -769,7 +704,7 @@ typedef void (^OWSLoadedThumbnailSuccess)(OWSLoadedThumbnail *loadedThumbnail);
|
|
|
|
|
NSArray<NSString *> *_Nullable fileNames =
|
|
|
|
|
[[NSFileManager defaultManager] contentsOfDirectoryAtPath:thumbnailsDirPath error:&error];
|
|
|
|
|
if (error || !fileNames) {
|
|
|
|
|
OWSFailDebug(@"contentsOfDirectoryAtPath failed with error: %@", error);
|
|
|
|
|
// Do nothing
|
|