2018-02-07 18:44:09 +01:00
|
|
|
//
|
|
|
|
// Copyright (c) 2018 Open Whisper Systems. All rights reserved.
|
|
|
|
//
|
|
|
|
|
|
|
|
#import "TSQuotedMessage.h"
|
2018-04-07 21:26:22 +02:00
|
|
|
#import "OWSSignalServiceProtos.pb.h"
|
|
|
|
#import "TSAccountManager.h"
|
2018-04-04 22:26:41 +02:00
|
|
|
#import "TSAttachment.h"
|
2018-04-07 21:26:22 +02:00
|
|
|
#import "TSAttachmentPointer.h"
|
2018-04-05 18:01:53 +02:00
|
|
|
#import "TSAttachmentStream.h"
|
2018-04-07 21:26:22 +02:00
|
|
|
#import "TSIncomingMessage.h"
|
|
|
|
#import "TSInteraction.h"
|
|
|
|
#import "TSOutgoingMessage.h"
|
|
|
|
#import "TSThread.h"
|
2018-04-06 18:31:20 +02:00
|
|
|
#import <YapDatabase/YapDatabaseTransaction.h>
|
2018-02-07 18:44:09 +01:00
|
|
|
|
|
|
|
NS_ASSUME_NONNULL_BEGIN
|
|
|
|
|
2018-04-05 18:01:53 +02:00
|
|
|
@implementation OWSAttachmentInfo
|
2018-02-07 18:44:09 +01:00
|
|
|
|
2018-05-03 17:31:06 +02:00
|
|
|
- (instancetype)initWithAttachmentStream:(TSAttachmentStream *)attachmentStream;
|
2018-04-06 18:31:20 +02:00
|
|
|
{
|
2018-05-03 17:31:06 +02:00
|
|
|
OWSAssert([attachmentStream isKindOfClass:[TSAttachmentStream class]]);
|
|
|
|
OWSAssert(attachmentStream.uniqueId);
|
|
|
|
OWSAssert(attachmentStream.contentType);
|
2018-04-06 18:31:20 +02:00
|
|
|
|
2018-05-03 17:31:06 +02:00
|
|
|
return [self initWithAttachmentId:attachmentStream.uniqueId
|
|
|
|
contentType:attachmentStream.contentType
|
|
|
|
sourceFilename:attachmentStream.sourceFilename];
|
2018-04-06 18:31:20 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
- (instancetype)initWithAttachmentId:(nullable NSString *)attachmentId
|
|
|
|
contentType:(NSString *)contentType
|
|
|
|
sourceFilename:(NSString *)sourceFilename
|
2018-02-07 18:44:09 +01:00
|
|
|
{
|
2018-04-05 18:01:53 +02:00
|
|
|
self = [super init];
|
2018-02-07 18:44:09 +01:00
|
|
|
if (!self) {
|
|
|
|
return self;
|
|
|
|
}
|
2018-04-06 18:31:20 +02:00
|
|
|
|
|
|
|
_attachmentId = attachmentId;
|
|
|
|
_contentType = contentType;
|
|
|
|
_sourceFilename = sourceFilename;
|
|
|
|
|
2018-04-05 18:01:53 +02:00
|
|
|
return self;
|
|
|
|
}
|
|
|
|
|
|
|
|
@end
|
|
|
|
|
|
|
|
@interface TSQuotedMessage ()
|
|
|
|
|
2018-04-06 18:31:20 +02:00
|
|
|
@property (atomic) NSArray<OWSAttachmentInfo *> *quotedAttachments;
|
|
|
|
@property (atomic) NSArray<TSAttachmentStream *> *quotedAttachmentsForSending;
|
2018-04-05 18:01:53 +02:00
|
|
|
|
|
|
|
@end
|
2018-02-07 18:44:09 +01:00
|
|
|
|
2018-04-05 18:01:53 +02:00
|
|
|
@implementation TSQuotedMessage
|
|
|
|
|
2018-04-06 18:31:20 +02:00
|
|
|
- (instancetype)initWithTimestamp:(uint64_t)timestamp
|
|
|
|
authorId:(NSString *)authorId
|
|
|
|
body:(NSString *_Nullable)body
|
2018-04-09 15:51:51 +02:00
|
|
|
receivedQuotedAttachmentInfos:(NSArray<OWSAttachmentInfo *> *)attachmentInfos
|
2018-04-05 18:01:53 +02:00
|
|
|
{
|
2018-04-06 18:31:20 +02:00
|
|
|
OWSAssert(timestamp > 0);
|
|
|
|
OWSAssert(authorId.length > 0);
|
2018-04-05 18:01:53 +02:00
|
|
|
|
2018-04-06 18:31:20 +02:00
|
|
|
self = [super init];
|
|
|
|
if (!self) {
|
|
|
|
return nil;
|
|
|
|
}
|
|
|
|
|
|
|
|
_timestamp = timestamp;
|
|
|
|
_authorId = authorId;
|
|
|
|
_body = body;
|
|
|
|
_quotedAttachments = attachmentInfos;
|
|
|
|
|
|
|
|
return self;
|
2018-04-05 18:01:53 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
- (instancetype)initWithTimestamp:(uint64_t)timestamp
|
|
|
|
authorId:(NSString *)authorId
|
|
|
|
body:(NSString *_Nullable)body
|
2018-04-06 18:31:20 +02:00
|
|
|
quotedAttachmentsForSending:(NSArray<TSAttachmentStream *> *)attachments
|
2018-04-05 18:01:53 +02:00
|
|
|
{
|
2018-02-07 18:44:09 +01:00
|
|
|
OWSAssert(timestamp > 0);
|
|
|
|
OWSAssert(authorId.length > 0);
|
|
|
|
|
2018-04-05 18:01:53 +02:00
|
|
|
self = [super init];
|
|
|
|
if (!self) {
|
|
|
|
return nil;
|
|
|
|
}
|
|
|
|
|
2018-02-07 18:44:09 +01:00
|
|
|
_timestamp = timestamp;
|
|
|
|
_authorId = authorId;
|
|
|
|
_body = body;
|
2018-04-05 18:01:53 +02:00
|
|
|
|
|
|
|
NSMutableArray *attachmentInfos = [NSMutableArray new];
|
2018-05-03 17:31:06 +02:00
|
|
|
for (TSAttachmentStream *attachmentStream in attachments) {
|
|
|
|
[attachmentInfos addObject:[[OWSAttachmentInfo alloc] initWithAttachmentStream:attachmentStream]];
|
2018-04-05 18:01:53 +02:00
|
|
|
}
|
2018-04-06 18:31:20 +02:00
|
|
|
_quotedAttachments = [attachmentInfos copy];
|
2018-02-07 18:44:09 +01:00
|
|
|
|
|
|
|
return self;
|
|
|
|
}
|
|
|
|
|
2018-04-07 21:26:22 +02:00
|
|
|
+ (TSQuotedMessage *_Nullable)quotedMessageForDataMessage:(OWSSignalServiceProtosDataMessage *)dataMessage
|
|
|
|
thread:(TSThread *)thread
|
|
|
|
relay:(nullable NSString *)relay
|
|
|
|
transaction:(YapDatabaseReadWriteTransaction *)transaction
|
|
|
|
{
|
|
|
|
OWSAssert(dataMessage);
|
|
|
|
|
|
|
|
if (!dataMessage.hasQuote) {
|
|
|
|
return nil;
|
|
|
|
}
|
|
|
|
|
|
|
|
OWSSignalServiceProtosDataMessageQuote *quoteProto = [dataMessage quote];
|
|
|
|
|
|
|
|
if (![quoteProto hasId] || [quoteProto id] == 0) {
|
|
|
|
OWSFail(@"%@ quoted message missing id", self.logTag);
|
|
|
|
return nil;
|
|
|
|
}
|
|
|
|
uint64_t timestamp = [quoteProto id];
|
|
|
|
|
|
|
|
if (![quoteProto hasAuthor] || [quoteProto author].length == 0) {
|
|
|
|
OWSFail(@"%@ quoted message missing author", self.logTag);
|
|
|
|
return nil;
|
|
|
|
}
|
|
|
|
// TODO: We could verify that this is a valid e164 value.
|
|
|
|
NSString *authorId = [quoteProto author];
|
|
|
|
|
|
|
|
NSString *_Nullable body = nil;
|
|
|
|
BOOL hasText = NO;
|
|
|
|
BOOL hasAttachment = NO;
|
|
|
|
if ([quoteProto hasText] && [quoteProto text].length > 0) {
|
|
|
|
body = [quoteProto text];
|
|
|
|
hasText = YES;
|
|
|
|
}
|
|
|
|
|
|
|
|
NSMutableArray<OWSAttachmentInfo *> *attachmentInfos = [NSMutableArray new];
|
|
|
|
for (OWSSignalServiceProtosDataMessageQuoteQuotedAttachment *quotedAttachment in quoteProto.attachments) {
|
|
|
|
hasAttachment = YES;
|
|
|
|
OWSAttachmentInfo *attachmentInfo = [[OWSAttachmentInfo alloc] initWithAttachmentId:nil
|
|
|
|
contentType:quotedAttachment.contentType
|
|
|
|
sourceFilename:quotedAttachment.fileName];
|
|
|
|
|
|
|
|
// We prefer deriving any thumbnail locally rather than fetching one from the network.
|
|
|
|
TSAttachmentStream *_Nullable thumbnailStream =
|
|
|
|
[self tryToDeriveLocalThumbnailWithAttachmentInfo:attachmentInfo
|
|
|
|
timestamp:timestamp
|
|
|
|
threadId:thread.uniqueId
|
|
|
|
authorId:authorId
|
|
|
|
transaction:transaction];
|
|
|
|
|
|
|
|
if (thumbnailStream) {
|
2018-05-07 18:32:31 +02:00
|
|
|
DDLogDebug(@"%@ Generated local thumbnail for quoted quoted message: %@:%lu",
|
2018-04-07 21:26:22 +02:00
|
|
|
self.logTag,
|
|
|
|
thread.uniqueId,
|
2018-05-07 18:32:31 +02:00
|
|
|
(unsigned long)timestamp);
|
2018-04-07 21:26:22 +02:00
|
|
|
|
|
|
|
[thumbnailStream saveWithTransaction:transaction];
|
|
|
|
|
|
|
|
attachmentInfo.thumbnailAttachmentStreamId = thumbnailStream.uniqueId;
|
|
|
|
} else if (quotedAttachment.hasThumbnail) {
|
2018-05-07 18:32:31 +02:00
|
|
|
DDLogDebug(@"%@ Saving reference for fetching remote thumbnail for quoted message: %@:%lu",
|
2018-04-07 21:26:22 +02:00
|
|
|
self.logTag,
|
|
|
|
thread.uniqueId,
|
2018-05-07 18:32:31 +02:00
|
|
|
(unsigned long)timestamp);
|
2018-04-07 21:26:22 +02:00
|
|
|
|
|
|
|
OWSSignalServiceProtosAttachmentPointer *thumbnailAttachmentProto = quotedAttachment.thumbnail;
|
|
|
|
TSAttachmentPointer *thumbnailPointer =
|
|
|
|
[TSAttachmentPointer attachmentPointerFromProto:thumbnailAttachmentProto relay:relay];
|
|
|
|
[thumbnailPointer saveWithTransaction:transaction];
|
|
|
|
|
|
|
|
attachmentInfo.thumbnailAttachmentPointerId = thumbnailPointer.uniqueId;
|
|
|
|
} else {
|
2018-05-07 18:32:31 +02:00
|
|
|
DDLogDebug(
|
|
|
|
@"%@ No thumbnail for quoted message: %@:%lu", self.logTag, thread.uniqueId, (unsigned long)timestamp);
|
2018-04-07 21:26:22 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
[attachmentInfos addObject:attachmentInfo];
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!hasText && !hasAttachment) {
|
|
|
|
OWSFail(@"%@ quoted message has neither text nor attachment", self.logTag);
|
|
|
|
return nil;
|
|
|
|
}
|
|
|
|
|
|
|
|
return [[TSQuotedMessage alloc] initWithTimestamp:timestamp
|
|
|
|
authorId:authorId
|
|
|
|
body:body
|
2018-04-09 15:51:51 +02:00
|
|
|
receivedQuotedAttachmentInfos:attachmentInfos];
|
2018-04-07 21:26:22 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
+ (nullable TSAttachmentStream *)tryToDeriveLocalThumbnailWithAttachmentInfo:(OWSAttachmentInfo *)attachmentInfo
|
|
|
|
timestamp:(uint64_t)timestamp
|
|
|
|
threadId:(NSString *)threadId
|
|
|
|
authorId:(NSString *)authorId
|
|
|
|
transaction:
|
|
|
|
(YapDatabaseReadWriteTransaction *)transaction
|
|
|
|
{
|
|
|
|
if (![TSAttachmentStream hasThumbnailForMimeType:attachmentInfo.contentType]) {
|
|
|
|
return nil;
|
|
|
|
}
|
|
|
|
|
|
|
|
NSArray<TSMessage *> *quotedMessages = (NSArray<TSMessage *> *)[TSInteraction
|
|
|
|
interactionsWithTimestamp:timestamp
|
|
|
|
filter:^BOOL(TSInteraction *interaction) {
|
|
|
|
|
|
|
|
if (![threadId isEqual:interaction.uniqueThreadId]) {
|
|
|
|
return NO;
|
|
|
|
}
|
|
|
|
|
|
|
|
if ([interaction isKindOfClass:[TSIncomingMessage class]]) {
|
|
|
|
TSIncomingMessage *incomingMessage = (TSIncomingMessage *)interaction;
|
|
|
|
return [authorId isEqual:incomingMessage.messageAuthorId];
|
|
|
|
} else if ([interaction isKindOfClass:[TSOutgoingMessage class]]) {
|
|
|
|
return [authorId isEqual:[TSAccountManager localNumber]];
|
|
|
|
} else {
|
|
|
|
// ignore other interaction types
|
|
|
|
return NO;
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
withTransaction:transaction];
|
|
|
|
|
|
|
|
TSMessage *_Nullable quotedMessage = quotedMessages.firstObject;
|
|
|
|
|
|
|
|
if (!quotedMessage) {
|
|
|
|
return nil;
|
|
|
|
}
|
|
|
|
|
|
|
|
TSAttachment *attachment = [quotedMessage attachmentWithTransaction:transaction];
|
|
|
|
if (![attachment isKindOfClass:[TSAttachmentStream class]]) {
|
|
|
|
return nil;
|
|
|
|
}
|
|
|
|
TSAttachmentStream *sourceStream = (TSAttachmentStream *)attachment;
|
|
|
|
|
|
|
|
TSAttachmentStream *_Nullable thumbnailStream = [sourceStream cloneAsThumbnail];
|
|
|
|
if (!thumbnailStream) {
|
|
|
|
return nil;
|
|
|
|
}
|
|
|
|
|
|
|
|
return thumbnailStream;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2018-04-06 18:31:20 +02:00
|
|
|
#pragma mark - Attachment (not necessarily with a thumbnail)
|
2018-04-04 22:26:41 +02:00
|
|
|
|
2018-04-05 18:01:53 +02:00
|
|
|
- (nullable OWSAttachmentInfo *)firstAttachmentInfo
|
2018-04-04 22:26:41 +02:00
|
|
|
{
|
2018-04-07 19:59:49 +02:00
|
|
|
OWSAssert(self.quotedAttachments.count <= 1);
|
2018-04-06 18:31:20 +02:00
|
|
|
return self.quotedAttachments.firstObject;
|
2018-04-05 18:01:53 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
- (nullable NSString *)contentType
|
|
|
|
{
|
2018-04-06 18:31:20 +02:00
|
|
|
OWSAttachmentInfo *firstAttachment = self.firstAttachmentInfo;
|
2018-04-05 18:01:53 +02:00
|
|
|
|
|
|
|
return firstAttachment.contentType;
|
|
|
|
}
|
|
|
|
|
2018-04-06 17:15:21 +02:00
|
|
|
- (nullable NSString *)sourceFilename
|
|
|
|
{
|
2018-04-06 18:31:20 +02:00
|
|
|
OWSAttachmentInfo *firstAttachment = self.firstAttachmentInfo;
|
2018-04-06 17:15:21 +02:00
|
|
|
|
|
|
|
return firstAttachment.sourceFilename;
|
|
|
|
}
|
|
|
|
|
2018-04-07 20:46:45 +02:00
|
|
|
- (nullable NSString *)thumbnailAttachmentPointerId
|
2018-04-07 19:59:49 +02:00
|
|
|
{
|
|
|
|
OWSAttachmentInfo *firstAttachment = self.firstAttachmentInfo;
|
2018-04-06 22:53:16 +02:00
|
|
|
|
2018-04-07 20:46:45 +02:00
|
|
|
return firstAttachment.thumbnailAttachmentPointerId;
|
2018-04-07 19:59:49 +02:00
|
|
|
}
|
|
|
|
|
2018-04-07 20:46:45 +02:00
|
|
|
- (nullable NSString *)thumbnailAttachmentStreamId
|
2018-04-07 19:59:49 +02:00
|
|
|
{
|
|
|
|
OWSAttachmentInfo *firstAttachment = self.firstAttachmentInfo;
|
|
|
|
|
2018-04-07 20:46:45 +02:00
|
|
|
return firstAttachment.thumbnailAttachmentStreamId;
|
2018-04-07 19:59:49 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
- (void)setThumbnailAttachmentStream:(TSAttachmentStream *)attachmentStream
|
|
|
|
{
|
|
|
|
OWSAssert([attachmentStream isKindOfClass:[TSAttachmentStream class]]);
|
|
|
|
OWSAssert(self.quotedAttachments.count == 1);
|
|
|
|
|
|
|
|
OWSAttachmentInfo *firstAttachment = self.firstAttachmentInfo;
|
|
|
|
firstAttachment.thumbnailAttachmentStreamId = attachmentStream.uniqueId;
|
|
|
|
}
|
2018-04-05 18:01:53 +02:00
|
|
|
|
2018-04-07 20:46:45 +02:00
|
|
|
- (NSArray<NSString *> *)thumbnailAttachmentStreamIds
|
|
|
|
{
|
|
|
|
NSMutableArray *streamIds = [NSMutableArray new];
|
|
|
|
for (OWSAttachmentInfo *info in self.quotedAttachments) {
|
|
|
|
if (info.thumbnailAttachmentStreamId) {
|
|
|
|
[streamIds addObject:info.thumbnailAttachmentStreamId];
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return [streamIds copy];
|
|
|
|
}
|
|
|
|
|
2018-04-09 18:02:35 +02:00
|
|
|
// Before sending, persist a thumbnail attachment derived from the quoted attachment
|
2018-04-06 22:53:16 +02:00
|
|
|
- (NSArray<TSAttachmentStream *> *)createThumbnailAttachmentsIfNecessaryWithTransaction:
|
|
|
|
(YapDatabaseReadWriteTransaction *)transaction
|
2018-04-05 18:01:53 +02:00
|
|
|
{
|
2018-04-06 22:53:16 +02:00
|
|
|
NSMutableArray<TSAttachmentStream *> *thumbnailAttachments = [NSMutableArray new];
|
|
|
|
|
|
|
|
for (OWSAttachmentInfo *info in self.quotedAttachments) {
|
|
|
|
|
|
|
|
OWSAssert(info.attachmentId);
|
|
|
|
TSAttachment *attachment = [TSAttachment fetchObjectWithUniqueID:info.attachmentId transaction:transaction];
|
|
|
|
if (![attachment isKindOfClass:[TSAttachmentStream class]]) {
|
|
|
|
continue;
|
|
|
|
}
|
2018-04-07 02:08:17 +02:00
|
|
|
TSAttachmentStream *sourceStream = (TSAttachmentStream *)attachment;
|
2018-04-06 22:53:16 +02:00
|
|
|
|
2018-04-07 02:08:17 +02:00
|
|
|
TSAttachmentStream *_Nullable thumbnailStream = [sourceStream cloneAsThumbnail];
|
|
|
|
if (!thumbnailStream) {
|
|
|
|
continue;
|
2018-04-06 22:53:16 +02:00
|
|
|
}
|
|
|
|
|
2018-04-07 02:08:17 +02:00
|
|
|
[thumbnailStream saveWithTransaction:transaction];
|
2018-04-07 19:59:49 +02:00
|
|
|
info.thumbnailAttachmentStreamId = thumbnailStream.uniqueId;
|
2018-04-07 02:08:17 +02:00
|
|
|
[thumbnailAttachments addObject:thumbnailStream];
|
2018-04-06 22:53:16 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
return [thumbnailAttachments copy];
|
2018-04-05 18:01:53 +02:00
|
|
|
}
|
2018-04-07 02:08:17 +02:00
|
|
|
|
2018-02-07 18:44:09 +01:00
|
|
|
@end
|
|
|
|
|
|
|
|
NS_ASSUME_NONNULL_END
|