session-ios/SignalServiceKit/src/Messages/Attachments/TSAttachmentStream.m

943 lines
32 KiB
Mathematica
Raw Normal View History

//
// Copyright (c) 2018 Open Whisper Systems. All rights reserved.
//
2015-12-07 03:31:43 +01:00
#import "TSAttachmentStream.h"
#import "MIMETypeUtil.h"
2017-08-30 15:58:02 +02:00
#import "NSData+Image.h"
2017-11-16 16:12:47 +01:00
#import "OWSFileSystem.h"
Explain send failures for text and media messages Motivation ---------- We were often swallowing errors or yielding generic errors when it would be better to provide specific errors. We also didn't create an attachment when attachments failed to send, making it impossible to show the user what was happening with an in-progress or failed attachment. Primary Changes --------------- - Funnel all message sending through MessageSender, and remove message sending from MessagesManager. - Record most recent sending error so we can expose it in the UI - Can resend attachments. - Update message status for attachments, just like text messages - Extracted UploadingService from MessagesManager - Saving attachment stream before uploading gives uniform API for send vs. resend - update status for downloading transcript attachments - TSAttachments have a local id, separate from the server allocated id This allows us to save the attachment before the allocation request. Which is is good because: 1. can show feedback to user faster. 2. allows us to show an error when allocation fails. Code Cleanup ------------ - Replaced a lot of global singleton access with injected dependencies to make for easier testing. - Never save group meta messages. Rather than checking before (hopefully) every save, do it in the save method. - Don't use callbacks for sync code. - Handle errors on writing attachment data - Fix old long broken tests that weren't even running. =( - Removed dead code - Use constants vs define - Port flaky travis fixes from Signal-iOS // FREEBIE
2016-10-14 23:00:29 +02:00
#import "TSAttachmentPointer.h"
#import <AVFoundation/AVFoundation.h>
#import <ImageIO/ImageIO.h>
#import <SignalServiceKit/SignalServiceKit-Swift.h>
#import <YapDatabase/YapDatabase.h>
2015-12-07 03:31:43 +01:00
NS_ASSUME_NONNULL_BEGIN
const NSUInteger kThumbnailDimensionPointsSmall = 300;
2018-09-04 21:21:48 +02:00
const NSUInteger kThumbnailDimensionPointsMedium = 800;
// This size is large enough to render full screen.
const NSUInteger ThumbnailDimensionPointsLarge() {
CGSize screenSizePoints = UIScreen.mainScreen.bounds.size;
return MAX(screenSizePoints.width, screenSizePoints.height);
}
typedef void (^OWSLoadedThumbnailSuccess)(OWSLoadedThumbnail *loadedThumbnail);
2018-09-04 21:21:48 +02:00
@implementation TSAttachmentThumbnail
- (instancetype)initWithFilename:(NSString *)filename
size:(CGSize)size
thumbnailDimensionPoints:(NSUInteger)thumbnailDimensionPoints
{
self = [super init];
if (!self) {
return self;
}
_filename = filename;
_size = size;
_thumbnailDimensionPoints = thumbnailDimensionPoints;
return self;
}
@end
#pragma mark -
@interface TSAttachmentStream ()
// We only want to generate the file path for this attachment once, so that
// changes in the file path generation logic don't break existing attachments.
@property (nullable, nonatomic) NSString *localRelativeFilePath;
2018-02-02 16:56:16 +01:00
// These properties should only be accessed while synchronized on self.
@property (nullable, nonatomic) NSNumber *cachedImageWidth;
@property (nullable, nonatomic) NSNumber *cachedImageHeight;
2018-02-02 16:56:16 +01:00
// This property should only be accessed on the main thread.
@property (nullable, nonatomic) NSNumber *cachedAudioDurationSeconds;
// Optional property. Only set for attachments which need "lazy backup restore."
@property (nonatomic, nullable) NSString *lazyRestoreFragmentId;
2018-03-20 22:22:19 +01:00
2018-09-04 21:21:48 +02:00
@property (nonatomic, nullable) NSArray<TSAttachmentThumbnail *> *thumbnails;
@end
#pragma mark -
2015-12-07 03:31:43 +01:00
@implementation TSAttachmentStream
- (instancetype)initWithContentType:(NSString *)contentType
byteCount:(UInt32)byteCount
sourceFilename:(nullable NSString *)sourceFilename
{
self = [super initWithContentType:contentType byteCount:byteCount sourceFilename:sourceFilename];
if (!self) {
return self;
}
2015-12-07 03:31:43 +01:00
self.isDownloaded = YES;
// TSAttachmentStream doesn't have any "incoming vs. outgoing"
// state, but this constructor is used only for new outgoing
// attachments which haven't been uploaded yet.
_isUploaded = NO;
_creationTimestamp = [NSDate new];
2017-06-21 15:58:19 +02:00
[self ensureFilePath];
2017-05-17 18:44:05 +02:00
2015-12-07 03:31:43 +01:00
return self;
}
Explain send failures for text and media messages Motivation ---------- We were often swallowing errors or yielding generic errors when it would be better to provide specific errors. We also didn't create an attachment when attachments failed to send, making it impossible to show the user what was happening with an in-progress or failed attachment. Primary Changes --------------- - Funnel all message sending through MessageSender, and remove message sending from MessagesManager. - Record most recent sending error so we can expose it in the UI - Can resend attachments. - Update message status for attachments, just like text messages - Extracted UploadingService from MessagesManager - Saving attachment stream before uploading gives uniform API for send vs. resend - update status for downloading transcript attachments - TSAttachments have a local id, separate from the server allocated id This allows us to save the attachment before the allocation request. Which is is good because: 1. can show feedback to user faster. 2. allows us to show an error when allocation fails. Code Cleanup ------------ - Replaced a lot of global singleton access with injected dependencies to make for easier testing. - Never save group meta messages. Rather than checking before (hopefully) every save, do it in the save method. - Don't use callbacks for sync code. - Handle errors on writing attachment data - Fix old long broken tests that weren't even running. =( - Removed dead code - Use constants vs define - Port flaky travis fixes from Signal-iOS // FREEBIE
2016-10-14 23:00:29 +02:00
- (instancetype)initWithPointer:(TSAttachmentPointer *)pointer
{
// Once saved, this AttachmentStream will replace the AttachmentPointer in the attachments collection.
self = [super initWithPointer:pointer];
Explain send failures for text and media messages Motivation ---------- We were often swallowing errors or yielding generic errors when it would be better to provide specific errors. We also didn't create an attachment when attachments failed to send, making it impossible to show the user what was happening with an in-progress or failed attachment. Primary Changes --------------- - Funnel all message sending through MessageSender, and remove message sending from MessagesManager. - Record most recent sending error so we can expose it in the UI - Can resend attachments. - Update message status for attachments, just like text messages - Extracted UploadingService from MessagesManager - Saving attachment stream before uploading gives uniform API for send vs. resend - update status for downloading transcript attachments - TSAttachments have a local id, separate from the server allocated id This allows us to save the attachment before the allocation request. Which is is good because: 1. can show feedback to user faster. 2. allows us to show an error when allocation fails. Code Cleanup ------------ - Replaced a lot of global singleton access with injected dependencies to make for easier testing. - Never save group meta messages. Rather than checking before (hopefully) every save, do it in the save method. - Don't use callbacks for sync code. - Handle errors on writing attachment data - Fix old long broken tests that weren't even running. =( - Removed dead code - Use constants vs define - Port flaky travis fixes from Signal-iOS // FREEBIE
2016-10-14 23:00:29 +02:00
if (!self) {
return self;
}
_contentType = pointer.contentType;
self.isDownloaded = YES;
// TSAttachmentStream doesn't have any "incoming vs. outgoing"
// state, but this constructor is used only for new incoming
// attachments which don't need to be uploaded.
_isUploaded = YES;
self.attachmentType = pointer.attachmentType;
_creationTimestamp = [NSDate new];
Explain send failures for text and media messages Motivation ---------- We were often swallowing errors or yielding generic errors when it would be better to provide specific errors. We also didn't create an attachment when attachments failed to send, making it impossible to show the user what was happening with an in-progress or failed attachment. Primary Changes --------------- - Funnel all message sending through MessageSender, and remove message sending from MessagesManager. - Record most recent sending error so we can expose it in the UI - Can resend attachments. - Update message status for attachments, just like text messages - Extracted UploadingService from MessagesManager - Saving attachment stream before uploading gives uniform API for send vs. resend - update status for downloading transcript attachments - TSAttachments have a local id, separate from the server allocated id This allows us to save the attachment before the allocation request. Which is is good because: 1. can show feedback to user faster. 2. allows us to show an error when allocation fails. Code Cleanup ------------ - Replaced a lot of global singleton access with injected dependencies to make for easier testing. - Never save group meta messages. Rather than checking before (hopefully) every save, do it in the save method. - Don't use callbacks for sync code. - Handle errors on writing attachment data - Fix old long broken tests that weren't even running. =( - Removed dead code - Use constants vs define - Port flaky travis fixes from Signal-iOS // FREEBIE
2016-10-14 23:00:29 +02:00
2017-06-21 15:58:19 +02:00
[self ensureFilePath];
2017-05-17 18:44:05 +02:00
return self;
}
- (nullable instancetype)initWithCoder:(NSCoder *)coder
{
self = [super initWithCoder:coder];
if (!self) {
return self;
}
// OWS105AttachmentFilePaths will ensure the file path is saved if necessary.
2017-06-21 15:58:19 +02:00
[self ensureFilePath];
2017-05-17 18:44:05 +02:00
// OWS105AttachmentFilePaths will ensure the creation timestamp is saved if necessary.
if (!_creationTimestamp) {
_creationTimestamp = [NSDate new];
}
Explain send failures for text and media messages Motivation ---------- We were often swallowing errors or yielding generic errors when it would be better to provide specific errors. We also didn't create an attachment when attachments failed to send, making it impossible to show the user what was happening with an in-progress or failed attachment. Primary Changes --------------- - Funnel all message sending through MessageSender, and remove message sending from MessagesManager. - Record most recent sending error so we can expose it in the UI - Can resend attachments. - Update message status for attachments, just like text messages - Extracted UploadingService from MessagesManager - Saving attachment stream before uploading gives uniform API for send vs. resend - update status for downloading transcript attachments - TSAttachments have a local id, separate from the server allocated id This allows us to save the attachment before the allocation request. Which is is good because: 1. can show feedback to user faster. 2. allows us to show an error when allocation fails. Code Cleanup ------------ - Replaced a lot of global singleton access with injected dependencies to make for easier testing. - Never save group meta messages. Rather than checking before (hopefully) every save, do it in the save method. - Don't use callbacks for sync code. - Handle errors on writing attachment data - Fix old long broken tests that weren't even running. =( - Removed dead code - Use constants vs define - Port flaky travis fixes from Signal-iOS // FREEBIE
2016-10-14 23:00:29 +02:00
return self;
}
- (void)upgradeFromAttachmentSchemaVersion:(NSUInteger)attachmentSchemaVersion
{
[super upgradeFromAttachmentSchemaVersion:attachmentSchemaVersion];
if (attachmentSchemaVersion < 3) {
// We want to treat any legacy TSAttachmentStream as though
// they have already been uploaded. If it needs to be reuploaded,
// the OWSUploadingService will update this progress when the
// upload begins.
self.isUploaded = YES;
}
2017-10-10 22:13:54 +02:00
if (attachmentSchemaVersion < 4) {
// Legacy image sizes don't correctly reflect image orientation.
2018-02-02 16:56:16 +01:00
@synchronized(self)
{
self.cachedImageWidth = nil;
self.cachedImageHeight = nil;
}
2017-10-10 22:13:54 +02:00
}
}
2017-06-21 15:58:19 +02:00
- (void)ensureFilePath
2017-05-17 18:44:05 +02:00
{
if (self.localRelativeFilePath) {
return;
}
NSString *attachmentsFolder = [[self class] attachmentsFolder];
2017-05-19 23:33:17 +02:00
NSString *filePath = [MIMETypeUtil filePathForAttachment:self.uniqueId
ofMIMEType:self.contentType
sourceFilename:self.sourceFilename
inFolder:attachmentsFolder];
if (!filePath) {
2017-11-08 20:04:51 +01:00
OWSFail(@"%@ Could not generate path for attachment.", self.logTag);
2017-05-17 18:44:05 +02:00
return;
}
2017-05-19 23:33:17 +02:00
if (![filePath hasPrefix:attachmentsFolder]) {
2017-11-08 20:04:51 +01:00
OWSFail(@"%@ Attachment paths should all be in the attachments folder.", self.logTag);
2017-05-17 18:44:05 +02:00
return;
}
2017-05-19 23:33:17 +02:00
NSString *localRelativeFilePath = [filePath substringFromIndex:attachmentsFolder.length];
2017-05-17 18:44:05 +02:00
if (localRelativeFilePath.length < 1) {
2017-11-08 20:04:51 +01:00
OWSFail(@"%@ Empty local relative attachment paths.", self.logTag);
2017-05-17 18:44:05 +02:00
return;
}
self.localRelativeFilePath = localRelativeFilePath;
2018-09-04 16:25:42 +02:00
OWSAssert(self.originalFilePath);
}
Explain send failures for text and media messages Motivation ---------- We were often swallowing errors or yielding generic errors when it would be better to provide specific errors. We also didn't create an attachment when attachments failed to send, making it impossible to show the user what was happening with an in-progress or failed attachment. Primary Changes --------------- - Funnel all message sending through MessageSender, and remove message sending from MessagesManager. - Record most recent sending error so we can expose it in the UI - Can resend attachments. - Update message status for attachments, just like text messages - Extracted UploadingService from MessagesManager - Saving attachment stream before uploading gives uniform API for send vs. resend - update status for downloading transcript attachments - TSAttachments have a local id, separate from the server allocated id This allows us to save the attachment before the allocation request. Which is is good because: 1. can show feedback to user faster. 2. allows us to show an error when allocation fails. Code Cleanup ------------ - Replaced a lot of global singleton access with injected dependencies to make for easier testing. - Never save group meta messages. Rather than checking before (hopefully) every save, do it in the save method. - Don't use callbacks for sync code. - Handle errors on writing attachment data - Fix old long broken tests that weren't even running. =( - Removed dead code - Use constants vs define - Port flaky travis fixes from Signal-iOS // FREEBIE
2016-10-14 23:00:29 +02:00
#pragma mark - File Management
- (nullable NSData *)readDataFromFileWithError:(NSError **)error
{
2017-05-17 18:44:05 +02:00
*error = nil;
2018-09-04 16:25:42 +02:00
NSString *_Nullable filePath = self.originalFilePath;
2017-05-19 23:33:17 +02:00
if (!filePath) {
2017-11-08 20:04:51 +01:00
OWSFail(@"%@ Missing path for attachment.", self.logTag);
2017-05-17 18:44:05 +02:00
return nil;
}
2017-05-19 23:33:17 +02:00
return [NSData dataWithContentsOfFile:filePath options:0 error:error];
Explain send failures for text and media messages Motivation ---------- We were often swallowing errors or yielding generic errors when it would be better to provide specific errors. We also didn't create an attachment when attachments failed to send, making it impossible to show the user what was happening with an in-progress or failed attachment. Primary Changes --------------- - Funnel all message sending through MessageSender, and remove message sending from MessagesManager. - Record most recent sending error so we can expose it in the UI - Can resend attachments. - Update message status for attachments, just like text messages - Extracted UploadingService from MessagesManager - Saving attachment stream before uploading gives uniform API for send vs. resend - update status for downloading transcript attachments - TSAttachments have a local id, separate from the server allocated id This allows us to save the attachment before the allocation request. Which is is good because: 1. can show feedback to user faster. 2. allows us to show an error when allocation fails. Code Cleanup ------------ - Replaced a lot of global singleton access with injected dependencies to make for easier testing. - Never save group meta messages. Rather than checking before (hopefully) every save, do it in the save method. - Don't use callbacks for sync code. - Handle errors on writing attachment data - Fix old long broken tests that weren't even running. =( - Removed dead code - Use constants vs define - Port flaky travis fixes from Signal-iOS // FREEBIE
2016-10-14 23:00:29 +02:00
}
2015-12-07 03:31:43 +01:00
Explain send failures for text and media messages Motivation ---------- We were often swallowing errors or yielding generic errors when it would be better to provide specific errors. We also didn't create an attachment when attachments failed to send, making it impossible to show the user what was happening with an in-progress or failed attachment. Primary Changes --------------- - Funnel all message sending through MessageSender, and remove message sending from MessagesManager. - Record most recent sending error so we can expose it in the UI - Can resend attachments. - Update message status for attachments, just like text messages - Extracted UploadingService from MessagesManager - Saving attachment stream before uploading gives uniform API for send vs. resend - update status for downloading transcript attachments - TSAttachments have a local id, separate from the server allocated id This allows us to save the attachment before the allocation request. Which is is good because: 1. can show feedback to user faster. 2. allows us to show an error when allocation fails. Code Cleanup ------------ - Replaced a lot of global singleton access with injected dependencies to make for easier testing. - Never save group meta messages. Rather than checking before (hopefully) every save, do it in the save method. - Don't use callbacks for sync code. - Handle errors on writing attachment data - Fix old long broken tests that weren't even running. =( - Removed dead code - Use constants vs define - Port flaky travis fixes from Signal-iOS // FREEBIE
2016-10-14 23:00:29 +02:00
- (BOOL)writeData:(NSData *)data error:(NSError **)error
{
OWSAssert(data);
2017-05-17 18:44:05 +02:00
*error = nil;
2018-09-04 16:25:42 +02:00
NSString *_Nullable filePath = self.originalFilePath;
2017-05-19 23:33:17 +02:00
if (!filePath) {
2017-11-08 20:04:51 +01:00
OWSFail(@"%@ Missing path for attachment.", self.logTag);
2017-05-17 18:44:05 +02:00
return NO;
}
2017-11-08 20:04:51 +01:00
DDLogInfo(@"%@ Writing attachment to file: %@", self.logTag, filePath);
2017-05-19 23:33:17 +02:00
return [data writeToFile:filePath options:0 error:error];
2015-12-07 03:31:43 +01:00
}
2017-09-18 21:02:34 +02:00
- (BOOL)writeDataSource:(DataSource *)dataSource
{
OWSAssert(dataSource);
2018-09-04 16:25:42 +02:00
NSString *_Nullable filePath = self.originalFilePath;
if (!filePath) {
2017-11-08 20:04:51 +01:00
OWSFail(@"%@ Missing path for attachment.", self.logTag);
return NO;
}
2017-11-08 20:04:51 +01:00
DDLogInfo(@"%@ Writing attachment to file: %@", self.logTag, filePath);
return [dataSource writeToPath:filePath];
}
2017-11-30 16:02:04 +01:00
+ (NSString *)legacyAttachmentsDirPath
2017-11-28 19:46:26 +01:00
{
return [[OWSFileSystem appDocumentDirectoryPath] stringByAppendingPathComponent:@"Attachments"];
}
2017-11-30 16:02:04 +01:00
+ (NSString *)sharedDataAttachmentsDirPath
2017-11-28 19:46:26 +01:00
{
return [[OWSFileSystem appSharedDataDirectoryPath] stringByAppendingPathComponent:@"Attachments"];
}
+ (nullable NSError *)migrateToSharedData
2017-11-28 19:46:26 +01:00
{
DDLogInfo(@"%@ %s", self.logTag, __PRETTY_FUNCTION__);
return [OWSFileSystem moveAppFilePath:self.legacyAttachmentsDirPath
sharedDataFilePath:self.sharedDataAttachmentsDirPath];
2017-11-28 19:46:26 +01:00
}
+ (NSString *)attachmentsFolder
{
static NSString *attachmentsFolder = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
2017-11-30 16:02:04 +01:00
attachmentsFolder = TSAttachmentStream.sharedDataAttachmentsDirPath;
[OWSFileSystem ensureDirectoryExists:attachmentsFolder];
});
return attachmentsFolder;
2015-12-07 03:31:43 +01:00
}
2018-09-04 16:25:42 +02:00
- (nullable NSString *)originalFilePath
{
if (!self.localRelativeFilePath) {
2017-11-08 20:04:51 +01:00
OWSFail(@"%@ Attachment missing local file path.", self.logTag);
return nil;
}
2017-05-17 18:44:05 +02:00
return [[[self class] attachmentsFolder] stringByAppendingPathComponent:self.localRelativeFilePath];
2015-12-07 03:31:43 +01:00
}
- (nullable NSString *)legacyThumbnailPath
2018-03-19 02:57:05 +01:00
{
2018-09-04 16:25:42 +02:00
NSString *filePath = self.originalFilePath;
2018-03-19 02:57:05 +01:00
if (!filePath) {
OWSFail(@"%@ Attachment missing local file path.", self.logTag);
return nil;
}
if (!self.isImage && !self.isVideo && !self.isAnimated) {
return nil;
}
NSString *filename = filePath.lastPathComponent.stringByDeletingPathExtension;
NSString *containingDir = filePath.stringByDeletingLastPathComponent;
NSString *newFilename = [filename stringByAppendingString:@"-signal-ios-thumbnail"];
2018-03-19 02:57:05 +01:00
return [[containingDir stringByAppendingPathComponent:newFilename] stringByAppendingPathExtension:@"jpg"];
}
2018-09-04 21:21:48 +02:00
- (nullable NSString *)pathForThumbnail:(TSAttachmentThumbnail *)thumbnail
{
NSString *filePath = self.originalFilePath;
if (!filePath) {
OWSFail(@"%@ Attachment missing local file path.", self.logTag);
return nil;
}
NSString *containingDir = filePath.stringByDeletingLastPathComponent;
return [containingDir stringByAppendingPathComponent:thumbnail.filename];
}
2018-09-04 16:25:42 +02:00
- (nullable NSURL *)originalMediaURL
{
2018-09-04 16:25:42 +02:00
NSString *_Nullable filePath = self.originalFilePath;
2017-05-19 23:33:17 +02:00
if (!filePath) {
2017-11-08 20:04:51 +01:00
OWSFail(@"%@ Missing path for attachment.", self.logTag);
return nil;
}
2017-05-19 23:33:17 +02:00
return [NSURL fileURLWithPath:filePath];
2015-12-07 03:31:43 +01:00
}
- (void)removeFileWithTransaction:(YapDatabaseReadWriteTransaction *)transaction
Explain send failures for text and media messages Motivation ---------- We were often swallowing errors or yielding generic errors when it would be better to provide specific errors. We also didn't create an attachment when attachments failed to send, making it impossible to show the user what was happening with an in-progress or failed attachment. Primary Changes --------------- - Funnel all message sending through MessageSender, and remove message sending from MessagesManager. - Record most recent sending error so we can expose it in the UI - Can resend attachments. - Update message status for attachments, just like text messages - Extracted UploadingService from MessagesManager - Saving attachment stream before uploading gives uniform API for send vs. resend - update status for downloading transcript attachments - TSAttachments have a local id, separate from the server allocated id This allows us to save the attachment before the allocation request. Which is is good because: 1. can show feedback to user faster. 2. allows us to show an error when allocation fails. Code Cleanup ------------ - Replaced a lot of global singleton access with injected dependencies to make for easier testing. - Never save group meta messages. Rather than checking before (hopefully) every save, do it in the save method. - Don't use callbacks for sync code. - Handle errors on writing attachment data - Fix old long broken tests that weren't even running. =( - Removed dead code - Use constants vs define - Port flaky travis fixes from Signal-iOS // FREEBIE
2016-10-14 23:00:29 +02:00
{
NSError *error;
2018-09-04 21:21:48 +02:00
for (TSAttachmentThumbnail *thumbnail in self.thumbnails) {
NSString *_Nullable thumbnailPath = [self pathForThumbnail:thumbnail];
if (thumbnailPath) {
BOOL success = [[NSFileManager defaultManager] removeItemAtPath:thumbnailPath error:&error];
if (error || !success) {
DDLogError(@"%@ remove thumbnail failed with: %@", self.logTag, error);
}
}
}
NSString *_Nullable legacyThumbnailPath = self.legacyThumbnailPath;
if (legacyThumbnailPath) {
BOOL success = [[NSFileManager defaultManager] removeItemAtPath:legacyThumbnailPath error:&error];
2018-09-04 21:21:48 +02:00
if (error || !success) {
DDLogError(@"%@ remove legacy thumbnail failed with: %@", self.logTag, error);
}
}
2018-09-04 16:25:42 +02:00
NSString *_Nullable filePath = self.originalFilePath;
2017-05-19 23:33:17 +02:00
if (!filePath) {
2017-11-08 20:04:51 +01:00
OWSFail(@"%@ Missing path for attachment.", self.logTag);
2017-05-17 18:44:05 +02:00
return;
}
2018-09-04 21:21:48 +02:00
BOOL success = [[NSFileManager defaultManager] removeItemAtPath:filePath error:&error];
if (error || !success) {
DDLogError(@"%@ remove file failed with: %@", self.logTag, error);
Explain send failures for text and media messages Motivation ---------- We were often swallowing errors or yielding generic errors when it would be better to provide specific errors. We also didn't create an attachment when attachments failed to send, making it impossible to show the user what was happening with an in-progress or failed attachment. Primary Changes --------------- - Funnel all message sending through MessageSender, and remove message sending from MessagesManager. - Record most recent sending error so we can expose it in the UI - Can resend attachments. - Update message status for attachments, just like text messages - Extracted UploadingService from MessagesManager - Saving attachment stream before uploading gives uniform API for send vs. resend - update status for downloading transcript attachments - TSAttachments have a local id, separate from the server allocated id This allows us to save the attachment before the allocation request. Which is is good because: 1. can show feedback to user faster. 2. allows us to show an error when allocation fails. Code Cleanup ------------ - Replaced a lot of global singleton access with injected dependencies to make for easier testing. - Never save group meta messages. Rather than checking before (hopefully) every save, do it in the save method. - Don't use callbacks for sync code. - Handle errors on writing attachment data - Fix old long broken tests that weren't even running. =( - Removed dead code - Use constants vs define - Port flaky travis fixes from Signal-iOS // FREEBIE
2016-10-14 23:00:29 +02:00
}
}
- (void)removeWithTransaction:(YapDatabaseReadWriteTransaction *)transaction
{
[super removeWithTransaction:transaction];
[self removeFileWithTransaction:transaction];
}
2015-12-07 03:31:43 +01:00
- (BOOL)isAnimated {
return [MIMETypeUtil isAnimated:self.contentType];
}
- (BOOL)isImage {
return [MIMETypeUtil isImage:self.contentType];
}
- (BOOL)isVideo {
return [MIMETypeUtil isVideo:self.contentType];
}
- (BOOL)isAudio {
return [MIMETypeUtil isAudio:self.contentType];
}
2018-06-19 20:34:44 +02:00
#pragma mark - Image Validation
2018-08-31 02:59:26 +02:00
- (BOOL)isValidImage
2018-06-19 20:34:44 +02:00
{
OWSAssert(self.isImage || self.isAnimated);
2018-09-04 16:25:42 +02:00
return [NSData ows_isValidImageAtPath:self.originalFilePath mimeType:self.contentType];
2018-06-19 20:34:44 +02:00
}
2018-08-31 02:59:26 +02:00
- (BOOL)isValidVideo
2018-06-19 20:34:44 +02:00
{
2018-08-31 02:59:26 +02:00
OWSAssert(self.isVideo);
2018-06-19 20:34:44 +02:00
return [OWSMediaUtils isValidVideoWithPath:self.originalFilePath];
2018-06-19 20:34:44 +02:00
}
#pragma mark -
2018-09-04 16:29:30 +02:00
- (nullable UIImage *)originalImage
{
if ([self isVideo]) {
return [self videoStillImage];
} else if ([self isImage] || [self isAnimated]) {
2018-09-04 16:25:42 +02:00
NSURL *_Nullable mediaUrl = self.originalMediaURL;
if (!mediaUrl) {
return nil;
}
2018-08-31 02:59:26 +02:00
if (![self isValidImage]) {
2017-08-30 15:58:02 +02:00
return nil;
}
2018-09-04 16:25:42 +02:00
return [[UIImage alloc] initWithContentsOfFile:self.originalFilePath];
} else {
return nil;
2015-12-07 03:31:43 +01:00
}
}
2018-05-10 03:10:23 +02:00
- (nullable NSData *)validStillImageData
{
if ([self isVideo]) {
OWSFail(@"%@ in %s isVideo was unexpectedly true", self.logTag, __PRETTY_FUNCTION__);
return nil;
}
if ([self isAnimated]) {
OWSFail(@"%@ in %s isAnimated was unexpectedly true", self.logTag, __PRETTY_FUNCTION__);
return nil;
}
2018-09-04 16:25:42 +02:00
if (![NSData ows_isValidImageAtPath:self.originalFilePath mimeType:self.contentType]) {
2018-08-31 02:59:26 +02:00
OWSFail(@"%@ skipping invalid image", self.logTag);
2018-05-10 03:10:23 +02:00
return nil;
}
2018-09-04 16:25:42 +02:00
return [NSData dataWithContentsOfFile:self.originalFilePath];
2018-05-10 03:10:23 +02:00
}
2018-04-03 18:35:43 +02:00
+ (BOOL)hasThumbnailForMimeType:(NSString *)contentType
{
return ([MIMETypeUtil isVideo:contentType] || [MIMETypeUtil isImage:contentType] ||
[MIMETypeUtil isAnimated:contentType]);
}
- (nullable UIImage *)videoStillImage
2018-03-19 02:57:05 +01:00
{
NSError *error;
UIImage *_Nullable image = [OWSMediaUtils thumbnailForVideoAtPath:self.originalFilePath error:&error];
if (error || !image) {
DDLogError(@"Could not create video still: %@.", error);
2018-08-31 02:59:26 +02:00
return nil;
}
return image;
2015-12-07 03:31:43 +01:00
}
+ (void)deleteAttachments
{
2015-12-07 03:31:43 +01:00
NSError *error;
NSFileManager *fileManager = [NSFileManager defaultManager];
NSURL *fileURL = [NSURL fileURLWithPath:self.attachmentsFolder];
NSArray<NSURL *> *contents =
[fileManager contentsOfDirectoryAtURL:fileURL includingPropertiesForKeys:nil options:0 error:&error];
2015-12-07 03:31:43 +01:00
if (error) {
OWSFail(@"failed to get contents of attachments folder: %@ with error: %@", self.attachmentsFolder, error);
return;
2015-12-07 03:31:43 +01:00
}
for (NSURL *url in contents) {
2017-12-19 03:07:54 +01:00
[fileManager removeItemAtURL:url error:&error];
if (error) {
OWSFail(@"failed to remove item at path: %@ with error: %@", url, error);
}
}
2015-12-07 03:31:43 +01:00
}
- (CGSize)calculateImageSize
{
if ([self isVideo]) {
2018-08-31 02:59:26 +02:00
if (![self isValidVideo]) {
return CGSizeZero;
}
return [self videoStillImage].size;
} else if ([self isImage] || [self isAnimated]) {
2018-09-04 16:25:42 +02:00
NSURL *_Nullable mediaUrl = self.originalMediaURL;
if (!mediaUrl) {
return CGSizeZero;
}
2018-06-19 20:34:44 +02:00
if (![self isValidImage]) {
2017-08-30 15:58:02 +02:00
return CGSizeZero;
}
// With CGImageSource we avoid loading the whole image into memory.
CGImageSourceRef source = CGImageSourceCreateWithURL((CFURLRef)mediaUrl, NULL);
if (!source) {
2017-11-08 20:04:51 +01:00
OWSFail(@"%@ Could not load image: %@", self.logTag, mediaUrl);
return CGSizeZero;
}
NSDictionary *options = @{
(NSString *)kCGImageSourceShouldCache : @(NO),
};
NSDictionary *properties
= (__bridge_transfer NSDictionary *)CGImageSourceCopyPropertiesAtIndex(source, 0, (CFDictionaryRef)options);
CGSize imageSize = CGSizeZero;
if (properties) {
2017-10-10 22:13:54 +02:00
NSNumber *orientation = properties[(NSString *)kCGImagePropertyOrientation];
NSNumber *width = properties[(NSString *)kCGImagePropertyPixelWidth];
NSNumber *height = properties[(NSString *)kCGImagePropertyPixelHeight];
2017-10-10 22:13:54 +02:00
if (width && height) {
imageSize = CGSizeMake(width.floatValue, height.floatValue);
2017-10-10 22:13:54 +02:00
if (orientation) {
imageSize =
[self applyImageOrientation:(UIImageOrientation)orientation.intValue toImageSize:imageSize];
}
} else {
2017-11-08 20:04:51 +01:00
OWSFail(@"%@ Could not determine size of image: %@", self.logTag, mediaUrl);
}
}
CFRelease(source);
return imageSize;
} else {
return CGSizeZero;
}
}
2017-10-10 22:13:54 +02:00
- (CGSize)applyImageOrientation:(UIImageOrientation)orientation toImageSize:(CGSize)imageSize
{
switch (orientation) {
case UIImageOrientationUp: // EXIF = 1
case UIImageOrientationUpMirrored: // EXIF = 2
case UIImageOrientationDown: // EXIF = 3
case UIImageOrientationDownMirrored: // EXIF = 4
return imageSize;
case UIImageOrientationLeftMirrored: // EXIF = 5
case UIImageOrientationLeft: // EXIF = 6
case UIImageOrientationRightMirrored: // EXIF = 7
case UIImageOrientationRight: // EXIF = 8
return CGSizeMake(imageSize.height, imageSize.width);
default:
return imageSize;
}
}
2018-02-02 16:56:16 +01:00
- (BOOL)shouldHaveImageSize
{
return ([self isVideo] || [self isImage] || [self isAnimated]);
}
- (CGSize)imageSize
{
2018-02-02 16:56:16 +01:00
OWSAssert(self.shouldHaveImageSize);
2018-02-02 16:56:16 +01:00
@synchronized(self)
{
if (self.cachedImageWidth && self.cachedImageHeight) {
return CGSizeMake(self.cachedImageWidth.floatValue, self.cachedImageHeight.floatValue);
}
2018-02-02 16:56:16 +01:00
CGSize imageSize = [self calculateImageSize];
if (imageSize.width <= 0 || imageSize.height <= 0) {
return CGSizeZero;
2017-11-20 20:50:43 +01:00
}
2018-02-02 16:56:16 +01:00
self.cachedImageWidth = @(imageSize.width);
self.cachedImageHeight = @(imageSize.height);
[self.dbReadWriteConnection asyncReadWriteWithBlock:^(YapDatabaseReadWriteTransaction *transaction) {
2018-02-02 16:56:16 +01:00
NSString *collection = [[self class] collection];
TSAttachmentStream *latestInstance = [transaction objectForKey:self.uniqueId inCollection:collection];
if (latestInstance) {
latestInstance.cachedImageWidth = @(imageSize.width);
latestInstance.cachedImageHeight = @(imageSize.height);
[latestInstance saveWithTransaction:transaction];
} else {
// This message has not yet been saved or has been deleted; do nothing.
// This isn't an error per se, but these race conditions should be
// _very_ rare.
OWSFail(@"%@ Attachment not yet saved.", self.logTag);
}
}];
return imageSize;
}
}
- (CGFloat)calculateAudioDurationSeconds
{
2017-12-19 17:38:25 +01:00
OWSAssertIsOnMainThread();
OWSAssert([self isAudio]);
NSError *error;
2018-09-04 16:25:42 +02:00
AVAudioPlayer *audioPlayer = [[AVAudioPlayer alloc] initWithContentsOfURL:self.originalMediaURL error:&error];
if (error && [error.domain isEqualToString:NSOSStatusErrorDomain]
&& (error.code == kAudioFileInvalidFileError || error.code == kAudioFileStreamError_InvalidFile)) {
// Ignore "invalid audio file" errors.
return 0.f;
}
if (!error) {
return (CGFloat)[audioPlayer duration];
} else {
2018-09-04 16:25:42 +02:00
DDLogError(@"Could not find audio duration: %@", self.originalMediaURL);
return 0;
}
}
- (CGFloat)audioDurationSeconds
{
2017-12-19 17:38:25 +01:00
OWSAssertIsOnMainThread();
if (self.cachedAudioDurationSeconds) {
return self.cachedAudioDurationSeconds.floatValue;
}
CGFloat audioDurationSeconds = [self calculateAudioDurationSeconds];
self.cachedAudioDurationSeconds = @(audioDurationSeconds);
2017-11-20 20:50:43 +01:00
[self.dbReadWriteConnection asyncReadWriteWithBlock:^(YapDatabaseReadWriteTransaction *transaction) {
NSString *collection = [[self class] collection];
TSAttachmentStream *latestInstance = [transaction objectForKey:self.uniqueId inCollection:collection];
if (latestInstance) {
latestInstance.cachedAudioDurationSeconds = @(audioDurationSeconds);
[latestInstance saveWithTransaction:transaction];
} else {
// This message has not yet been saved or has been deleted; do nothing.
// This isn't an error per se, but these race conditions should be
// _very_ rare.
OWSFail(@"%@ Attachment not yet saved.", self.logTag);
}
}];
return audioDurationSeconds;
}
- (nullable OWSBackupFragment *)lazyRestoreFragment
{
if (!self.lazyRestoreFragmentId) {
return nil;
}
return [OWSBackupFragment fetchObjectWithUniqueID:self.lazyRestoreFragmentId];
}
2018-06-13 23:35:22 +02:00
- (BOOL)isOversizeText
{
return [self.contentType isEqualToString:OWSMimeTypeOversizeTextMessage];
}
- (nullable NSString *)readOversizeText
{
if (!self.isOversizeText) {
OWSFail(@"%@ oversize text attachment has unexpected content type.", self.logTag);
return nil;
}
NSError *error;
NSData *_Nullable data = [self readDataFromFileWithError:&error];
if (error || !data) {
OWSFail(@"%@ could not read oversize text attachment: %@.", self.logTag, error);
return nil;
}
NSString *_Nullable string = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
return string;
}
2018-09-04 21:21:48 +02:00
#pragma mark - Thumbnails
- (nullable UIImage *)thumbnailImageWithSizeHint:(CGSize)sizeHint
success:(OWSThumbnailSuccess)success
failure:(OWSThumbnailFailure)failure
2018-09-04 21:21:48 +02:00
{
CGFloat maxDimensionHint = MAX(sizeHint.width, sizeHint.height);
NSUInteger thumbnailDimensionPoints;
if (maxDimensionHint <= kThumbnailDimensionPointsSmall) {
thumbnailDimensionPoints = kThumbnailDimensionPointsSmall;
} else if (maxDimensionHint <= kThumbnailDimensionPointsMedium) {
thumbnailDimensionPoints = kThumbnailDimensionPointsMedium;
} else {
thumbnailDimensionPoints = ThumbnailDimensionPointsLarge();
}
return [self thumbnailImageWithThumbnailDimensionPoints:thumbnailDimensionPoints success:success failure:failure];
2018-09-04 21:21:48 +02:00
}
- (nullable UIImage *)thumbnailImageSmallWithSuccess:(OWSThumbnailSuccess)success failure:(OWSThumbnailFailure)failure
2018-09-04 21:21:48 +02:00
{
return [self thumbnailImageWithThumbnailDimensionPoints:kThumbnailDimensionPointsSmall
success:success
failure:failure];
2018-09-04 21:21:48 +02:00
}
- (nullable UIImage *)thumbnailImageMediumWithSuccess:(OWSThumbnailSuccess)success failure:(OWSThumbnailFailure)failure
2018-09-04 21:21:48 +02:00
{
return [self thumbnailImageWithThumbnailDimensionPoints:kThumbnailDimensionPointsMedium
success:success
failure:failure];
2018-09-04 21:21:48 +02:00
}
- (nullable UIImage *)thumbnailImageLargeWithSuccess:(OWSThumbnailSuccess)success failure:(OWSThumbnailFailure)failure
2018-09-04 21:21:48 +02:00
{
return [self thumbnailImageWithThumbnailDimensionPoints:ThumbnailDimensionPointsLarge()
success:success
failure:failure];
2018-09-04 21:21:48 +02:00
}
- (nullable UIImage *)thumbnailImageWithThumbnailDimensionPoints:(NSUInteger)thumbnailDimensionPoints
success:(OWSThumbnailSuccess)success
failure:(OWSThumbnailFailure)failure
{
OWSLoadedThumbnail *_Nullable loadedThumbnail;
loadedThumbnail = [self loadedThumbnailWithThumbnailDimensionPoints:thumbnailDimensionPoints
success:^(OWSLoadedThumbnail *loadedThumbnail) {
success(loadedThumbnail.image);
}
failure:failure];
return loadedThumbnail.image;
}
- (nullable OWSLoadedThumbnail *)loadedThumbnailWithThumbnailDimensionPoints:(NSUInteger)thumbnailDimensionPoints
success:(OWSLoadedThumbnailSuccess)success
failure:(OWSThumbnailFailure)failure
2018-09-04 21:21:48 +02:00
{
CGSize originalSize = self.imageSize;
if (originalSize.width < 1 || originalSize.height < 1) {
return nil;
}
if (originalSize.width <= thumbnailDimensionPoints || originalSize.height <= thumbnailDimensionPoints) {
// There's no point in generating a thumbnail if the original is smaller than the
// thumbnail size.
return [[OWSLoadedThumbnail alloc] initWithImage:self.originalImage filePath:self.originalFilePath];
2018-09-04 21:21:48 +02:00
}
for (TSAttachmentThumbnail *thumbnail in self.thumbnails) {
if (thumbnail.thumbnailDimensionPoints != thumbnailDimensionPoints) {
continue;
}
NSString *_Nullable thumbnailPath = [self pathForThumbnail:thumbnail];
if (!thumbnailPath) {
OWSFail(@"Missing thumbnail path.");
continue;
}
UIImage *_Nullable image = [UIImage imageWithContentsOfFile:thumbnailPath];
if (!image) {
OWSFail(@"couldn't load image.");
continue;
}
return [[OWSLoadedThumbnail alloc] initWithImage:image filePath:thumbnailPath];
2018-09-04 21:21:48 +02:00
}
[OWSThumbnailService.shared ensureThumbnailForAttachmentId:self.uniqueId
thumbnailDimensionPoints:thumbnailDimensionPoints
success:success
failure:failure];
2018-09-04 21:21:48 +02:00
return nil;
}
- (nullable OWSLoadedThumbnail *)loadedThumbnailSmallSync
{
__block dispatch_semaphore_t semaphore = dispatch_semaphore_create(0);
__block OWSLoadedThumbnail *_Nullable loadedThumbnail = nil;
loadedThumbnail = [self loadedThumbnailWithThumbnailDimensionPoints:kThumbnailDimensionPointsSmall
success:^(OWSLoadedThumbnail *asyncLoadedThumbnail) {
@synchronized(self) {
loadedThumbnail = asyncLoadedThumbnail;
}
dispatch_semaphore_signal(semaphore);
}
failure:^{
dispatch_semaphore_signal(semaphore);
}];
// Wait up to five seconds.
dispatch_semaphore_wait(semaphore, dispatch_time(DISPATCH_TIME_NOW, (int64_t)(5 * NSEC_PER_SEC)));
@synchronized(self) {
return loadedThumbnail;
}
}
- (nullable UIImage *)thumbnailImageSmallSync
{
return [self loadedThumbnailSmallSync].image;
}
- (nullable NSData *)thumbnailDataSmallSync
{
NSError *error;
NSData *_Nullable data = [[self loadedThumbnailSmallSync] dataAndReturnError:&error];
if (error || !data) {
OWSFail(@"Couldn't load thumbnail data: %@", error);
return nil;
}
return data;
}
- (NSArray<NSString *> *)allThumbnailPaths
{
NSMutableArray<NSString *> *result = [NSMutableArray new];
for (TSAttachmentThumbnail *thumbnail in self.thumbnails) {
NSString *_Nullable thumbnailPath = [self pathForThumbnail:thumbnail];
if (!thumbnailPath) {
OWSFail(@"Missing thumbnail path.");
continue;
}
[result addObject:thumbnailPath];
}
NSString *_Nullable legacyThumbnailPath = self.legacyThumbnailPath;
if (legacyThumbnailPath && [[NSFileManager defaultManager] fileExistsAtPath:legacyThumbnailPath]) {
[result addObject:legacyThumbnailPath];
}
return result;
}
2018-03-20 22:22:19 +01:00
#pragma mark - Update With... Methods
2018-03-22 19:12:29 +01:00
- (void)markForLazyRestoreWithFragment:(OWSBackupFragment *)lazyRestoreFragment
transaction:(YapDatabaseReadWriteTransaction *)transaction
2018-03-20 22:22:19 +01:00
{
OWSAssert(lazyRestoreFragment);
2018-03-22 18:33:34 +01:00
OWSAssert(transaction);
2018-03-20 22:22:19 +01:00
2018-03-22 18:33:34 +01:00
if (!lazyRestoreFragment.uniqueId) {
// If metadata hasn't been saved yet, save now.
[lazyRestoreFragment saveWithTransaction:transaction];
2018-03-22 18:33:34 +01:00
OWSAssert(lazyRestoreFragment.uniqueId);
}
[self applyChangeToSelfAndLatestCopy:transaction
changeBlock:^(TSAttachmentStream *attachment) {
[attachment setLazyRestoreFragmentId:lazyRestoreFragment.uniqueId];
}];
2018-03-20 22:22:19 +01:00
}
- (void)updateWithLazyRestoreComplete
2018-03-20 22:22:19 +01:00
{
[self.dbReadWriteConnection readWriteWithBlock:^(YapDatabaseReadWriteTransaction *transaction) {
[self applyChangeToSelfAndLatestCopy:transaction
changeBlock:^(TSAttachmentStream *attachment) {
[attachment setLazyRestoreFragmentId:nil];
2018-03-20 22:22:19 +01:00
}];
}];
}
- (nullable TSAttachmentStream *)cloneAsThumbnail
{
NSData *_Nullable thumbnailData = self.thumbnailDataSmallSync;
// Only some media types have thumbnails
if (!thumbnailData) {
return nil;
}
// Copy the thumbnail to a new attachment.
NSString *thumbnailName = [NSString stringWithFormat:@"quoted-thumbnail-%@", self.sourceFilename];
TSAttachmentStream *thumbnailAttachment =
[[TSAttachmentStream alloc] initWithContentType:OWSMimeTypeImageJpeg
byteCount:(uint32_t)thumbnailData.length
sourceFilename:thumbnailName];
NSError *error;
BOOL success = [thumbnailAttachment writeData:thumbnailData error:&error];
if (!success || error) {
DDLogError(@"%@ Couldn't copy attachment data for message sent to self: %@.", self.logTag, error);
return nil;
}
return thumbnailAttachment;
}
2018-09-04 21:21:48 +02:00
- (void)updateWithNewThumbnail:(NSString *)tempFilePath
thumbnailDimensionPoints:(NSUInteger)thumbnailDimensionPoints
size:(CGSize)size
transaction:(YapDatabaseReadWriteTransaction *)transaction
{
OWSAssert(tempFilePath.length > 0);
OWSAssert(thumbnailDimensionPoints > 0);
OWSAssert(size.width > 0 && size.height);
OWSAssert(transaction);
NSString *filename = tempFilePath.lastPathComponent;
NSString *containingDir = self.originalFilePath.stringByDeletingLastPathComponent;
NSString *filePath = [containingDir stringByAppendingPathComponent:filename];
NSError *_Nullable error;
BOOL success = [[NSFileManager defaultManager] moveItemAtPath:tempFilePath toPath:filePath error:&error];
if (error || !success) {
OWSFail(@"Could not move new thumbnail image: %@.", error);
return;
}
TSAttachmentThumbnail *newThumbnail = [[TSAttachmentThumbnail alloc] initWithFilename:filename
size:size
thumbnailDimensionPoints:thumbnailDimensionPoints];
[self applyChangeToSelfAndLatestCopy:transaction
changeBlock:^(TSAttachmentStream *attachment) {
NSMutableArray<TSAttachmentThumbnail *> *thumbnails
= (attachment.thumbnails ? [attachment.thumbnails mutableCopy]
: [NSMutableArray new]);
[thumbnails addObject:newThumbnail];
[attachment setThumbnails:thumbnails];
}];
}
// MARK: Protobuf serialization
+ (nullable SSKProtoAttachmentPointer *)buildProtoForAttachmentId:(nullable NSString *)attachmentId
{
OWSAssert(attachmentId.length > 0);
2018-05-07 18:32:31 +02:00
// TODO we should past in a transaction, rather than sneakily generate one in `fetch...` to make sure we're
// getting a consistent view in the message sending process. A brief glance shows it touches quite a bit of code,
// but should be straight forward.
TSAttachment *attachment = [TSAttachmentStream fetchObjectWithUniqueID:attachmentId];
if (![attachment isKindOfClass:[TSAttachmentStream class]]) {
DDLogError(@"Unexpected type for attachment builder: %@", attachment);
return nil;
}
TSAttachmentStream *attachmentStream = (TSAttachmentStream *)attachment;
return [attachmentStream buildProto];
}
- (nullable SSKProtoAttachmentPointer *)buildProto
{
SSKProtoAttachmentPointerBuilder *builder = [SSKProtoAttachmentPointerBuilder new];
builder.id = self.serverId;
OWSAssert(self.contentType.length > 0);
builder.contentType = self.contentType;
DDLogVerbose(@"%@ Sending attachment with filename: '%@'", self.logTag, self.sourceFilename);
builder.fileName = self.sourceFilename;
builder.size = self.byteCount;
builder.key = self.encryptionKey;
builder.digest = self.digest;
builder.flags = self.isVoiceMessage ? SSKProtoAttachmentPointerFlagsVoiceMessage : 0;
if (self.shouldHaveImageSize) {
CGSize imageSize = self.imageSize;
if (imageSize.width < NSIntegerMax && imageSize.height < NSIntegerMax) {
NSInteger imageWidth = (NSInteger)round(imageSize.width);
NSInteger imageHeight = (NSInteger)round(imageSize.height);
if (imageWidth > 0 && imageHeight > 0) {
builder.width = (UInt32)imageWidth;
builder.height = (UInt32)imageHeight;
}
}
}
NSError *error;
SSKProtoAttachmentPointer *_Nullable attachmentProto = [builder buildAndReturnError:&error];
if (error || !attachmentProto) {
OWSFail(@"%@ could not build protobuf: %@", self.logTag, error);
return nil;
}
return attachmentProto;
}
2015-12-07 03:31:43 +01:00
@end
NS_ASSUME_NONNULL_END