diff --git a/Pods b/Pods index 7d20c06f1..e54eb900c 160000 --- a/Pods +++ b/Pods @@ -1 +1 @@ -Subproject commit 7d20c06f10623230848be112f493804b108e6aa1 +Subproject commit e54eb900c0c4be9646d4c7ed800c8ea45d275686 diff --git a/Signal/src/ViewControllers/ConversationView/Cells/OWSQuotedMessageView.m b/Signal/src/ViewControllers/ConversationView/Cells/OWSQuotedMessageView.m index 11c35e60e..b3d9a28e2 100644 --- a/Signal/src/ViewControllers/ConversationView/Cells/OWSQuotedMessageView.m +++ b/Signal/src/ViewControllers/ConversationView/Cells/OWSQuotedMessageView.m @@ -209,11 +209,9 @@ NS_ASSUME_NONNULL_BEGIN if (!self.hasQuotedAttachmentThumbnailImage) { return nil; } - if (!self.quotedMessage.thumbnailData) { - return nil; - } + // TODO: Possibly ignore data that is too large. - UIImage *_Nullable image = [UIImage imageWithData:self.quotedMessage.thumbnailData]; + UIImage *_Nullable image = self.quotedMessage.thumbnailImage; // TODO: Possibly ignore images that are too large. return image; } @@ -241,7 +239,7 @@ NS_ASSUME_NONNULL_BEGIN NSString *text = @""; NSString *_Nullable fileTypeForSnippet = [self fileTypeForSnippet]; - NSString *_Nullable sourceFilename = [self.quotedMessage.sourceFilename filterStringForDisplay]; + NSString *_Nullable sourceFilename = [self.quotedMessage.firstThumbnailAttachment.sourceFilename filterStringForDisplay]; if (self.displayableQuotedText.displayText.length > 0) { text = self.displayableQuotedText.displayText; diff --git a/Signal/src/views/QuotedReplyPreview.swift b/Signal/src/views/QuotedReplyPreview.swift index fea4ff21e..87698ab38 100644 --- a/Signal/src/views/QuotedReplyPreview.swift +++ b/Signal/src/views/QuotedReplyPreview.swift @@ -43,7 +43,7 @@ class QuotedReplyPreview: UIView { bodyLabel.font = .ows_footnote bodyLabel.text = { - if let contentType = quotedMessage.contentType { + if let contentType = quotedMessage.contentType() { let emoji = TSAttachmentStream.emoji(forMimeType: contentType) return "\(emoji) \(quotedMessage.body ?? "")" } else { @@ -52,16 +52,15 @@ class QuotedReplyPreview: UIView { }() let thumbnailView: UIView? = { - // FIXME TODO -// if let image = quotedMessage.thumbnailImage() { -// let imageView = UIImageView(image: image) -// imageView.contentMode = .scaleAspectFill -// imageView.autoPinToSquareAspectRatio() -// imageView.layer.cornerRadius = 3.0 -// imageView.clipsToBounds = true -// -// return imageView -// } + if let image = quotedMessage.thumbnailImage() { + let imageView = UIImageView(image: image) + imageView.contentMode = .scaleAspectFill + imageView.autoPinToSquareAspectRatio() + imageView.layer.cornerRadius = 3.0 + imageView.clipsToBounds = true + + return imageView + } return nil }() diff --git a/SignalServiceKit/src/Devices/OWSRecordTranscriptJob.m b/SignalServiceKit/src/Devices/OWSRecordTranscriptJob.m index 94ef2e91b..6ce9ca627 100644 --- a/SignalServiceKit/src/Devices/OWSRecordTranscriptJob.m +++ b/SignalServiceKit/src/Devices/OWSRecordTranscriptJob.m @@ -79,9 +79,7 @@ NS_ASSUME_NONNULL_BEGIN OWSAttachmentsProcessor *attachmentsProcessor = [[OWSAttachmentsProcessor alloc] initWithAttachmentProtos:transcript.attachmentPointerProtos - timestamp:transcript.timestamp relay:transcript.relay - thread:thread networkManager:self.networkManager primaryStorage:self.primaryStorage transaction:transaction]; diff --git a/SignalServiceKit/src/Messages/Attachments/OWSAttachmentsProcessor.h b/SignalServiceKit/src/Messages/Attachments/OWSAttachmentsProcessor.h index df9aa9a38..5f5fea5c1 100644 --- a/SignalServiceKit/src/Messages/Attachments/OWSAttachmentsProcessor.h +++ b/SignalServiceKit/src/Messages/Attachments/OWSAttachmentsProcessor.h @@ -25,14 +25,13 @@ extern NSString *const kAttachmentDownloadAttachmentIDKey; @property (nullable, nonatomic, readonly) NSArray *attachmentIds; @property (nonatomic, readonly) NSArray *supportedAttachmentIds; +@property (nonatomic, readonly) NSArray *supportedAttachmentPointers; @property (nonatomic, readonly) BOOL hasSupportedAttachments; - (instancetype)init NS_UNAVAILABLE; - (instancetype)initWithAttachmentProtos:(NSArray *)attachmentProtos - timestamp:(uint64_t)timestamp relay:(nullable NSString *)relay - thread:(TSThread *)thread networkManager:(TSNetworkManager *)networkManager primaryStorage:(OWSPrimaryStorage *)primaryStorage transaction:(YapDatabaseReadWriteTransaction *)transaction NS_DESIGNATED_INITIALIZER; diff --git a/SignalServiceKit/src/Messages/Attachments/OWSAttachmentsProcessor.m b/SignalServiceKit/src/Messages/Attachments/OWSAttachmentsProcessor.m index 3b8103efc..b4576a542 100644 --- a/SignalServiceKit/src/Messages/Attachments/OWSAttachmentsProcessor.m +++ b/SignalServiceKit/src/Messages/Attachments/OWSAttachmentsProcessor.m @@ -36,7 +36,6 @@ static const CGFloat kAttachmentDownloadProgressTheta = 0.001f; @property (nonatomic, readonly) TSNetworkManager *networkManager; @property (nonatomic, readonly) OWSPrimaryStorage *primaryStorage; -@property (nonatomic, readonly) NSArray *supportedAttachmentPointers; @end @@ -61,9 +60,7 @@ static const CGFloat kAttachmentDownloadProgressTheta = 0.001f; } - (instancetype)initWithAttachmentProtos:(NSArray *)attachmentProtos - timestamp:(uint64_t)timestamp relay:(nullable NSString *)relay - thread:(TSThread *)thread networkManager:(TSNetworkManager *)networkManager primaryStorage:(OWSPrimaryStorage *)primaryStorage transaction:(YapDatabaseReadWriteTransaction *)transaction @@ -120,6 +117,8 @@ static const CGFloat kAttachmentDownloadProgressTheta = 0.001f; return self; } + +// Remove this? - (void)fetchAttachmentsForMessage:(nullable TSMessage *)message primaryStorage:(OWSPrimaryStorage *)primaryStorage success:(void (^)(TSAttachmentStream *attachmentStream))successHandler diff --git a/SignalServiceKit/src/Messages/Attachments/TSAttachmentStream.m b/SignalServiceKit/src/Messages/Attachments/TSAttachmentStream.m index dc09f7ad7..e1ef97574 100644 --- a/SignalServiceKit/src/Messages/Attachments/TSAttachmentStream.m +++ b/SignalServiceKit/src/Messages/Attachments/TSAttachmentStream.m @@ -370,7 +370,7 @@ NS_ASSUME_NONNULL_BEGIN if (![[NSFileManager defaultManager] fileExistsAtPath:self.mediaURL.path]) { DDLogError(@"%@ while generating thumbnail, source file doesn't exist: %@", self.logTag, self.mediaURL); // If we're not lazy-restoring this message, the attachment should exist on disk. - OWSAssert(self.lazyRestoreFragmentId); +// OWSAssert(self.lazyRestoreFragmentId); return; } diff --git a/SignalServiceKit/src/Messages/DeviceSyncing/OWSSyncContactsMessage.m b/SignalServiceKit/src/Messages/DeviceSyncing/OWSSyncContactsMessage.m index d77fd6506..2bd3d4a2c 100644 --- a/SignalServiceKit/src/Messages/DeviceSyncing/OWSSyncContactsMessage.m +++ b/SignalServiceKit/src/Messages/DeviceSyncing/OWSSyncContactsMessage.m @@ -58,7 +58,7 @@ NS_ASSUME_NONNULL_BEGIN } OWSSignalServiceProtosAttachmentPointer *attachmentProto = - [self buildAttachmentProtoForAttachmentId:self.attachmentIds[0] filename:nil]; + [self buildProtoForAttachmentId:self.attachmentIds[0] filename:nil]; OWSSignalServiceProtosSyncMessageContactsBuilder *contactsBuilder = [OWSSignalServiceProtosSyncMessageContactsBuilder new]; diff --git a/SignalServiceKit/src/Messages/DeviceSyncing/OWSSyncGroupsMessage.m b/SignalServiceKit/src/Messages/DeviceSyncing/OWSSyncGroupsMessage.m index 674eabb84..4a9701e13 100644 --- a/SignalServiceKit/src/Messages/DeviceSyncing/OWSSyncGroupsMessage.m +++ b/SignalServiceKit/src/Messages/DeviceSyncing/OWSSyncGroupsMessage.m @@ -35,7 +35,7 @@ NS_ASSUME_NONNULL_BEGIN (unsigned long)self.attachmentIds.count); } OWSSignalServiceProtosAttachmentPointer *attachmentProto = - [self buildAttachmentProtoForAttachmentId:self.attachmentIds[0] filename:nil]; + [self buildProtoForAttachmentId:self.attachmentIds[0] filename:nil]; OWSSignalServiceProtosSyncMessageGroupsBuilder *groupsBuilder = [OWSSignalServiceProtosSyncMessageGroupsBuilder new]; diff --git a/SignalServiceKit/src/Messages/Interactions/TSOutgoingMessage.h b/SignalServiceKit/src/Messages/Interactions/TSOutgoingMessage.h index 4f7e6d81d..1ba24257b 100644 --- a/SignalServiceKit/src/Messages/Interactions/TSOutgoingMessage.h +++ b/SignalServiceKit/src/Messages/Interactions/TSOutgoingMessage.h @@ -132,8 +132,8 @@ typedef NS_ENUM(NSInteger, TSGroupMetaMessage) { * @return * An attachment pointer protobuf suitable for including in various container protobuf builders */ -- (OWSSignalServiceProtosAttachmentPointer *)buildAttachmentProtoForAttachmentId:(NSString *)attachmentId - filename:(nullable NSString *)filename; +- (OWSSignalServiceProtosAttachmentPointer *)buildProtoForAttachmentId:(NSString *)attachmentId + filename:(nullable NSString *)filename; - (BOOL)shouldBeSaved; diff --git a/SignalServiceKit/src/Messages/Interactions/TSOutgoingMessage.m b/SignalServiceKit/src/Messages/Interactions/TSOutgoingMessage.m index 0255297fa..d8595034f 100644 --- a/SignalServiceKit/src/Messages/Interactions/TSOutgoingMessage.m +++ b/SignalServiceKit/src/Messages/Interactions/TSOutgoingMessage.m @@ -415,8 +415,7 @@ NSString *const kTSOutgoingMessageSentRecipientAll = @"kTSOutgoingMessageSentRec case TSGroupMessageNew: { if (gThread.groupModel.groupImage != nil && self.attachmentIds.count == 1) { attachmentWasGroupAvatar = YES; - [groupBuilder - setAvatar:[self buildAttachmentProtoForAttachmentId:self.attachmentIds[0] filename:nil]]; + [groupBuilder setAvatar:[self buildProtoForAttachmentId:self.attachmentIds[0] filename:nil]]; } [groupBuilder setMembersArray:gThread.groupModel.groupMemberIds]; @@ -437,7 +436,7 @@ NSString *const kTSOutgoingMessageSentRecipientAll = @"kTSOutgoingMessageSentRec NSMutableArray *attachments = [NSMutableArray new]; for (NSString *attachmentId in self.attachmentIds) { NSString *_Nullable sourceFilename = self.attachmentFilenameMap[attachmentId]; - [attachments addObject:[self buildAttachmentProtoForAttachmentId:attachmentId filename:sourceFilename]]; + [attachments addObject:[self buildProtoForAttachmentId:attachmentId filename:sourceFilename]]; } [builder setAttachmentsArray:attachments]; } @@ -456,14 +455,15 @@ NSString *const kTSOutgoingMessageSentRecipientAll = @"kTSOutgoingMessageSentRec [quoteBuilder setText:quotedMessage.body]; } - if (quotedMessage.thumbnailAttachmentIds.count > 0) { + if (quotedMessage.attachmentInfos) { NSMutableArray *thumbnailAttachments = [NSMutableArray new]; - for (NSString *attachmentId in quotedMessage.thumbnailAttachmentIds) { - hasQuotedAttachment = YES; - NSString *_Nullable sourceFilename = quotedMessage.thumbnailAttachmentFilenameMap[attachmentId]; - [thumbnailAttachments addObject:[self buildAttachmentProtoForAttachmentId:attachmentId filename:sourceFilename]]; - } - [quoteBuilder setAttachmentsArray:thumbnailAttachments]; + // FIXME TODO if has thumbnail build proto for attachment stream + // but if no thumbnail we only set contentType/filename +// for (TSAttachmentStream *attachment in quotedMessage.thumbnailAttachments) { +// OWSAssert([attachment isKindOfClass:[TSAttachmentStream class]]); +// +// [quoteBuilder addAttachments:[self buildProtoForAttachmentStream:attachment filename:attachment.sourceFilename]];] +// } } if (hasQuotedText || hasQuotedAttachment) { @@ -498,8 +498,8 @@ NSString *const kTSOutgoingMessageSentRecipientAll = @"kTSOutgoingMessageSentRec return !self.hasSyncedTranscript; } -- (OWSSignalServiceProtosAttachmentPointer *)buildAttachmentProtoForAttachmentId:(NSString *)attachmentId - filename:(nullable NSString *)filename +- (OWSSignalServiceProtosAttachmentPointer *)buildProtoForAttachmentId:(NSString *)attachmentId + filename:(nullable NSString *)filename { OWSAssert(attachmentId.length > 0); @@ -509,7 +509,12 @@ NSString *const kTSOutgoingMessageSentRecipientAll = @"kTSOutgoingMessageSentRec return nil; } TSAttachmentStream *attachmentStream = (TSAttachmentStream *)attachment; + return [self buildProtoForAttachmentStream:attachmentStream filename:filename]; +} +- (OWSSignalServiceProtosAttachmentPointer *)buildProtoForAttachmentStream:(TSAttachmentStream *)attachmentStream + filename:(nullable NSString *)filename +{ OWSSignalServiceProtosAttachmentPointerBuilder *builder = [OWSSignalServiceProtosAttachmentPointerBuilder new]; [builder setId:attachmentStream.serverId]; OWSAssert(attachmentStream.contentType.length > 0); diff --git a/SignalServiceKit/src/Messages/Interactions/TSQuotedMessage.h b/SignalServiceKit/src/Messages/Interactions/TSQuotedMessage.h index d24c0ecb3..d9a21e696 100644 --- a/SignalServiceKit/src/Messages/Interactions/TSQuotedMessage.h +++ b/SignalServiceKit/src/Messages/Interactions/TSQuotedMessage.h @@ -3,10 +3,23 @@ // #import +#import NS_ASSUME_NONNULL_BEGIN @class TSAttachment; +@class TSAttachmentStream; + +@interface OWSAttachmentInfo: MTLModel + +@property (nonatomic, readonly, nullable) NSString *contentType; +@property (nonatomic, readonly, nullable) NSString *sourceFilename; +@property (nonatomic, readonly, nullable) NSString *attachmentId; + +- (instancetype)init NS_UNAVAILABLE; +- (instancetype)initWithAttachment:(TSAttachment *)attachment; + +@end @interface TSQuotedMessage : TSYapDatabaseObject @@ -17,31 +30,32 @@ NS_ASSUME_NONNULL_BEGIN // or attachment with caption. @property (nullable, nonatomic, readonly) NSString *body; -//// This property can be set IFF we are quoting an attachment message, but it is optional. -//@property (nullable, nonatomic, readonly) NSData *thumbnailData; +#pragma mark - Attachments // This is a MIME type. // // This property should be set IFF we are quoting an attachment message. -@property (nullable, nonatomic, readonly) NSString *contentType; +- (nullable NSString *)contentType; +- (nullable NSString *)sourceFilename; -- (instancetype)init NS_UNAVAILABLE; +@property (atomic, readonly) NSArray *attachmentInfos; +- (void)addAttachment:(TSAttachmentStream *)attachment; +- (BOOL)hasAttachments; -- (instancetype)initWithTimestamp:(uint64_t)timestamp - authorId:(NSString *)authorId - body:(NSString *_Nullable)body - sourceFilename:(NSString *_Nullable)sourceFilename - thumbnailData:(NSData *_Nullable)thumbnailData - contentType:(NSString *_Nullable)contentType; +- (nullable TSAttachment *)firstAttachmentWithTransaction:(YapDatabaseReadTransaction *)transaction; +- (nullable UIImage *)thumbnailImageWithTransaction:(YapDatabaseReadTransaction *)transaction; -#pragma mark - Attachments +- (instancetype)init NS_UNAVAILABLE; -@property (nonatomic, readonly) NSArray *thumbnailAttachmentIds; -// A map of attachment id-to-"source" filename. -@property (nonatomic, readonly) NSMutableDictionary *thumbnailAttachmentFilenameMap; +- (instancetype)initOutgoingWithTimestamp:(uint64_t)timestamp + authorId:(NSString *)authorId + body:(NSString *_Nullable)body + attachment:(TSAttachmentStream *_Nullable)attachmentStream; -- (BOOL)hasThumbnailAttachments; -- (nullable TSAttachment *)firstThumbnailAttachmentWithTransaction:(YapDatabaseReadTransaction *)transaction; +- (instancetype)initIncomingWithTimestamp:(uint64_t)timestamp + authorId:(NSString *)authorId + body:(NSString *_Nullable)body + attachments:(NSArray *)attachments; @end diff --git a/SignalServiceKit/src/Messages/Interactions/TSQuotedMessage.m b/SignalServiceKit/src/Messages/Interactions/TSQuotedMessage.m index 8b154cde0..0fb7d7c40 100644 --- a/SignalServiceKit/src/Messages/Interactions/TSQuotedMessage.m +++ b/SignalServiceKit/src/Messages/Interactions/TSQuotedMessage.m @@ -4,74 +4,171 @@ #import "TSQuotedMessage.h" #import "TSAttachment.h" +#import "TSAttachmentStream.h" NS_ASSUME_NONNULL_BEGIN -@implementation TSQuotedMessage +@implementation OWSAttachmentInfo -- (instancetype)initWithTimestamp:(uint64_t)timestamp - authorId:(NSString *)authorId - body:(NSString *_Nullable)body - sourceFilename:(NSString *_Nullable)sourceFilename - thumbnailData:(NSData *_Nullable)thumbnailData - contentType:(NSString *_Nullable)contentType +- (instancetype)initWithAttachment:(TSAttachment *)attachment { - self = [super initWithUniqueId:[NSUUID UUID].UUIDString]; + self = [super init]; if (!self) { return self; } + + OWSAssert(attachment.uniqueId); + OWSAssert(attachment.contentType); + + _attachmentId = attachment.uniqueId; + _contentType = attachment.contentType; + + // maybe nil + _sourceFilename = attachment.sourceFilename; + + return self; +} + +@end + +@interface TSQuotedMessage () + +@property (atomic) NSArray *thumbnailAttachments; + + +@end +@implementation TSQuotedMessage + +- (instancetype)initOutgoingWithTimestamp:(uint64_t)timestamp + authorId:(NSString *)authorId + body:(NSString *_Nullable)body + attachment:(TSAttachmentStream *_Nullable)attachmentStream +{ + return [self initWithTimestamp:timestamp authorId:authorId body:body attachments:@[ attachmentStream ]]; +} + +- (instancetype)initIncomingWithTimestamp:(uint64_t)timestamp + authorId:(NSString *)authorId + body:(NSString *_Nullable)body + attachments:(NSArray *)attachments +{ + return [self initWithTimestamp:timestamp authorId:authorId body:body attachments:attachments]; +} + +- (instancetype)initWithTimestamp:(uint64_t)timestamp + authorId:(NSString *)authorId + body:(NSString *_Nullable)body + attachments:(NSArray *)attachments +{ OWSAssert(timestamp > 0); OWSAssert(authorId.length > 0); + self = [super init]; + if (!self) { + return nil; + } + _timestamp = timestamp; _authorId = authorId; _body = body; - // TODO get source filename from attachment -// _sourceFilename = sourceFilename; -// _thumbnailData = thumbnailData; - _contentType = contentType; + + NSMutableArray *attachmentInfos = [NSMutableArray new]; + for (TSAttachment *attachment in attachments) { + [attachmentInfos addObject:[[OWSAttachmentInfo alloc] initWithAttachment:attachment]]; + } + _thumbnailAttachments = [attachmentInfos copy]; return self; } -// TODO maybe this should live closer to the view -- (nullable UIImage *)thumbnailImage +- (nullable TSAttachment *)firstAttachmentWithTransaction:(YapDatabaseReadTransaction *)transaction { -// if (self.thumbnailData.length == 0) { -// return nil; -// } -// -// // PERF TODO cache -// return [UIImage imageWithData:self.thumbnailData]; - return nil; + OWSAttachmentInfo *attachmentInfo = self.firstAttachmentInfo; + if (!attachmentInfo) { + return nil; + } + + return [TSAttachment fetchObjectWithUniqueID:attachmentInfo.attachmentId]; } -//- (void)setThumbnailAttachmentId:(NSString *)thumbnailAttachmentId -//{ -// _thumbnailAttachmentId = thumbnailAttachmentId; -//} -// -//- (BOOL)hasThumbnailAttachment -//{ -// return self.thumbnailAttachmentId.length > 0; -//} -// - -- (BOOL)hasThumbnailAttachments +- (nullable OWSAttachmentInfo *)firstAttachmentInfo { - return self.thumbnailAttachmentIds.count > 0; + return self.attachmentInfos.firstObject; } -- (nullable TSAttachment *)firstThumbnailAttachmentWithTransaction:(YapDatabaseReadTransaction *)transaction; +- (nullable UIImage *)thumbnailImageWithTransaction:(YapDatabaseReadTransaction *)transaction { - if (!self.hasThumbnailAttachments) { + TSAttachmentStream *firstAttachment = (TSAttachmentStream *)self.firstThumbnailAttachment; + if (![firstAttachment isKindOfClass:[TSAttachmentStream class]]) { return nil; } + + return firstAttachment.thumbnailImage; +} + +- (nullable NSString *)contentType +{ + OWSAttachmentInfo *firstAttachment = self.firstThumbnailAttachment; + + return firstAttachment.contentType; +} + +- (BOOL)hasThumbnailAttachments +{ + return self.thumbnailAttachments.count > 0; +} + +- (void)addThumbnailAttachment:(TSAttachmentStream *)attachment +{ + NSMutableArray *existingAttachments = [self.thumbnailAttachments mutableCopy]; + + OWSAttachmentInfo *attachmentInfo = [[OWSAttachmentInfo alloc] initWithAttachment:attachment]; + [existingAttachments addObject:attachmentInfo]; + + self.thumbnailAttachments = [existingAttachments copy]; +} + +- (nullable OWSAttachmentInfo *)firstThumbnailAttachment +{ + return self.thumbnailAttachments.firstObject; +} + +- (TSAttachmentStream *)thumbnailAttachmentWithTransaction:(YapDatabaseReadTransaction *)transaction +{ - return [TSAttachment fetchObjectWithUniqueID:self.thumbnailAttachmentIds.firstObject transaction:transaction]; } +- (void)createThumbnailAttachmentIfNecessaryWithTransaction:(YapDatabaseReadWriteTransaction *)transaction +{ +// OWSAssert([attachment isKindOfClass:[TSAttachmentStream class]]); +// UIImage *thumbnailImage = attachment.thumbnailImage; +// // Only some media types have thumbnails +// if (thumbnailImage) { +// // Copy the thumbnail to a new attachment. +// TSAttachmentStream *thumbnailAttachment = +// [[TSAttachmentStream alloc] initWithContentType:attachment.contentType +// byteCount:attachment.byteCount +// sourceFilename:attachment.sourceFilename]; +// +// NSError *error; +// NSData *_Nullable data = [attachment readDataFromFileWithError:&error]; +// if (!data || error) { +// DDLogError(@"%@ Couldn't load attachment data for message sent to self: %@.", self.logTag, error); +// } else { +// [thumbnailAttachment writeData:data error:&error]; +// if (error) { +// DDLogError( +// @"%@ Couldn't copy attachment data for message sent to self: %@.", self.logTag, error); +// } else { +// [self.dbConnection readWriteWithBlock:^(YapDatabaseReadWriteTransaction *_Nonnull transaction) { +// [thumbnailAttachment saveWithTransaction:transaction]; +// quotedMessage.attachments = +// [message saveWithTransaction:transaction]; +// }]; +// } +// } +} @end NS_ASSUME_NONNULL_END diff --git a/SignalServiceKit/src/Messages/OWSMessageManager.m b/SignalServiceKit/src/Messages/OWSMessageManager.m index ec172349d..398a07fb7 100644 --- a/SignalServiceKit/src/Messages/OWSMessageManager.m +++ b/SignalServiceKit/src/Messages/OWSMessageManager.m @@ -33,6 +33,8 @@ #import "OWSSyncGroupsRequestMessage.h" #import "ProfileManagerProtocol.h" #import "TSAccountManager.h" +#import "TSAttachment.h" +#import "TSAttachmentPointer.h" #import "TSContactThread.h" #import "TSDatabaseView.h" #import "TSGroupModel.h" @@ -512,9 +514,7 @@ NS_ASSUME_NONNULL_BEGIN OWSAssert(groupThread); OWSAttachmentsProcessor *attachmentsProcessor = [[OWSAttachmentsProcessor alloc] initWithAttachmentProtos:@[ dataMessage.group.avatar ] - timestamp:envelope.timestamp relay:envelope.relay - thread:groupThread networkManager:self.networkManager primaryStorage:self.primaryStorage transaction:transaction]; @@ -552,9 +552,7 @@ NS_ASSUME_NONNULL_BEGIN OWSAttachmentsProcessor *attachmentsProcessor = [[OWSAttachmentsProcessor alloc] initWithAttachmentProtos:dataMessage.attachments - timestamp:envelope.timestamp relay:envelope.relay - thread:thread networkManager:self.networkManager primaryStorage:self.primaryStorage transaction:transaction]; @@ -993,7 +991,8 @@ NS_ASSUME_NONNULL_BEGIN return nil; } - TSQuotedMessage *_Nullable quotedMessage = [self quotedMessageForDataMessage:dataMessage]; + TSQuotedMessage *_Nullable quotedMessage = + [self quotedMessageForDataMessage:dataMessage envelope:envelope transaction:transaction]; DDLogDebug(@"%@ incoming message from: %@ for group: %@ with timestamp: %lu", self.logTag, @@ -1041,7 +1040,8 @@ NS_ASSUME_NONNULL_BEGIN transaction:transaction relay:envelope.relay]; - TSQuotedMessage *_Nullable quotedMessage = [self quotedMessageForDataMessage:dataMessage]; + TSQuotedMessage *_Nullable quotedMessage = + [self quotedMessageForDataMessage:dataMessage envelope:envelope transaction:transaction]; TSIncomingMessage *incomingMessage = [[TSIncomingMessage alloc] initIncomingMessageWithTimestamp:timestamp @@ -1063,6 +1063,8 @@ NS_ASSUME_NONNULL_BEGIN } - (TSQuotedMessage *_Nullable)quotedMessageForDataMessage:(OWSSignalServiceProtosDataMessage *)dataMessage + envelope:(OWSSignalServiceProtosEnvelope *)envelope + transaction:(YapDatabaseReadWriteTransaction *)transaction { OWSAssert(dataMessage); @@ -1093,22 +1095,39 @@ NS_ASSUME_NONNULL_BEGIN hasText = YES; } - NSString *_Nullable sourceFilename = nil; - NSData *_Nullable thumbnailData = nil; - NSString *_Nullable contentType = nil; + NSArray *attachments; - if (quoteProto.attachments.count > 0) { - OWSSignalServiceProtosAttachmentPointer *attachmentProto = quoteProto.attachments.firstObject; - if ([attachmentProto hasContentType] && attachmentProto.contentType.length > 0) { - contentType = attachmentProto.contentType; - - if ([attachmentProto hasFileName] && attachmentProto.fileName.length > 0) { - sourceFilename = attachmentProto.fileName; - } - if ([attachmentProto hasThumbnail] && attachmentProto.thumbnail.length > 0) { - thumbnailData = [attachmentProto thumbnail]; - } + if (quoteProto.attachments.count == 0) { + attachments = @[]; + } else { + OWSAttachmentsProcessor *attachmentsProcessor = + [[OWSAttachmentsProcessor alloc] initWithAttachmentProtos:quoteProto.attachments + relay:envelope.relay + networkManager:self.networkManager + primaryStorage:self.primaryStorage + transaction:transaction]; + + if (!attachmentsProcessor.hasSupportedAttachments) { + attachments = @[]; + } else { + attachments = attachmentsProcessor.supportedAttachmentPointers; } + + // TODO + // [attachmentsProcessor fetchAttachmentsForMessage:nil + // transaction:transaction + // success:^(TSAttachmentStream *attachmentStream) { + // [groupThread + // updateAvatarWithAttachmentStream:attachmentStream]; + // } + // failure:^(NSError *error) { + // DDLogError(@"%@ failed to fetch attachments for group + // avatar sent at: %llu. with error: %@", + // self.logTag, + // envelope.timestamp, + // error); + // }]; + hasAttachment = YES; } @@ -1117,12 +1136,17 @@ NS_ASSUME_NONNULL_BEGIN return nil; } - TSQuotedMessage *quotedMessage = [[TSQuotedMessage alloc] initWithTimestamp:timestamp - authorId:authorId - body:body - sourceFilename:sourceFilename - thumbnailData:thumbnailData - contentType:contentType]; + // TSQuotedMessage *quotedMessage = [[TSQuotedMessage alloc] initIncomingWithTimestamp:timestamp + // authorId:authorId + // body:body + // sourceFilename:sourceFilename + // thumbnailData:thumbnailData + // contentType:contentType]; + TSQuotedMessage *quotedMessage = [[TSQuotedMessage alloc] initIncomingWithTimestamp:timestamp + authorId:authorId + body:body + attachments:attachments]; + return quotedMessage; } diff --git a/SignalServiceKit/src/Messages/OWSMessageSender.m b/SignalServiceKit/src/Messages/OWSMessageSender.m index f32005f7a..ce1c280c6 100644 --- a/SignalServiceKit/src/Messages/OWSMessageSender.m +++ b/SignalServiceKit/src/Messages/OWSMessageSender.m @@ -319,124 +319,37 @@ NSString *const OWSMessageSenderRateLimitedException = @"RateLimitedException"; if (message.hasAttachments) { OWSUploadOperation *uploadAttachmentOperation = [[OWSUploadOperation alloc] initWithAttachmentId:message.attachmentIds.firstObject - message:message dbConnection:self.dbConnection]; [sendMessageOperation addDependency:uploadAttachmentOperation]; [sendingQueue addOperation:uploadAttachmentOperation]; } - // if (message.quotedMessage.hasThumbnailAttachments) { - // OWSUploadOperation *uploadQuoteThumbnailOperation = [[OWSUploadOperation alloc] - // initWithAttachmentId:message.attachmentIds.firstObject - // message:message - // dbConnection:self.dbConnection]; - // [sendMessageOperation addDependency:uploadAttachmentOperation]; - // [sendingQueue addOperation:uploadQuoteThumbnailOperation]; - // } + + if (message.quotedMessage) { + + // TODO do we want a different thumbnail size for quotes vs the gallery? This seems reasonable, + // and has the advantage of already having been generated. + __block TSAttachmentStream *attachment; + [self.dbConnection readWriteWithBlock:^(YapDatabaseReadWriteTransaction *_Nonnull transaction) { + [message.quotedMessage createThumbnailAttachmentIfNecessaryWithTransaction:transaction]; + attachment = (TSAttachmentStream *)[message.quotedMessage thumbnailAttachmentWithTransaction:transaction]; + }]; + + if (attachment) { + OWSUploadOperation *uploadQuoteThumbnailOperation = + [[OWSUploadOperation alloc] initWithAttachmentId:thumbnailAttachment.uniqueId + dbConnection:self.dbConnection]; + + // TODO put attachment uploads on a (lowly) concurrent queue + [sendMessageOperation addDependency:uploadQuoteThumbnailOperation]; + [sendingQueue addOperation:uploadQuoteThumbnailOperation]; + } + } [sendingQueue addOperation:sendMessageOperation]; }); } -//- (void)attemptToSendMessage:(TSOutgoingMessage *)message -// success:(void (^)(void))successHandler -// failure:(RetryableFailureHandler)failureHandler -//{ -// [self ensureAnyAttachmentsUploaded:message -// success:^() { -// [self sendMessageToService:message -// success:successHandler -// failure:^(NSError *error) { -// DDLogDebug( -// @"%@ Message send attempt failed: %@", self.logTag, message.debugDescription); -// failureHandler(error); -// }]; -// } -// failure:^(NSError *error) { -// DDLogDebug(@"%@ Attachment upload attempt failed: %@", self.logTag, message.debugDescription); -// failureHandler(error); -// }]; -//} -//- (void)ensureAnyAttachmentsUploaded:(TSOutgoingMessage *)message -// success:(void (^)(void))successHandler -// failure:(RetryableFailureHandler)failureHandler -//{ -// if (!message.hasAttachments) { -// return successHandler(); -// } -// -// TSAttachmentStream *attachmentStream = -// [TSAttachmentStream fetchObjectWithUniqueID:message.attachmentIds.firstObject]; -// -// if (!attachmentStream) { -// OWSProdError([OWSAnalyticsEvents messageSenderErrorCouldNotLoadAttachment]); -// NSError *error = OWSErrorMakeFailedToSendOutgoingMessageError(); -// // Not finding local attachment is a terminal failure. -// [error setIsRetryable:NO]; -// return failureHandler(error); -// } -// -// [OWSUploadingService uploadAttachmentStream:attachmentStream -// message:message -// networkManager:self.networkManager -// success:successHandler -// failure:failureHandler]; -//} - -//- (void)ensureAnyQuotedThumbnailUploaded:(TSOutgoingMessage *)message -// success:(void (^)(void))successHandler -// failure:(RetryableFailureHandler)failureHandler -//{ -// if (!message.hasAttachments) { -// return successHandler(); -// } -// -// TSAttachmentStream *attachmentStream = -// [TSAttachmentStream fetchObjectWithUniqueID:message.attachmentIds.firstObject]; -// -// if (!attachmentStream) { -// OWSProdError([OWSAnalyticsEvents messageSenderErrorCouldNotLoadAttachment]); -// NSError *error = OWSErrorMakeFailedToSendOutgoingMessageError(); -// // Not finding local attachment is a terminal failure. -// [error setIsRetryable:NO]; -// return failureHandler(error); -// } -// -// if (message.quotedMessage.hasThumbnailAttachment) { -// DDLogDebug(@"%@ uploading thumbnail for message: %llu", self.logTag, message.timestamp); -// -// __block TSAttachmentStream *thumbnailAttachmentStream; -// [self.dbConnection readWithBlock:^(YapDatabaseReadTransaction * _Nonnull transaction) { -// thumbnailAttachmentStream = [message.quotedMessage thumbnailAttachmentWithTransaction:transaction]; -// }]; -// -// if (!thumbnailAttachmentStream) { -// OWSProdError([OWSAnalyticsEvents messageSenderErrorCouldNotLoadAttachment]); -// NSError *error = OWSErrorMakeFailedToSendOutgoingMessageError(); -// // Not finding local attachment is a terminal failure. -// [error setIsRetryable:NO]; -// return failureHandler(error); -// } -// -// [self.uploadingService uploadAttachmentStream:attachmentStream -// message:message -// success:^() { -// [self.uploadingService uploadAttachmentStream:attachmentStream -// message:message -// success:successHandler -// failure:failureHandler]; -// } -// failure:failureHandler]; -// -// } -// [self.uploadingService uploadAttachmentStream:attachmentStream -// message:message -// success:successHandler -// failure:failureHandler]; -// -// -//} - - (void)enqueueTemporaryAttachment:(DataSource *)dataSource contentType:(NSString *)contentType inMessage:(TSOutgoingMessage *)message diff --git a/SignalServiceKit/src/Messages/OWSMessageUtils.m b/SignalServiceKit/src/Messages/OWSMessageUtils.m index 475b9b453..33b8f2f20 100644 --- a/SignalServiceKit/src/Messages/OWSMessageUtils.m +++ b/SignalServiceKit/src/Messages/OWSMessageUtils.m @@ -141,63 +141,55 @@ NS_ASSUME_NONNULL_BEGIN NSString *_Nullable quotedText = message.body; BOOL hasText = quotedText.length > 0; BOOL hasAttachment = NO; - NSString *_Nullable sourceFilename = nil; - NSData *_Nullable thumbnailData = nil; - NSString *_Nullable contentType = nil; - - if (message.attachmentIds.count > 0) { - NSString *attachmentId = message.attachmentIds[0]; - TSAttachment *_Nullable attachment = - [TSAttachment fetchObjectWithUniqueID:attachmentId transaction:transaction]; - if (attachment) { - // If the attachment is "oversize text", try to treat it appropriately. - if (!hasText && [OWSMimeTypeOversizeTextMessage isEqualToString:attachment.contentType] && - [attachment isKindOfClass:[TSAttachmentStream class]]) { - - hasText = YES; - quotedText = @""; - - TSAttachmentStream *attachmentStream = (TSAttachmentStream *)attachment; - NSData *_Nullable oversizeTextData = [NSData dataWithContentsOfFile:attachmentStream.filePath]; - if (oversizeTextData) { - // We don't need to include the entire text body of the message, just - // enough to render a snippet. kOversizeTextMessageSizeThreshold is our - // limit on how long text should be in protos since they'll be stored in - // the database. We apply this constant here for the same reasons. - NSString *_Nullable oversizeText = - [[NSString alloc] initWithData:oversizeTextData encoding:NSUTF8StringEncoding]; - // First, truncate to the rough max characters. - NSString *_Nullable truncatedText = - [oversizeText substringToIndex:kOversizeTextMessageSizeThreshold - 1]; - // But kOversizeTextMessageSizeThreshold is in _bytes_, not characters, - // so we need to continue to trim the string until it fits. - while (truncatedText && truncatedText.length > 0 && - [truncatedText dataUsingEncoding:NSUTF8StringEncoding].length - >= kOversizeTextMessageSizeThreshold) { - // A very coarse binary search by halving is acceptable, since - // kOversizeTextMessageSizeThreshold is much longer than our target - // length of "three short lines of text on any device we might - // display this on. - // - // The search will always converge since in the worst case (namely - // a single character which in utf-8 is >= 1024 bytes) the loop will - // exit when the string is empty. - truncatedText = [truncatedText substringToIndex:oversizeText.length / 2]; - } - if ([truncatedText dataUsingEncoding:NSUTF8StringEncoding].length - < kOversizeTextMessageSizeThreshold) { - quotedText = truncatedText; - } else { - OWSFail(@"%@ Missing valid text snippet.", self.logTag); - } + + TSAttachment *_Nullable attachment = [message attachmentWithTransaction:transaction]; + TSAttachmentStream *quotedAttachment; + if (attachment && [attachment isKindOfClass:[TSAttachmentStream class]]) { + + TSAttachmentStream *attachmentStream = (TSAttachmentStream *)attachment; + + // If the attachment is "oversize text", try the quote as a reply to text, not as + // a reply to an attachment. + if (!hasText && [OWSMimeTypeOversizeTextMessage isEqualToString:attachment.contentType]) { + hasText = YES; + quotedText = @""; + + NSData *_Nullable oversizeTextData = [NSData dataWithContentsOfFile:attachmentStream.filePath]; + if (oversizeTextData) { + // We don't need to include the entire text body of the message, just + // enough to render a snippet. kOversizeTextMessageSizeThreshold is our + // limit on how long text should be in protos since they'll be stored in + // the database. We apply this constant here for the same reasons. + NSString *_Nullable oversizeText = + [[NSString alloc] initWithData:oversizeTextData encoding:NSUTF8StringEncoding]; + // First, truncate to the rough max characters. + NSString *_Nullable truncatedText = + [oversizeText substringToIndex:kOversizeTextMessageSizeThreshold - 1]; + // But kOversizeTextMessageSizeThreshold is in _bytes_, not characters, + // so we need to continue to trim the string until it fits. + while (truncatedText && truncatedText.length > 0 && + [truncatedText dataUsingEncoding:NSUTF8StringEncoding].length + >= kOversizeTextMessageSizeThreshold) { + // A very coarse binary search by halving is acceptable, since + // kOversizeTextMessageSizeThreshold is much longer than our target + // length of "three short lines of text on any device we might + // display this on. + // + // The search will always converge since in the worst case (namely + // a single character which in utf-8 is >= 1024 bytes) the loop will + // exit when the string is empty. + truncatedText = [truncatedText substringToIndex:oversizeText.length / 2]; + } + if ([truncatedText dataUsingEncoding:NSUTF8StringEncoding].length + < kOversizeTextMessageSizeThreshold) { + quotedText = truncatedText; + } else { + OWSFail(@"%@ Missing valid text snippet.", self.logTag); } - } else { - sourceFilename = attachment.sourceFilename; - contentType = attachment.contentType; - // Try to generate a thumbnail, if possible. - thumbnailData = [self thumbnailDataForAttachment:attachment]; - hasAttachment = YES; } + } else { + quotedAttachment = attachmentStream; + hasAttachment = YES; } } @@ -206,14 +198,10 @@ NS_ASSUME_NONNULL_BEGIN return nil; } - // It's conceivable that the logic above will find neither valid text - // or an attachment to quote. - TSQuotedMessage *quotedMessage = [[TSQuotedMessage alloc] initWithTimestamp:timestamp - authorId:authorId - body:quotedText - sourceFilename:sourceFilename - thumbnailData:thumbnailData - contentType:contentType]; + TSQuotedMessage *quotedMessage = [[TSQuotedMessage alloc] initOutgoingWithTimestamp:timestamp + authorId:authorId + body:quotedText + attachment:quotedAttachment]; return quotedMessage; } diff --git a/SignalServiceKit/src/Network/API/OWSUploadOperation.h b/SignalServiceKit/src/Network/API/OWSUploadOperation.h index d102ec285..ebbaadd4f 100644 --- a/SignalServiceKit/src/Network/API/OWSUploadOperation.h +++ b/SignalServiceKit/src/Network/API/OWSUploadOperation.h @@ -19,7 +19,6 @@ extern NSString *const kAttachmentUploadAttachmentIDKey; - (instancetype)init NS_UNAVAILABLE; - (instancetype)initWithAttachmentId:(NSString *)attachmentId - message:(TSOutgoingMessage *)outgoingMessage dbConnection:(YapDatabaseConnection *)dbConnection NS_DESIGNATED_INITIALIZER; @end diff --git a/SignalServiceKit/src/Network/API/OWSUploadOperation.m b/SignalServiceKit/src/Network/API/OWSUploadOperation.m index c30162c2c..58a649987 100644 --- a/SignalServiceKit/src/Network/API/OWSUploadOperation.m +++ b/SignalServiceKit/src/Network/API/OWSUploadOperation.m @@ -27,7 +27,6 @@ static const CGFloat kAttachmentUploadProgressTheta = 0.001f; @interface OWSUploadOperation () @property (readonly, nonatomic) NSString *attachmentId; -@property (readonly, nonatomic) TSOutgoingMessage *outgoingMessage; @property (readonly, nonatomic) YapDatabaseConnection *dbConnection; @end @@ -35,7 +34,6 @@ static const CGFloat kAttachmentUploadProgressTheta = 0.001f; @implementation OWSUploadOperation - (instancetype)initWithAttachmentId:(NSString *)attachmentId - message:(TSOutgoingMessage *)outgoingMessage dbConnection:(YapDatabaseConnection *)dbConnection { self = [super init]; @@ -45,7 +43,6 @@ static const CGFloat kAttachmentUploadProgressTheta = 0.001f; self.remainingRetries = 4; _attachmentId = attachmentId; - _outgoingMessage = outgoingMessage; _dbConnection = dbConnection; return self; diff --git a/SignalServiceKit/src/Util/DataSource.h b/SignalServiceKit/src/Util/DataSource.h index ee5702d14..00b474ab3 100755 --- a/SignalServiceKit/src/Util/DataSource.h +++ b/SignalServiceKit/src/Util/DataSource.h @@ -1,10 +1,10 @@ // -// Copyright (c) 2017 Open Whisper Systems. All rights reserved. +// Copyright (c) 2018 Open Whisper Systems. All rights reserved. // NS_ASSUME_NONNULL_BEGIN -// A protocol that abstracts away a source of NSData +// A base class that abstracts away a source of NSData // and allows us to: // // * Lazy-load if possible. diff --git a/SignalServiceKit/src/Util/MIMETypeUtil.h b/SignalServiceKit/src/Util/MIMETypeUtil.h index f83a273b4..3757adf59 100644 --- a/SignalServiceKit/src/Util/MIMETypeUtil.h +++ b/SignalServiceKit/src/Util/MIMETypeUtil.h @@ -7,6 +7,7 @@ NS_ASSUME_NONNULL_BEGIN extern NSString *const OWSMimeTypeApplicationOctetStream; extern NSString *const OWSMimeTypeApplicationZip; extern NSString *const OWSMimeTypeImagePng; +extern NSString *const OWSMimeTypeImageJpeg; extern NSString *const OWSMimeTypeOversizeTextMessage; extern NSString *const OWSMimeTypeUnknownForTests; diff --git a/SignalServiceKit/src/Util/MIMETypeUtil.m b/SignalServiceKit/src/Util/MIMETypeUtil.m index 786731ef1..2127724ae 100644 --- a/SignalServiceKit/src/Util/MIMETypeUtil.m +++ b/SignalServiceKit/src/Util/MIMETypeUtil.m @@ -17,6 +17,7 @@ NS_ASSUME_NONNULL_BEGIN NSString *const OWSMimeTypeApplicationOctetStream = @"application/octet-stream"; NSString *const OWSMimeTypeImagePng = @"image/png"; +NSString *const OWSMimeTypeImageJpeg = @"image/jpeg"; NSString *const OWSMimeTypeOversizeTextMessage = @"text/x-signal-plain"; NSString *const OWSMimeTypeUnknownForTests = @"unknown/mimetype"; NSString *const OWSMimeTypeApplicationZip = @"application/zip";