From 51a4298c14238fcac7bd3d1cfa481d3d5273aaa7 Mon Sep 17 00:00:00 2001 From: Michael Kirk Date: Fri, 6 Apr 2018 12:31:20 -0400 Subject: [PATCH] WIP: Send attachment info in protobuf (no thumbnails yet) // FREEBIE --- Signal/src/views/QuotedReplyPreview.swift | 4 +- .../Attachments/OWSAttachmentsProcessor.h | 6 + .../Attachments/OWSAttachmentsProcessor.m | 1 - .../Messages/Attachments/TSAttachmentStream.h | 1 + .../Messages/Attachments/TSAttachmentStream.m | 18 ++ .../Messages/Interactions/TSOutgoingMessage.m | 26 +- .../Messages/Interactions/TSQuotedMessage.h | 25 +- .../Messages/Interactions/TSQuotedMessage.m | 223 ++++++++++-------- .../src/Messages/OWSMessageManager.m | 74 +++--- 9 files changed, 229 insertions(+), 149 deletions(-) diff --git a/Signal/src/views/QuotedReplyPreview.swift b/Signal/src/views/QuotedReplyPreview.swift index ff4e1dd66..893c2ce4e 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 = quotedReplyDraft.contentType() { + if let contentType = quotedReplyDraft.contentType { let emoji = TSAttachmentStream.emoji(forMimeType: contentType) return "\(emoji) \(quotedReplyDraft.body ?? "")" } else { @@ -52,7 +52,7 @@ class QuotedReplyPreview: UIView { }() let thumbnailView: UIView? = { - if let image = quotedReplyDraft.thumbnailImage() { + if let image = quotedReplyDraft.thumbnailImage { let imageView = UIImageView(image: image) imageView.contentMode = .scaleAspectFill imageView.autoPinToSquareAspectRatio() diff --git a/SignalServiceKit/src/Messages/Attachments/OWSAttachmentsProcessor.h b/SignalServiceKit/src/Messages/Attachments/OWSAttachmentsProcessor.h index 5f5fea5c1..68d70f7e8 100644 --- a/SignalServiceKit/src/Messages/Attachments/OWSAttachmentsProcessor.h +++ b/SignalServiceKit/src/Messages/Attachments/OWSAttachmentsProcessor.h @@ -36,6 +36,12 @@ extern NSString *const kAttachmentDownloadAttachmentIDKey; primaryStorage:(OWSPrimaryStorage *)primaryStorage transaction:(YapDatabaseReadWriteTransaction *)transaction NS_DESIGNATED_INITIALIZER; +- (instancetype)initWithQuotedAttachmentProtos:(NSArray *)attachmentProtos + relay:(nullable NSString *)relay + networkManager:(TSNetworkManager *)networkManager + primaryStorage:(OWSPrimaryStorage *)primaryStorage + transaction:(YapDatabaseReadWriteTransaction *)transaction NS_DESIGNATED_INITIALIZER; + /* * Retry fetching failed attachment download */ diff --git a/SignalServiceKit/src/Messages/Attachments/OWSAttachmentsProcessor.m b/SignalServiceKit/src/Messages/Attachments/OWSAttachmentsProcessor.m index b4576a542..384405d29 100644 --- a/SignalServiceKit/src/Messages/Attachments/OWSAttachmentsProcessor.m +++ b/SignalServiceKit/src/Messages/Attachments/OWSAttachmentsProcessor.m @@ -117,7 +117,6 @@ static const CGFloat kAttachmentDownloadProgressTheta = 0.001f; return self; } - // Remove this? - (void)fetchAttachmentsForMessage:(nullable TSMessage *)message primaryStorage:(OWSPrimaryStorage *)primaryStorage diff --git a/SignalServiceKit/src/Messages/Attachments/TSAttachmentStream.h b/SignalServiceKit/src/Messages/Attachments/TSAttachmentStream.h index 22e6fe54b..9289bf003 100644 --- a/SignalServiceKit/src/Messages/Attachments/TSAttachmentStream.h +++ b/SignalServiceKit/src/Messages/Attachments/TSAttachmentStream.h @@ -38,6 +38,7 @@ NS_ASSUME_NONNULL_BEGIN #if TARGET_OS_IPHONE - (nullable UIImage *)image; - (nullable UIImage *)thumbnailImage; +- (nullable NSData *)thumbnailData; #endif - (BOOL)isAnimated; diff --git a/SignalServiceKit/src/Messages/Attachments/TSAttachmentStream.m b/SignalServiceKit/src/Messages/Attachments/TSAttachmentStream.m index e1ef97574..1801c4e70 100644 --- a/SignalServiceKit/src/Messages/Attachments/TSAttachmentStream.m +++ b/SignalServiceKit/src/Messages/Attachments/TSAttachmentStream.m @@ -355,6 +355,24 @@ NS_ASSUME_NONNULL_BEGIN return [UIImage imageWithContentsOfFile:self.thumbnailPath]; } +- (nullable NSData *)thumbnailData +{ + NSString *thumbnailPath = self.thumbnailPath; + if (!thumbnailPath) { + OWSAssert(!self.isImage && !self.isVideo && !self.isAnimated); + + return nil; + } + + if (![[NSFileManager defaultManager] fileExistsAtPath:thumbnailPath]) { + OWSFail(@"%@ missing thumbnail for attachmentId: %@", self.logTag, self.uniqueId); + + return nil; + } + + return [NSData dataWithContentsOfFile:self.thumbnailPath]; +} + - (void)ensureThumbnail { NSString *thumbnailPath = self.thumbnailPath; diff --git a/SignalServiceKit/src/Messages/Interactions/TSOutgoingMessage.m b/SignalServiceKit/src/Messages/Interactions/TSOutgoingMessage.m index 8f6594d6a..529e04b84 100644 --- a/SignalServiceKit/src/Messages/Interactions/TSOutgoingMessage.m +++ b/SignalServiceKit/src/Messages/Interactions/TSOutgoingMessage.m @@ -472,15 +472,23 @@ NSString *const kTSOutgoingMessageSentRecipientAll = @"kTSOutgoingMessageSentRec [quoteBuilder setText:quotedMessage.body]; } - if (quotedMessage.attachmentInfos) { - NSMutableArray *thumbnailAttachments = [NSMutableArray new]; - // 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 (quotedMessage.quotedAttachments) { + for (OWSAttachmentInfo *attachment in quotedMessage.quotedAttachments) { + hasQuotedAttachment = YES; + + // Add non-thumbnail quoted attachment + OWSSignalServiceProtosAttachmentPointerBuilder *attachmentBuilder = + [OWSSignalServiceProtosAttachmentPointerBuilder new]; + attachmentBuilder.contentType = attachment.contentType; + attachmentBuilder.fileName = attachment.sourceFilename; + + [quoteBuilder addAttachments:[attachmentBuilder build]]; + + // FIXME handle thumbnail uploading. The proto changes for this are up in the air. + // OWSAssert([attachment isKindOfClass:[TSAttachmentStream class]]); + // [quoteBuilder addAttachments:[self buildProtoForAttachmentStream:attachment + // filename:attachment.sourceFilename]];] + } } if (hasQuotedText || hasQuotedAttachment) { diff --git a/SignalServiceKit/src/Messages/Interactions/TSQuotedMessage.h b/SignalServiceKit/src/Messages/Interactions/TSQuotedMessage.h index 9f912ff39..0b3a83b01 100644 --- a/SignalServiceKit/src/Messages/Interactions/TSQuotedMessage.h +++ b/SignalServiceKit/src/Messages/Interactions/TSQuotedMessage.h @@ -26,9 +26,9 @@ NS_ASSUME_NONNULL_BEGIN // This is a MIME type. // // This property should be set IFF we are quoting an attachment message. -- (nullable NSString *)contentType; -- (nullable NSString *)sourceFilename; -- (nullable UIImage *)thumbnailImage; +@property (nonatomic, readonly, nullable) NSString *contentType; +@property (nonatomic, readonly, nullable) NSString *sourceFilename; +@property (nonatomic, readonly, nullable) UIImage *thumbnailImage; - (instancetype)initWithTimestamp:(uint64_t)timestamp authorId:(NSString *)authorId @@ -46,6 +46,11 @@ NS_ASSUME_NONNULL_BEGIN @property (nonatomic, readonly, nullable) NSString *attachmentId; - (instancetype)init NS_UNAVAILABLE; + +- (instancetype)initWithAttachmentId:(nullable NSString *)attachmentId + contentType:(NSString *)contentType + sourceFilename:(NSString *)sourceFilename NS_DESIGNATED_INITIALIZER; + - (instancetype)initWithAttachment:(TSAttachment *)attachment; @end @@ -67,8 +72,8 @@ NS_ASSUME_NONNULL_BEGIN - (nullable NSString *)contentType; - (nullable NSString *)sourceFilename; -@property (atomic, readonly) NSArray *attachmentInfos; -- (void)addAttachment:(TSAttachmentStream *)attachment; +@property (atomic, readonly) NSArray *quotedAttachments; +//- (void)addAttachment:(TSAttachmentStream *)attachment; - (BOOL)hasAttachments; - (nullable TSAttachment *)firstAttachmentWithTransaction:(YapDatabaseReadTransaction *)transaction; @@ -76,10 +81,18 @@ NS_ASSUME_NONNULL_BEGIN - (instancetype)init NS_UNAVAILABLE; +// used in message manager - (instancetype)initWithTimestamp:(uint64_t)timestamp authorId:(NSString *)authorId body:(NSString *_Nullable)body - attachments:(NSArray *)attachments; + quotedAttachmentInfos:(NSArray *)attachmentInfos; + +// used by OWSAttachmentInfo#buildQuotedMessage +- (instancetype)initWithTimestamp:(uint64_t)timestamp + authorId:(NSString *)authorId + body:(NSString *_Nullable)body + quotedAttachmentsForSending:(NSArray *)attachments; + @end diff --git a/SignalServiceKit/src/Messages/Interactions/TSQuotedMessage.m b/SignalServiceKit/src/Messages/Interactions/TSQuotedMessage.m index d45c49699..af8a0022f 100644 --- a/SignalServiceKit/src/Messages/Interactions/TSQuotedMessage.m +++ b/SignalServiceKit/src/Messages/Interactions/TSQuotedMessage.m @@ -5,33 +5,41 @@ #import "TSQuotedMessage.h" #import "TSAttachment.h" #import "TSAttachmentStream.h" +#import NS_ASSUME_NONNULL_BEGIN @implementation OWSAttachmentInfo - (instancetype)initWithAttachment:(TSAttachment *)attachment +{ + OWSAssert(attachment.uniqueId); + OWSAssert(attachment.contentType); + + return [self initWithAttachmentId:attachment.uniqueId + contentType:attachment.contentType + sourceFilename:attachment.sourceFilename]; +} + +- (instancetype)initWithAttachmentId:(nullable NSString *)attachmentId + contentType:(NSString *)contentType + sourceFilename:(NSString *)sourceFilename { self = [super init]; if (!self) { return self; } - - OWSAssert(attachment.uniqueId); - OWSAssert(attachment.contentType); - - _attachmentId = attachment.uniqueId; - _contentType = attachment.contentType; - - // maybe nil - _sourceFilename = attachment.sourceFilename; - + + _attachmentId = attachmentId; + _contentType = contentType; + _sourceFilename = sourceFilename; + return self; } @end - +// View Model which has already fetched any thumbnail attachment. @implementation OWSQuotedReplyDraft // This is a MIME type. @@ -47,7 +55,7 @@ NS_ASSUME_NONNULL_BEGIN return self.attachmentStream.sourceFilename; } -- (nullable NSString *)thumbnailImage +- (nullable UIImage *)thumbnailImage { return self.attachmentStream.thumbnailImage; } @@ -70,36 +78,52 @@ NS_ASSUME_NONNULL_BEGIN return self; } +- (TSQuotedMessage *)buildQuotedMessage +{ + NSArray *attachments = self.attachmentStream ? @[ self.attachmentStream ] : @[]; + + return [[TSQuotedMessage alloc] initWithTimestamp:self.timestamp + authorId:self.authorId + body:self.body + quotedAttachmentsForSending:attachments]; +} + @end @interface TSQuotedMessage () -@property (atomic) NSArray *thumbnailAttachments; +@property (atomic) NSArray *quotedAttachments; +@property (atomic) NSArray *quotedAttachmentsForSending; @end @implementation TSQuotedMessage -- (instancetype)initOutgoingWithTimestamp:(uint64_t)timestamp - authorId:(NSString *)authorId - body:(NSString *_Nullable)body - attachment:(TSAttachmentStream *_Nullable)attachmentStream +- (instancetype)initWithTimestamp:(uint64_t)timestamp + authorId:(NSString *)authorId + body:(NSString *_Nullable)body + quotedAttachmentInfos:(NSArray *)attachmentInfos { - return [self initWithTimestamp:timestamp authorId:authorId body:body attachments:@[ attachmentStream ]]; -} + OWSAssert(timestamp > 0); + OWSAssert(authorId.length > 0); -- (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]; + self = [super init]; + if (!self) { + return nil; + } + + _timestamp = timestamp; + _authorId = authorId; + _body = body; + _quotedAttachments = attachmentInfos; + + return self; } - (instancetype)initWithTimestamp:(uint64_t)timestamp authorId:(NSString *)authorId body:(NSString *_Nullable)body - attachments:(NSArray *)attachments + quotedAttachmentsForSending:(NSArray *)attachments { OWSAssert(timestamp > 0); OWSAssert(authorId.length > 0); @@ -117,104 +141,111 @@ NS_ASSUME_NONNULL_BEGIN for (TSAttachment *attachment in attachments) { [attachmentInfos addObject:[[OWSAttachmentInfo alloc] initWithAttachment:attachment]]; } - _thumbnailAttachments = [attachmentInfos copy]; + _quotedAttachments = [attachmentInfos copy]; return self; } -- (nullable TSAttachment *)firstAttachmentWithTransaction:(YapDatabaseReadTransaction *)transaction -{ - OWSAttachmentInfo *attachmentInfo = self.firstAttachmentInfo; - if (!attachmentInfo) { - return nil; - } - - return [TSAttachment fetchObjectWithUniqueID:attachmentInfo.attachmentId]; -} +#pragma mark - Attachment (not necessarily with a thumbnail) - (nullable OWSAttachmentInfo *)firstAttachmentInfo { - return self.attachmentInfos.firstObject; -} - -- (nullable UIImage *)thumbnailImageWithTransaction:(YapDatabaseReadTransaction *)transaction -{ - TSAttachmentStream *firstAttachment = (TSAttachmentStream *)self.firstThumbnailAttachment; - if (![firstAttachment isKindOfClass:[TSAttachmentStream class]]) { - return nil; - } - - return firstAttachment.thumbnailImage; + return self.quotedAttachments.firstObject; } - (nullable NSString *)contentType { - OWSAttachmentInfo *firstAttachment = self.firstThumbnailAttachment; + OWSAttachmentInfo *firstAttachment = self.firstAttachmentInfo; return firstAttachment.contentType; } - (nullable NSString *)sourceFilename { - OWSAttachmentInfo *firstAttachment = self.firstThumbnailAttachment; + OWSAttachmentInfo *firstAttachment = self.firstAttachmentInfo; return firstAttachment.sourceFilename; } -- (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; -} +#pragma mark - Thumbnail +//- (nullable TSAttachment *)firstAttachmentWithTransaction:(YapDatabaseReadTransaction *)transaction +//{ +// OWSAttachmentInfo *attachmentInfo = self.firstAttachmentInfo; +// if (!attachmentInfo) { +// return nil; +// } +// +// return [TSAttachment fetchObjectWithUniqueID:attachmentInfo.attachmentId]; +//} +//- (nullable UIImage *)thumbnailImageWithTransaction:(YapDatabaseReadTransaction *)transaction +//{ +// TSAttachmentStream *firstAttachment = (TSAttachmentStream *)self.firstAttachmentIn; +// if (![firstAttachment isKindOfClass:[TSAttachmentStream class]]) { +// return nil; +// } +// +// return firstAttachment.thumbnailImage; +//} +//- (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 //{ // //} - +// - (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]; -// }]; -// } -// } + for (OWSAttachmentInfo *info in self.quotedAttachments) { + // TODO should we just cach an optional TSAttachment on the info? + OWSAssert(info.attachmentId); + TSAttachment *attachment = [TSAttachment fetchObjectWithUniqueID:info.attachmentId transaction:transaction]; + if (![attachment isKindOfClass:[TSAttachmentStream class]]) { + return; + } + + TSAttachmentStream *attachmentStream = (TSAttachmentStream *)attachmentStream; + NSData *thumbnailData = attachmentStream.thumbnailData; + // Only some media types have thumbnails + if (thumbnailData) { + // Copy the thumbnail to a new attachment. + NSString *thumbnailName = + [NSString stringWithFormat:@"quoted-thumbnail-%@", attachmentStream.sourceFilename]; + TSAttachmentStream *thumbnailAttachment = + [[TSAttachmentStream alloc] initWithContentType:OWSMimeTypeJpeg + byteCount:attachmentStream.byteCount + sourceFilename:thumbnailName]; + + NSError *error; + [thumbnailAttachment writeData:thumbnailData 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 diff --git a/SignalServiceKit/src/Messages/OWSMessageManager.m b/SignalServiceKit/src/Messages/OWSMessageManager.m index 50e084520..baa595538 100644 --- a/SignalServiceKit/src/Messages/OWSMessageManager.m +++ b/SignalServiceKit/src/Messages/OWSMessageManager.m @@ -1095,41 +1095,42 @@ NS_ASSUME_NONNULL_BEGIN hasText = YES; } - NSArray *attachments; - - 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 - but only if the attachment can't be found locally. - // [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); - // }]; - + NSMutableArray *attachmentInfos = [NSMutableArray new]; + for (OWSSignalServiceProtosAttachmentPointer *attachmentPointer in quoteProto.attachments) { hasAttachment = YES; + OWSAttachmentInfo *attachmentInfo = + [[OWSAttachmentInfo alloc] initWithAttachmentId:nil + contentType:attachmentPointer.contentType + sourceFilename:attachmentPointer.fileName]; + [attachmentInfos addObject:attachmentInfo]; } + // TODO - but only if the attachment can't be found locally. + // 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; + // } + // + // [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); + // }]; if (!hasText && !hasAttachment) { OWSFail(@"%@ quoted message has neither text nor attachment", self.logTag); @@ -1142,8 +1143,11 @@ NS_ASSUME_NONNULL_BEGIN // sourceFilename:sourceFilename // thumbnailData:thumbnailData // contentType:contentType]; - TSQuotedMessage *quotedMessage = - [[TSQuotedMessage alloc] initWithTimestamp:timestamp authorId:authorId body:body attachments:attachments]; + + TSQuotedMessage *quotedMessage = [[TSQuotedMessage alloc] initWithTimestamp:timestamp + authorId:authorId + body:body + quotedAttachmentInfos:attachmentInfos]; return quotedMessage; }