Merge branch 'mlin/PR/FixQuotedReplies'
This commit is contained in:
commit
b84bba3dc0
@ -952,10 +952,9 @@ fileprivate extension CVComponentState.Builder {
|
||||
|
||||
// TODO: Should we validate and throw errors?
|
||||
mutating func buildQuotedReply(message: TSMessage) {
|
||||
guard let quotedMessage = message.quotedMessage else {
|
||||
guard let quotedReplyModel = OWSQuotedReplyModel.quotedReply(from: message, transaction: transaction) else {
|
||||
return
|
||||
}
|
||||
let quotedReplyModel = OWSQuotedReplyModel.quotedReply(with: quotedMessage, transaction: transaction)
|
||||
var displayableQuotedText: DisplayableText?
|
||||
if let quotedBody = quotedReplyModel.body,
|
||||
!quotedBody.isEmpty {
|
||||
|
||||
@ -250,7 +250,7 @@ const CGFloat kRemotelySourcedContentRowSpacing = 3;
|
||||
[quotedAttachmentView addSubview:contentImageView];
|
||||
[contentImageView autoCenterInSuperview];
|
||||
}
|
||||
} else if (self.quotedMessage.thumbnailDownloadFailed) {
|
||||
} else if (self.quotedMessage.failedThumbnailAttachmentPointer) {
|
||||
// TODO design review icon and color
|
||||
UIImage *contentIcon =
|
||||
[[UIImage imageNamed:@"btnRefresh--white"] imageWithRenderingMode:UIImageRenderingModeAlwaysTemplate];
|
||||
@ -270,6 +270,7 @@ const CGFloat kRemotelySourcedContentRowSpacing = 3;
|
||||
[quotedAttachmentView addGestureRecognizer:tapGesture];
|
||||
quotedAttachmentView.userInteractionEnabled = YES;
|
||||
} else {
|
||||
// TODO: Should we overlay the file extension like we do with CVComponentGenericAttachment
|
||||
UIImage *contentIcon = [UIImage imageNamed:@"generic-attachment"];
|
||||
UIImageView *contentImageView = [self imageViewForImage:contentIcon];
|
||||
contentImageView.contentMode = UIViewContentModeScaleAspectFit;
|
||||
@ -384,18 +385,13 @@ const CGFloat kRemotelySourcedContentRowSpacing = 3;
|
||||
{
|
||||
OWSLogDebug(@"in didTapFailedThumbnailDownload");
|
||||
|
||||
if (!self.quotedMessage.thumbnailDownloadFailed) {
|
||||
OWSFailDebug(@"thumbnailDownloadFailed was unexpectedly false");
|
||||
return;
|
||||
}
|
||||
|
||||
if (!self.quotedMessage.thumbnailAttachmentPointer) {
|
||||
if (!self.quotedMessage.failedThumbnailAttachmentPointer) {
|
||||
OWSFailDebug(@"thumbnailAttachmentPointer was unexpectedly nil");
|
||||
return;
|
||||
}
|
||||
|
||||
[self.delegate didTapQuotedReply:self.quotedMessage
|
||||
failedThumbnailDownloadAttachmentPointer:self.quotedMessage.thumbnailAttachmentPointer];
|
||||
failedThumbnailDownloadAttachmentPointer:self.quotedMessage.failedThumbnailAttachmentPointer];
|
||||
}
|
||||
|
||||
- (nullable UIImage *)tryToLoadThumbnailImage
|
||||
|
||||
@ -435,7 +435,7 @@ public class QuotedMessageView: ManualStackViewWithLayer {
|
||||
wrapper.addSubviewToCenterOnSuperviewWithDesiredSize(contentImageView)
|
||||
}
|
||||
return wrapper
|
||||
} else if quotedReplyModel.thumbnailDownloadFailed {
|
||||
} else if quotedReplyModel.failedThumbnailAttachmentPointer != nil {
|
||||
let wrapper = ManualLayoutViewWithLayer(name: "thumbnailDownloadFailedWrapper")
|
||||
wrapper.backgroundColor = configurator.highlightColor
|
||||
|
||||
@ -452,6 +452,7 @@ public class QuotedMessageView: ManualStackViewWithLayer {
|
||||
|
||||
return wrapper
|
||||
} else {
|
||||
// TODO: Should we overlay the file extension like we do with CVComponentGenericAttachment
|
||||
quotedImageView.setTemplateImageName("generic-attachment", tintColor: .clear)
|
||||
quotedImageView.contentMode = .scaleAspectFit
|
||||
quotedImageView.clipsToBounds = false
|
||||
@ -647,12 +648,7 @@ public class QuotedMessageView: ManualStackViewWithLayer {
|
||||
}
|
||||
let quotedReplyModel = state.quotedReplyModel
|
||||
|
||||
if !quotedReplyModel.thumbnailDownloadFailed {
|
||||
owsFailDebug("thumbnailDownloadFailed was unexpectedly false")
|
||||
return
|
||||
}
|
||||
|
||||
guard let thumbnailAttachmentPointer = quotedReplyModel.thumbnailAttachmentPointer else {
|
||||
guard let thumbnailAttachmentPointer = quotedReplyModel.failedThumbnailAttachmentPointer else {
|
||||
owsFailDebug("thumbnailAttachmentPointer was unexpectedly nil")
|
||||
return
|
||||
}
|
||||
|
||||
@ -281,6 +281,7 @@ extension ConversationViewController {
|
||||
func scrollToQuotedMessage(_ quotedReply: OWSQuotedReplyModel,
|
||||
isAnimated: Bool) {
|
||||
if quotedReply.isRemotelySourced {
|
||||
presentRemotelySourcedQuotedReplyToast()
|
||||
return
|
||||
}
|
||||
let quotedMessage = databaseStorage.read { transaction in
|
||||
@ -289,10 +290,8 @@ extension ConversationViewController {
|
||||
author: quotedReply.authorAddress,
|
||||
transaction: transaction)
|
||||
}
|
||||
guard let message = quotedMessage else {
|
||||
return
|
||||
}
|
||||
guard !message.wasRemotelyDeleted else {
|
||||
guard let message = quotedMessage, !message.wasRemotelyDeleted else {
|
||||
presentMissingQuotedReplyToast()
|
||||
return
|
||||
}
|
||||
ensureInteractionLoadedThenScrollToInteraction(message.uniqueId,
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
//
|
||||
// Copyright (c) 2020 Open Whisper Systems. All rights reserved.
|
||||
// Copyright (c) 2021 Open Whisper Systems. All rights reserved.
|
||||
//
|
||||
|
||||
#import "CVItemViewModel.h"
|
||||
@ -28,8 +28,7 @@ NS_ASSUME_NONNULL_BEGIN
|
||||
@property (nonatomic, readonly) uint64_t timestamp;
|
||||
@property (nonatomic, readonly) SignalServiceAddress *authorAddress;
|
||||
@property (nonatomic, readonly, nullable) TSAttachmentStream *attachmentStream;
|
||||
@property (nonatomic, readonly, nullable) TSAttachmentPointer *thumbnailAttachmentPointer;
|
||||
@property (nonatomic, readonly) BOOL thumbnailDownloadFailed;
|
||||
@property (nonatomic, readonly, nullable) TSAttachmentPointer *failedThumbnailAttachmentPointer;
|
||||
|
||||
// This property should be set IFF we are quoting a text message
|
||||
// or attachment with caption.
|
||||
@ -50,8 +49,7 @@ NS_ASSUME_NONNULL_BEGIN
|
||||
- (instancetype)init NS_UNAVAILABLE;
|
||||
|
||||
// Used for persisted quoted replies, both incoming and outgoing.
|
||||
+ (instancetype)quotedReplyWithQuotedMessage:(TSQuotedMessage *)quotedMessage
|
||||
transaction:(SDSAnyReadTransaction *)transaction;
|
||||
+ (nullable instancetype)quotedReplyFromMessage:(TSMessage *)message transaction:(SDSAnyReadTransaction *)transaction;
|
||||
|
||||
// Builds a not-yet-sent QuotedReplyModel
|
||||
+ (nullable instancetype)quotedReplyForSendingWithItem:(id<CVItemViewModel>)item
|
||||
|
||||
@ -22,16 +22,16 @@ NS_ASSUME_NONNULL_BEGIN
|
||||
@property (nonatomic, readonly) TSQuotedMessageContentSource bodySource;
|
||||
|
||||
- (instancetype)initWithTimestamp:(uint64_t)timestamp
|
||||
authorAddress:(SignalServiceAddress *)authorAddress
|
||||
body:(nullable NSString *)body
|
||||
bodyRanges:(nullable MessageBodyRanges *)bodyRanges
|
||||
bodySource:(TSQuotedMessageContentSource)bodySource
|
||||
thumbnailImage:(nullable UIImage *)thumbnailImage
|
||||
contentType:(nullable NSString *)contentType
|
||||
sourceFilename:(nullable NSString *)sourceFilename
|
||||
attachmentStream:(nullable TSAttachmentStream *)attachmentStream
|
||||
thumbnailAttachmentPointer:(nullable TSAttachmentPointer *)thumbnailAttachmentPointer
|
||||
thumbnailDownloadFailed:(BOOL)thumbnailDownloadFailed NS_DESIGNATED_INITIALIZER;
|
||||
authorAddress:(SignalServiceAddress *)authorAddress
|
||||
body:(nullable NSString *)body
|
||||
bodyRanges:(nullable MessageBodyRanges *)bodyRanges
|
||||
bodySource:(TSQuotedMessageContentSource)bodySource
|
||||
thumbnailImage:(nullable UIImage *)thumbnailImage
|
||||
contentType:(nullable NSString *)contentType
|
||||
sourceFilename:(nullable NSString *)sourceFilename
|
||||
attachmentStream:(nullable TSAttachmentStream *)attachmentStream
|
||||
failedThumbnailAttachmentPointer:(nullable TSAttachmentPointer *)failedThumbnailAttachmentPointer
|
||||
NS_DESIGNATED_INITIALIZER;
|
||||
|
||||
@end
|
||||
|
||||
@ -41,16 +41,15 @@ NS_ASSUME_NONNULL_BEGIN
|
||||
#pragma mark - Initializers
|
||||
|
||||
- (instancetype)initWithTimestamp:(uint64_t)timestamp
|
||||
authorAddress:(SignalServiceAddress *)authorAddress
|
||||
body:(nullable NSString *)body
|
||||
bodyRanges:(nullable MessageBodyRanges *)bodyRanges
|
||||
bodySource:(TSQuotedMessageContentSource)bodySource
|
||||
thumbnailImage:(nullable UIImage *)thumbnailImage
|
||||
contentType:(nullable NSString *)contentType
|
||||
sourceFilename:(nullable NSString *)sourceFilename
|
||||
attachmentStream:(nullable TSAttachmentStream *)attachmentStream
|
||||
thumbnailAttachmentPointer:(nullable TSAttachmentPointer *)thumbnailAttachmentPointer
|
||||
thumbnailDownloadFailed:(BOOL)thumbnailDownloadFailed
|
||||
authorAddress:(SignalServiceAddress *)authorAddress
|
||||
body:(nullable NSString *)body
|
||||
bodyRanges:(nullable MessageBodyRanges *)bodyRanges
|
||||
bodySource:(TSQuotedMessageContentSource)bodySource
|
||||
thumbnailImage:(nullable UIImage *)thumbnailImage
|
||||
contentType:(nullable NSString *)contentType
|
||||
sourceFilename:(nullable NSString *)sourceFilename
|
||||
attachmentStream:(nullable TSAttachmentStream *)attachmentStream
|
||||
failedThumbnailAttachmentPointer:(nullable TSAttachmentPointer *)failedThumbnailAttachmentPointer
|
||||
{
|
||||
self = [super init];
|
||||
if (!self) {
|
||||
@ -66,42 +65,35 @@ NS_ASSUME_NONNULL_BEGIN
|
||||
_contentType = contentType;
|
||||
_sourceFilename = sourceFilename;
|
||||
_attachmentStream = attachmentStream;
|
||||
_thumbnailAttachmentPointer = thumbnailAttachmentPointer;
|
||||
_thumbnailDownloadFailed = thumbnailDownloadFailed;
|
||||
_failedThumbnailAttachmentPointer = failedThumbnailAttachmentPointer;
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
#pragma mark - Factory Methods
|
||||
|
||||
+ (instancetype)quotedReplyWithQuotedMessage:(TSQuotedMessage *)quotedMessage
|
||||
transaction:(SDSAnyReadTransaction *)transaction
|
||||
+ (nullable instancetype)quotedReplyFromMessage:(TSMessage *)message transaction:(SDSAnyReadTransaction *)transaction
|
||||
{
|
||||
OWSAssertDebug(quotedMessage.quotedAttachments.count <= 1);
|
||||
OWSAttachmentInfo *attachmentInfo = quotedMessage.quotedAttachments.firstObject;
|
||||
TSQuotedMessage *quotedMessage = message.quotedMessage;
|
||||
if (!quotedMessage) {
|
||||
return nil;
|
||||
}
|
||||
|
||||
BOOL thumbnailDownloadFailed = NO;
|
||||
UIImage *_Nullable thumbnailImage;
|
||||
TSAttachmentPointer *attachmentPointer;
|
||||
if (attachmentInfo.thumbnailAttachmentStreamId) {
|
||||
TSAttachment *attachment =
|
||||
[TSAttachment anyFetchWithUniqueId:attachmentInfo.thumbnailAttachmentStreamId transaction:transaction];
|
||||
|
||||
TSAttachmentStream *attachmentStream;
|
||||
if ([attachment isKindOfClass:[TSAttachmentStream class]]) {
|
||||
attachmentStream = (TSAttachmentStream *)attachment;
|
||||
thumbnailImage = attachmentStream.thumbnailImageSmallSync;
|
||||
}
|
||||
} else if (attachmentInfo.thumbnailAttachmentPointerId) {
|
||||
// download failed, or hasn't completed yet.
|
||||
TSAttachment *attachment =
|
||||
[TSAttachment anyFetchWithUniqueId:attachmentInfo.thumbnailAttachmentPointerId transaction:transaction];
|
||||
TSAttachment *_Nullable attachment = [message fetchQuotedMessageThumbnailWithTransaction:transaction];
|
||||
TSAttachmentPointer *_Nullable failedAttachmentPointer = nil;
|
||||
|
||||
if ([attachment isKindOfClass:[TSAttachmentStream class]]) {
|
||||
thumbnailImage = [(TSAttachmentStream *)attachment thumbnailImageSmallSync];
|
||||
} else if (!quotedMessage.isThumbnailOwned) {
|
||||
// If the quoted message isn't owning the thumbnail attachment, it's going to be referencing
|
||||
// some other attachment (e.g. undownloaded media). In this case, let's just use the blur hash
|
||||
thumbnailImage = attachment.blurHash ? [BlurHash imageForBlurHash:attachment.blurHash] : nil;
|
||||
} else {
|
||||
// If the quoted message has ownership of the thumbnail, but it hasn't been downloaded yet,
|
||||
// we shoud surface this in the view.
|
||||
if ([attachment isKindOfClass:[TSAttachmentPointer class]]) {
|
||||
attachmentPointer = (TSAttachmentPointer *)attachment;
|
||||
if (attachmentPointer.state == TSAttachmentPointerStateFailed) {
|
||||
thumbnailDownloadFailed = YES;
|
||||
}
|
||||
failedAttachmentPointer = (TSAttachmentPointer *)attachment;
|
||||
}
|
||||
}
|
||||
|
||||
@ -111,11 +103,10 @@ NS_ASSUME_NONNULL_BEGIN
|
||||
bodyRanges:quotedMessage.bodyRanges
|
||||
bodySource:quotedMessage.bodySource
|
||||
thumbnailImage:thumbnailImage
|
||||
contentType:attachmentInfo.contentType
|
||||
sourceFilename:attachmentInfo.sourceFilename
|
||||
contentType:quotedMessage.contentType
|
||||
sourceFilename:quotedMessage.sourceFilename
|
||||
attachmentStream:nil
|
||||
thumbnailAttachmentPointer:attachmentPointer
|
||||
thumbnailDownloadFailed:thumbnailDownloadFailed];
|
||||
failedThumbnailAttachmentPointer:failedAttachmentPointer];
|
||||
}
|
||||
|
||||
+ (nullable instancetype)quotedReplyForSendingWithItem:(id<CVItemViewModel>)item
|
||||
@ -161,8 +152,7 @@ NS_ASSUME_NONNULL_BEGIN
|
||||
contentType:nil
|
||||
sourceFilename:nil
|
||||
attachmentStream:nil
|
||||
thumbnailAttachmentPointer:nil
|
||||
thumbnailDownloadFailed:NO];
|
||||
failedThumbnailAttachmentPointer:nil];
|
||||
}
|
||||
|
||||
if (item.contactShare) {
|
||||
@ -181,8 +171,7 @@ NS_ASSUME_NONNULL_BEGIN
|
||||
contentType:nil
|
||||
sourceFilename:nil
|
||||
attachmentStream:nil
|
||||
thumbnailAttachmentPointer:nil
|
||||
thumbnailDownloadFailed:NO];
|
||||
failedThumbnailAttachmentPointer:nil];
|
||||
}
|
||||
|
||||
if (item.stickerInfo || item.stickerAttachment || item.stickerMetadata) {
|
||||
@ -270,8 +259,7 @@ NS_ASSUME_NONNULL_BEGIN
|
||||
contentType:contentType
|
||||
sourceFilename:quotedAttachment.sourceFilename
|
||||
attachmentStream:quotedAttachment
|
||||
thumbnailAttachmentPointer:nil
|
||||
thumbnailDownloadFailed:NO];
|
||||
failedThumbnailAttachmentPointer:nil];
|
||||
}
|
||||
|
||||
NSString *_Nullable quotedText = message.body;
|
||||
@ -353,22 +341,19 @@ NS_ASSUME_NONNULL_BEGIN
|
||||
contentType:quotedAttachment.contentType
|
||||
sourceFilename:quotedAttachment.sourceFilename
|
||||
attachmentStream:quotedAttachment
|
||||
thumbnailAttachmentPointer:nil
|
||||
thumbnailDownloadFailed:NO];
|
||||
failedThumbnailAttachmentPointer:nil];
|
||||
}
|
||||
|
||||
#pragma mark - Instance Methods
|
||||
|
||||
- (TSQuotedMessage *)buildQuotedMessageForSending
|
||||
{
|
||||
NSArray *attachments = self.attachmentStream ? @[ self.attachmentStream ] : @[];
|
||||
|
||||
// Legit usage of senderTimestamp to reference existing message
|
||||
return [[TSQuotedMessage alloc] initWithTimestamp:self.timestamp
|
||||
authorAddress:self.authorAddress
|
||||
body:self.body
|
||||
bodyRanges:self.bodyRanges
|
||||
quotedAttachmentsForSending:attachments];
|
||||
quotedAttachmentForSending:self.attachmentStream];
|
||||
}
|
||||
|
||||
- (BOOL)isRemotelySourced
|
||||
|
||||
@ -1040,13 +1040,13 @@ public extension OWSAttachmentDownloads {
|
||||
return jobRequests
|
||||
}
|
||||
|
||||
if let quotedMessage = message.quotedMessage {
|
||||
for attachmentId in quotedMessage.thumbnailAttachmentStreamIds() {
|
||||
addJobRequest(attachmentId: attachmentId, category: .quotedReplyThumbnail)
|
||||
}
|
||||
if let attachmentId = quotedMessage.thumbnailAttachmentPointerId() {
|
||||
addJobRequest(attachmentId: attachmentId, category: .quotedReplyThumbnail)
|
||||
}
|
||||
// We only want to kick off a thumbnail fetching job if:
|
||||
// - The thumbnail attachment is owned by the quoted message content (so it's solely responsible for fetching)
|
||||
// - It's an unfetched pointer
|
||||
if message.quotedMessage?.isThumbnailOwned == true,
|
||||
let attachment = message.quotedMessage?.fetchThumbnail(with: transaction),
|
||||
attachment is TSAttachmentPointer {
|
||||
addJobRequest(attachment: attachment, category: .quotedReplyThumbnail)
|
||||
}
|
||||
|
||||
if let attachmentId = message.contactShare?.avatarAttachmentId {
|
||||
@ -1091,14 +1091,14 @@ public extension OWSAttachmentDownloads {
|
||||
Logger.warn("Missing message.")
|
||||
return
|
||||
}
|
||||
guard let thumbnailAttachmentPointerId = message.quotedMessage?.thumbnailAttachmentPointerId(),
|
||||
guard let thumbnailAttachmentPointerId = message.quotedMessage?.thumbnailAttachmentId,
|
||||
!thumbnailAttachmentPointerId.isEmpty else {
|
||||
return
|
||||
}
|
||||
guard let quotedMessageThumbnail = (attachmentStreams.filter { $0.uniqueId == thumbnailAttachmentPointerId }.first) else {
|
||||
return
|
||||
}
|
||||
message.setQuotedMessageThumbnailAttachmentStream(quotedMessageThumbnail)
|
||||
message.setQuotedMessageThumbnailAttachmentStream(quotedMessageThumbnail, transaction: transaction)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
//
|
||||
// Copyright (c) 2020 Open Whisper Systems. All rights reserved.
|
||||
// Copyright (c) 2021 Open Whisper Systems. All rights reserved.
|
||||
//
|
||||
|
||||
#import <Mantle/MTLModel.h>
|
||||
@ -8,7 +8,6 @@
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
@class CNContact;
|
||||
@class OWSAttachmentInfo;
|
||||
@class SDSAnyReadTransaction;
|
||||
@class SDSAnyWriteTransaction;
|
||||
@class SSKProtoDataMessage;
|
||||
|
||||
@ -112,7 +112,9 @@ NS_DESIGNATED_INITIALIZER NS_SWIFT_NAME(init(grdbId:uniqueId:receivedAtTimestamp
|
||||
// quoted reply thumbnails, contact share avatars, link preview images, etc.
|
||||
- (NSArray<NSString *> *)allAttachmentIds;
|
||||
|
||||
- (void)setQuotedMessageThumbnailAttachmentStream:(TSAttachmentStream *)attachmentStream;
|
||||
- (nullable TSAttachment *)fetchQuotedMessageThumbnailWithTransaction:(SDSAnyReadTransaction *)transaction;
|
||||
- (void)setQuotedMessageThumbnailAttachmentStream:(TSAttachmentStream *)attachmentStream
|
||||
transaction:(SDSAnyWriteTransaction *)transaction;
|
||||
|
||||
// The raw body contains placeholders for things like mentions and is not
|
||||
// user friendly. If you want a constant string representing the body of
|
||||
|
||||
@ -312,12 +312,8 @@ static const NSUInteger OWSMessageSchemaVersion = 4;
|
||||
[result addObjectsFromArray:self.attachmentIds];
|
||||
}
|
||||
|
||||
if (self.quotedMessage) {
|
||||
[result addObjectsFromArray:self.quotedMessage.thumbnailAttachmentStreamIds];
|
||||
|
||||
if (self.quotedMessage.thumbnailAttachmentPointerId != nil) {
|
||||
[result addObject:self.quotedMessage.thumbnailAttachmentPointerId];
|
||||
}
|
||||
if (self.quotedMessage.thumbnailAttachmentId) {
|
||||
[result addObject:self.quotedMessage.thumbnailAttachmentId];
|
||||
}
|
||||
|
||||
if (self.contactShare.avatarAttachmentId) {
|
||||
@ -700,13 +696,43 @@ static const NSUInteger OWSMessageSchemaVersion = 4;
|
||||
return _body.filterStringForDisplay;
|
||||
}
|
||||
|
||||
- (nullable TSAttachment *)fetchQuotedMessageThumbnailWithTransaction:(SDSAnyReadTransaction *)transaction
|
||||
{
|
||||
TSAttachment *_Nullable attachment = [self.quotedMessage fetchThumbnailWithTransaction:transaction];
|
||||
|
||||
// We should clone the attachment if it's been downloaded but our quotedMessage doesn't have its own copy.
|
||||
BOOL needsClone = [attachment isKindOfClass:[TSAttachmentStream class]] && !self.quotedMessage.isThumbnailOwned;
|
||||
TSAttachment * (^saveUpdatedThumbnail)(SDSAnyWriteTransaction *) = ^TSAttachment *(SDSAnyWriteTransaction *writeTx)
|
||||
{
|
||||
__block TSAttachment *_Nullable attachment = nil;
|
||||
[self anyUpdateMessageWithTransaction:writeTx
|
||||
block:^(TSMessage *message) {
|
||||
attachment = [message.quotedMessage
|
||||
createThumbnailIfNecessaryWithTransaction:writeTx];
|
||||
}];
|
||||
return attachment;
|
||||
};
|
||||
|
||||
// If we happen to be handed a write transaction, we can perform the clone synchronously
|
||||
// Otherwise, just hand the caller what we have. We'll clone it async.
|
||||
if (needsClone && [transaction isKindOfClass:[SDSAnyWriteTransaction class]]) {
|
||||
attachment = saveUpdatedThumbnail((SDSAnyWriteTransaction *)transaction);
|
||||
} else if (needsClone) {
|
||||
DatabaseStorageAsyncWrite(
|
||||
self.databaseStorage, ^(SDSAnyWriteTransaction *writeTx) { saveUpdatedThumbnail(writeTx); });
|
||||
}
|
||||
return attachment;
|
||||
}
|
||||
|
||||
- (void)setQuotedMessageThumbnailAttachmentStream:(TSAttachmentStream *)attachmentStream
|
||||
transaction:(SDSAnyWriteTransaction *)transaction
|
||||
{
|
||||
OWSAssertDebug([attachmentStream isKindOfClass:[TSAttachmentStream class]]);
|
||||
OWSAssertDebug(self.quotedMessage);
|
||||
OWSAssertDebug(self.quotedMessage.quotedAttachments.count == 1);
|
||||
|
||||
[self.quotedMessage setThumbnailAttachmentStream:attachmentStream];
|
||||
[self anyUpdateMessageWithTransaction:transaction
|
||||
block:^(TSMessage *message) {
|
||||
[message.quotedMessage setThumbnailAttachmentStream:attachmentStream];
|
||||
}];
|
||||
}
|
||||
|
||||
#pragma mark - Update With... Methods
|
||||
@ -886,19 +912,12 @@ static const NSUInteger OWSMessageSchemaVersion = 4;
|
||||
}
|
||||
}
|
||||
|
||||
NSMutableSet<NSString *> *quotedReplyAttachmentIds = [NSMutableSet new];
|
||||
if (self.quotedMessage.thumbnailAttachmentStreamIds != nil) {
|
||||
[quotedReplyAttachmentIds addObjectsFromArray:self.quotedMessage.thumbnailAttachmentStreamIds];
|
||||
if (self.quotedMessage.thumbnailAttachmentId) {
|
||||
[unknownAttachmentIds removeObject:self.quotedMessage.thumbnailAttachmentId];
|
||||
}
|
||||
if (self.quotedMessage.thumbnailAttachmentPointerId != nil) {
|
||||
[quotedReplyAttachmentIds addObject:self.quotedMessage.thumbnailAttachmentPointerId];
|
||||
}
|
||||
[unknownAttachmentIds minusSet:quotedReplyAttachmentIds];
|
||||
if (shouldRemoveQuotedReply) {
|
||||
for (NSString *attachmentId in quotedReplyAttachmentIds) {
|
||||
OWSLogVerbose(@"Removing quoted reply attachment.");
|
||||
[self removeAttachmentWithId:attachmentId transaction:transaction];
|
||||
}
|
||||
if (shouldRemoveQuotedReply && self.quotedMessage.thumbnailAttachmentId) {
|
||||
OWSLogVerbose(@"Removing quoted reply attachment.");
|
||||
[self removeAttachmentWithId:self.quotedMessage.thumbnailAttachmentId transaction:transaction];
|
||||
}
|
||||
|
||||
// Err on the side of cleaning up unknown attachments.
|
||||
|
||||
@ -1405,31 +1405,29 @@ NSUInteger const TSOutgoingMessageSchemaVersion = 1;
|
||||
}
|
||||
}
|
||||
|
||||
if (quotedMessage.quotedAttachments) {
|
||||
for (OWSAttachmentInfo *attachment in quotedMessage.quotedAttachments) {
|
||||
hasQuotedAttachment = YES;
|
||||
if (quotedMessage.hasAttachment) {
|
||||
SSKProtoDataMessageQuoteQuotedAttachmentBuilder *quotedAttachmentBuilder =
|
||||
[SSKProtoDataMessageQuoteQuotedAttachment builder];
|
||||
quotedAttachmentBuilder.contentType = quotedMessage.contentType;
|
||||
quotedAttachmentBuilder.fileName = quotedMessage.sourceFilename;
|
||||
|
||||
SSKProtoDataMessageQuoteQuotedAttachmentBuilder *quotedAttachmentBuilder =
|
||||
[SSKProtoDataMessageQuoteQuotedAttachment builder];
|
||||
|
||||
quotedAttachmentBuilder.contentType = attachment.contentType;
|
||||
quotedAttachmentBuilder.fileName = attachment.sourceFilename;
|
||||
if (attachment.thumbnailAttachmentStreamId) {
|
||||
quotedAttachmentBuilder.thumbnail =
|
||||
[TSAttachmentStream buildProtoForAttachmentId:attachment.thumbnailAttachmentStreamId
|
||||
transaction:transaction];
|
||||
}
|
||||
|
||||
NSError *error;
|
||||
SSKProtoDataMessageQuoteQuotedAttachment *_Nullable quotedAttachmentMessage =
|
||||
[quotedAttachmentBuilder buildAndReturnError:&error];
|
||||
if (error || !quotedAttachmentMessage) {
|
||||
OWSFailDebug(@"could not build protobuf: %@", error);
|
||||
return nil;
|
||||
}
|
||||
|
||||
[quoteBuilder addAttachments:quotedAttachmentMessage];
|
||||
if (quotedMessage.thumbnailAttachmentId && quotedMessage.isThumbnailOwned) {
|
||||
NSString *attachmentId = quotedMessage.thumbnailAttachmentId;
|
||||
quotedAttachmentBuilder.thumbnail = [TSAttachmentStream buildProtoForAttachmentId:attachmentId
|
||||
transaction:transaction];
|
||||
} else if (quotedMessage.thumbnailAttachmentId) {
|
||||
OWSFailDebug(@"Referencing an attachment that isn't owned by the quote.");
|
||||
}
|
||||
|
||||
NSError *error;
|
||||
SSKProtoDataMessageQuoteQuotedAttachment *_Nullable quotedAttachmentMessage =
|
||||
[quotedAttachmentBuilder buildAndReturnError:&error];
|
||||
if (error || !quotedAttachmentMessage) {
|
||||
OWSFailDebug(@"could not build protobuf: %@", error);
|
||||
return nil;
|
||||
}
|
||||
[quoteBuilder addAttachments:quotedAttachmentMessage];
|
||||
hasQuotedAttachment = YES;
|
||||
}
|
||||
|
||||
if (hasQuotedText || hasQuotedAttachment) {
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
//
|
||||
// Copyright (c) 2020 Open Whisper Systems. All rights reserved.
|
||||
// Copyright (c) 2021 Open Whisper Systems. All rights reserved.
|
||||
//
|
||||
|
||||
#import <Mantle/MTLModel.h>
|
||||
@ -7,6 +7,7 @@
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
@class MessageBodyRanges;
|
||||
@class SDSAnyReadTransaction;
|
||||
@class SDSAnyWriteTransaction;
|
||||
@class SSKProtoDataMessage;
|
||||
@class SignalServiceAddress;
|
||||
@ -15,42 +16,6 @@ NS_ASSUME_NONNULL_BEGIN
|
||||
@class TSQuotedMessage;
|
||||
@class TSThread;
|
||||
|
||||
@interface OWSAttachmentInfo : MTLModel
|
||||
|
||||
@property (nonatomic, readonly, nullable) NSString *contentType;
|
||||
@property (nonatomic, readonly, nullable) NSString *sourceFilename;
|
||||
|
||||
// This is only set when sending a new attachment so we have a way
|
||||
// to reference the original attachment when generating a thumbnail.
|
||||
// We don't want to do this until the message is saved, when the user sends
|
||||
// the message so as not to end up with an orphaned file.
|
||||
@property (nonatomic, readonly, nullable) NSString *attachmentId;
|
||||
|
||||
// References a yet-to-be downloaded thumbnail file
|
||||
@property (atomic, nullable) NSString *thumbnailAttachmentPointerId;
|
||||
|
||||
// References an already downloaded or locally generated thumbnail file
|
||||
@property (atomic, nullable) NSString *thumbnailAttachmentStreamId;
|
||||
|
||||
+ (instancetype)new NS_UNAVAILABLE;
|
||||
- (instancetype)init NS_UNAVAILABLE;
|
||||
|
||||
- (instancetype)initWithAttachmentId:(nullable NSString *)attachmentId
|
||||
contentType:(NSString *)contentType
|
||||
sourceFilename:(NSString *)sourceFilename NS_DESIGNATED_INITIALIZER;
|
||||
|
||||
- (instancetype)initWithAttachmentStream:(TSAttachmentStream *)attachmentStream;
|
||||
|
||||
- (instancetype)initWithAttachmentId:(nullable NSString *)attachmentId
|
||||
contentType:(NSString *)contentType
|
||||
sourceFilename:(NSString *)sourceFilename
|
||||
thumbnailAttachmentPointerId:(nullable NSString *)thumbnailAttachmentPointerId
|
||||
thumbnailAttachmentStreamId:(nullable NSString *)thumbnailAttachmentStreamId NS_DESIGNATED_INITIALIZER;
|
||||
|
||||
@end
|
||||
|
||||
#pragma mark -
|
||||
|
||||
typedef NS_ENUM(NSUInteger, TSQuotedMessageContentSource) {
|
||||
TSQuotedMessageContentSourceUnknown,
|
||||
TSQuotedMessageContentSourceLocal,
|
||||
@ -70,47 +35,35 @@ typedef NS_ENUM(NSUInteger, TSQuotedMessageContentSource) {
|
||||
|
||||
#pragma mark - Attachments
|
||||
|
||||
// This is a MIME type.
|
||||
//
|
||||
// This property should be set IFF we are quoting an attachment message.
|
||||
- (nullable NSString *)contentType;
|
||||
- (nullable NSString *)sourceFilename;
|
||||
@property (nonatomic, readonly) BOOL hasAttachment;
|
||||
|
||||
// References a yet-to-be downloaded thumbnail file
|
||||
- (nullable NSString *)thumbnailAttachmentPointerId;
|
||||
/// Returns YES if the thumbnail is something maintained by the quoted reply itself (as opposed to to media in some
|
||||
/// other message)
|
||||
@property (nonatomic, readonly) BOOL isThumbnailOwned;
|
||||
@property (nonatomic, readonly, nullable) NSString *thumbnailAttachmentId;
|
||||
@property (nonatomic, readonly, nullable) NSString *contentType;
|
||||
@property (nonatomic, readonly, nullable) NSString *sourceFilename;
|
||||
|
||||
// Should only be called by TSMessage. May perform a sneaky write if necessary
|
||||
- (nullable TSAttachment *)fetchThumbnailWithTransaction:(SDSAnyReadTransaction *)transaction;
|
||||
|
||||
// References an already downloaded or locally generated thumbnail file
|
||||
- (nullable NSString *)thumbnailAttachmentStreamId;
|
||||
- (void)setThumbnailAttachmentStream:(TSAttachment *)thumbnailAttachmentStream;
|
||||
|
||||
// currently only used by orphan attachment cleaner
|
||||
- (NSArray<NSString *> *)thumbnailAttachmentStreamIds;
|
||||
|
||||
@property (atomic, readonly) NSArray<OWSAttachmentInfo *> *quotedAttachments;
|
||||
|
||||
// Before sending, persist a thumbnail attachment derived from the quoted attachment
|
||||
- (NSArray<TSAttachmentStream *> *)createThumbnailAttachmentsIfNecessaryWithTransaction:
|
||||
(SDSAnyWriteTransaction *)transaction;
|
||||
- (nullable TSAttachmentStream *)createThumbnailIfNecessaryWithTransaction:(SDSAnyWriteTransaction *)transaction;
|
||||
|
||||
+ (instancetype)new NS_UNAVAILABLE;
|
||||
- (instancetype)init NS_UNAVAILABLE;
|
||||
|
||||
// used when receiving quoted messages
|
||||
- (instancetype)initWithTimestamp:(uint64_t)timestamp
|
||||
authorAddress:(SignalServiceAddress *)authorAddress
|
||||
body:(nullable NSString *)body
|
||||
bodyRanges:(nullable MessageBodyRanges *)bodyRanges
|
||||
bodySource:(TSQuotedMessageContentSource)bodySource
|
||||
receivedQuotedAttachmentInfos:(NSArray<OWSAttachmentInfo *> *)attachmentInfos;
|
||||
|
||||
// used when sending quoted messages
|
||||
- (instancetype)initWithTimestamp:(uint64_t)timestamp
|
||||
authorAddress:(SignalServiceAddress *)authorAddress
|
||||
body:(nullable NSString *)body
|
||||
bodyRanges:(nullable MessageBodyRanges *)bodyRanges
|
||||
quotedAttachmentsForSending:(NSArray<TSAttachment *> *)attachments;
|
||||
|
||||
quotedAttachmentForSending:(nullable TSAttachment *)attachment;
|
||||
|
||||
// used when receiving quoted messages
|
||||
+ (nullable instancetype)quotedMessageForDataMessage:(SSKProtoDataMessage *)dataMessage
|
||||
thread:(TSThread *)thread
|
||||
transaction:(SDSAnyWriteTransaction *)transaction;
|
||||
|
||||
@ -2,6 +2,7 @@
|
||||
// Copyright (c) 2021 Open Whisper Systems. All rights reserved.
|
||||
//
|
||||
|
||||
#import "TSQuotedMessage.h"
|
||||
#import <SignalServiceKit/SignalServiceKit-Swift.h>
|
||||
#import <SignalServiceKit/TSAccountManager.h>
|
||||
#import <SignalServiceKit/TSAttachment.h>
|
||||
@ -10,67 +11,131 @@
|
||||
#import <SignalServiceKit/TSIncomingMessage.h>
|
||||
#import <SignalServiceKit/TSInteraction.h>
|
||||
#import <SignalServiceKit/TSOutgoingMessage.h>
|
||||
#import <SignalServiceKit/TSQuotedMessage.h>
|
||||
#import <SignalServiceKit/TSThread.h>
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
/// Indicates the sort of attachment ID included in the attachment info
|
||||
typedef NS_ENUM(NSUInteger, OWSAttachmentInfoReference) {
|
||||
OWSAttachmentInfoReferenceUnset = 0,
|
||||
/// An original attachment for a quoted reply draft. This needs to be thumbnailed before it is sent.
|
||||
OWSAttachmentInfoReferenceOriginalForSend = 1,
|
||||
/// A reference to an original attachment in a quoted reply we've received. If this ever manifests as a stream
|
||||
/// we should clone it as a private thumbnail
|
||||
OWSAttachmentInfoReferenceOriginal,
|
||||
/// A private thumbnail that we (the quoted reply) have ownership of
|
||||
OWSAttachmentInfoReferenceThumbnail,
|
||||
/// An untrusted pointer to a thumbnail. This was included in the proto of a message we've received.
|
||||
OWSAttachmentInfoReferenceUntrustedPointer,
|
||||
};
|
||||
|
||||
@interface OWSAttachmentInfo : MTLModel
|
||||
@property (class, nonatomic, readonly) NSUInteger currentSchemaVersion;
|
||||
@property (nonatomic, readonly) NSUInteger schemaVersion;
|
||||
|
||||
@property (nonatomic, readonly, nullable) NSString *contentType;
|
||||
@property (nonatomic, readonly, nullable) NSString *sourceFilename;
|
||||
@property (nonatomic) OWSAttachmentInfoReference attachmentType;
|
||||
@property (nonatomic) NSString *rawAttachmentId;
|
||||
|
||||
+ (instancetype)new NS_UNAVAILABLE;
|
||||
- (instancetype)init NS_UNAVAILABLE;
|
||||
@end
|
||||
|
||||
@implementation OWSAttachmentInfo
|
||||
|
||||
- (instancetype)initWithAttachmentStream:(TSAttachmentStream *)attachmentStream
|
||||
- (instancetype)initWithoutThumbnailWithContentType:(NSString *)contentType sourceFilename:(NSString *)sourceFilename
|
||||
{
|
||||
self = [super init];
|
||||
if (self) {
|
||||
_schemaVersion = self.class.currentSchemaVersion;
|
||||
_rawAttachmentId = nil;
|
||||
_attachmentType = OWSAttachmentInfoReferenceUnset;
|
||||
_contentType = contentType;
|
||||
_sourceFilename = sourceFilename;
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (instancetype)initWithOriginalAttachmentStream:(TSAttachmentStream *)attachmentStream
|
||||
{
|
||||
OWSAssertDebug([attachmentStream isKindOfClass:[TSAttachmentStream class]]);
|
||||
OWSAssertDebug(attachmentStream.uniqueId);
|
||||
OWSAssertDebug(attachmentStream.contentType);
|
||||
|
||||
return [self initWithAttachmentId:attachmentStream.uniqueId
|
||||
contentType:attachmentStream.contentType
|
||||
sourceFilename:attachmentStream.sourceFilename];
|
||||
if ([TSAttachmentStream hasThumbnailForMimeType:attachmentStream.contentType]) {
|
||||
return [self initWithAttachmentId:attachmentStream.uniqueId
|
||||
ofType:OWSAttachmentInfoReferenceOriginalForSend
|
||||
contentType:attachmentStream.contentType
|
||||
sourceFilename:attachmentStream.sourceFilename];
|
||||
} else {
|
||||
return [self initWithoutThumbnailWithContentType:attachmentStream.contentType
|
||||
sourceFilename:attachmentStream.sourceFilename];
|
||||
}
|
||||
}
|
||||
|
||||
- (instancetype)initWithAttachmentId:(nullable NSString *)attachmentId
|
||||
- (instancetype)initWithAttachmentId:(NSString *)attachmentId
|
||||
ofType:(OWSAttachmentInfoReference)attachmentType
|
||||
contentType:(NSString *)contentType
|
||||
sourceFilename:(NSString *)sourceFilename
|
||||
{
|
||||
self = [super init];
|
||||
if (!self) {
|
||||
return self;
|
||||
if (self) {
|
||||
_schemaVersion = self.class.currentSchemaVersion;
|
||||
_rawAttachmentId = attachmentId;
|
||||
_attachmentType = attachmentType;
|
||||
_contentType = contentType;
|
||||
_sourceFilename = sourceFilename;
|
||||
}
|
||||
|
||||
_attachmentId = attachmentId;
|
||||
_contentType = contentType;
|
||||
_sourceFilename = sourceFilename;
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
- (instancetype)initWithAttachmentId:(nullable NSString *)attachmentId
|
||||
contentType:(NSString *)contentType
|
||||
sourceFilename:(NSString *)sourceFilename
|
||||
thumbnailAttachmentPointerId:(nullable NSString *)thumbnailAttachmentPointerId
|
||||
thumbnailAttachmentStreamId:(nullable NSString *)thumbnailAttachmentStreamId
|
||||
+ (NSUInteger)currentSchemaVersion
|
||||
{
|
||||
self = [super init];
|
||||
return 1;
|
||||
}
|
||||
|
||||
- (nullable instancetype)initWithCoder:(NSCoder *)coder
|
||||
{
|
||||
self = [super initWithCoder:coder];
|
||||
if (!self) {
|
||||
return self;
|
||||
}
|
||||
|
||||
_attachmentId = attachmentId;
|
||||
_contentType = contentType;
|
||||
_sourceFilename = sourceFilename;
|
||||
_thumbnailAttachmentPointerId = thumbnailAttachmentPointerId;
|
||||
_thumbnailAttachmentStreamId = thumbnailAttachmentStreamId;
|
||||
if (_schemaVersion == 0) {
|
||||
NSString *_Nullable oldStreamId = [coder decodeObjectOfClass:[NSString class]
|
||||
forKey:@"thumbnailAttachmentStreamId"];
|
||||
NSString *_Nullable oldPointerId = [coder decodeObjectOfClass:[NSString class]
|
||||
forKey:@"thumbnailAttachmentPointerId"];
|
||||
NSString *_Nullable oldSourceAttachmentId = [coder decodeObjectOfClass:[NSString class] forKey:@"attachmentId"];
|
||||
|
||||
// Before, we maintained each of these IDs in parallel, though in practice only one in use at a time.
|
||||
// Migration codifies this behavior.
|
||||
if (oldStreamId && [oldPointerId isEqualToString:oldStreamId]) {
|
||||
_attachmentType = OWSAttachmentInfoReferenceThumbnail;
|
||||
_rawAttachmentId = oldStreamId;
|
||||
} else if (oldPointerId) {
|
||||
_attachmentType = OWSAttachmentInfoReferenceUntrustedPointer;
|
||||
_rawAttachmentId = oldPointerId;
|
||||
} else if (oldStreamId) {
|
||||
_attachmentType = OWSAttachmentInfoReferenceThumbnail;
|
||||
_rawAttachmentId = oldStreamId;
|
||||
} else if (oldSourceAttachmentId) {
|
||||
_attachmentType = OWSAttachmentInfoReferenceOriginalForSend;
|
||||
_rawAttachmentId = oldSourceAttachmentId;
|
||||
} else {
|
||||
_attachmentType = OWSAttachmentInfoReferenceUnset;
|
||||
_rawAttachmentId = nil;
|
||||
}
|
||||
}
|
||||
_schemaVersion = self.class.currentSchemaVersion;
|
||||
return self;
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
@interface TSQuotedMessage ()
|
||||
|
||||
@property (atomic) NSArray<OWSAttachmentInfo *> *quotedAttachments;
|
||||
@property (atomic) NSArray<TSAttachmentStream *> *quotedAttachmentsForSending;
|
||||
|
||||
@property (nonatomic, nullable) OWSAttachmentInfo *quotedAttachment;
|
||||
@end
|
||||
|
||||
@implementation TSQuotedMessage
|
||||
@ -80,7 +145,7 @@ NS_ASSUME_NONNULL_BEGIN
|
||||
body:(nullable NSString *)body
|
||||
bodyRanges:(nullable MessageBodyRanges *)bodyRanges
|
||||
bodySource:(TSQuotedMessageContentSource)bodySource
|
||||
receivedQuotedAttachmentInfos:(NSArray<OWSAttachmentInfo *> *)attachmentInfos
|
||||
receivedQuotedAttachmentInfo:(nullable OWSAttachmentInfo *)attachmentInfo
|
||||
{
|
||||
OWSAssertDebug(timestamp > 0);
|
||||
OWSAssertDebug(authorAddress.isValid);
|
||||
@ -95,7 +160,7 @@ NS_ASSUME_NONNULL_BEGIN
|
||||
_body = body;
|
||||
_bodyRanges = bodyRanges;
|
||||
_bodySource = bodySource;
|
||||
_quotedAttachments = attachmentInfos;
|
||||
_quotedAttachment = attachmentInfo;
|
||||
|
||||
return self;
|
||||
}
|
||||
@ -104,7 +169,7 @@ NS_ASSUME_NONNULL_BEGIN
|
||||
authorAddress:(SignalServiceAddress *)authorAddress
|
||||
body:(nullable NSString *)body
|
||||
bodyRanges:(nullable MessageBodyRanges *)bodyRanges
|
||||
quotedAttachmentsForSending:(NSArray<TSAttachmentStream *> *)attachments
|
||||
quotedAttachmentForSending:(nullable TSAttachmentStream *)attachment
|
||||
{
|
||||
OWSAssertDebug(timestamp > 0);
|
||||
OWSAssertDebug(authorAddress.isValid);
|
||||
@ -119,12 +184,7 @@ NS_ASSUME_NONNULL_BEGIN
|
||||
_body = body;
|
||||
_bodyRanges = bodyRanges;
|
||||
_bodySource = TSQuotedMessageContentSourceLocal;
|
||||
|
||||
NSMutableArray *attachmentInfos = [NSMutableArray new];
|
||||
for (TSAttachmentStream *attachmentStream in attachments) {
|
||||
[attachmentInfos addObject:[[OWSAttachmentInfo alloc] initWithAttachmentStream:attachmentStream]];
|
||||
}
|
||||
_quotedAttachments = [attachmentInfos copy];
|
||||
_quotedAttachment = attachment ? [[OWSAttachmentInfo alloc] initWithOriginalAttachmentStream:attachment] : nil;
|
||||
|
||||
return self;
|
||||
}
|
||||
@ -141,6 +201,17 @@ NS_ASSUME_NONNULL_BEGIN
|
||||
OWSAssertDebug(_authorAddress.isValid);
|
||||
}
|
||||
|
||||
if (_quotedAttachment == nil) {
|
||||
NSSet *expectedClasses = [NSSet setWithArray:@[ [NSArray class], [OWSAttachmentInfo class] ]];
|
||||
NSArray *_Nullable attachmentInfos = [coder decodeObjectOfClasses:expectedClasses forKey:@"quotedAttachments"];
|
||||
|
||||
if ([attachmentInfos.firstObject isKindOfClass:[OWSAttachmentInfo class]]) {
|
||||
// In practice, we only used the first item of this array
|
||||
OWSAssertDebug(attachmentInfos.count <= 1);
|
||||
_quotedAttachment = attachmentInfos.firstObject;
|
||||
}
|
||||
}
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
@ -171,241 +242,260 @@ NS_ASSUME_NONNULL_BEGIN
|
||||
return nil;
|
||||
}
|
||||
|
||||
SignalServiceAddress *authorAddress = quoteProto.authorAddress;
|
||||
TSQuotedMessage *_Nullable quotedMessage = nil;
|
||||
TSMessage *_Nullable originalMessage = [InteractionFinder findMessageWithTimestamp:timestamp
|
||||
threadId:thread.uniqueId
|
||||
author:quoteProto.authorAddress
|
||||
transaction:transaction];
|
||||
if (originalMessage) {
|
||||
// Prefer to generate the quoted content locally if available.
|
||||
quotedMessage = [self localQuotedMessageFromSourceMessage:originalMessage
|
||||
quoteProto:quoteProto
|
||||
transaction:transaction];
|
||||
}
|
||||
if (!quotedMessage) {
|
||||
// If we couldn't generate the quoted content from locally available info, we can generate it from the proto.
|
||||
quotedMessage = [self remoteQuotedMessageFromQuoteProto:quoteProto transaction:transaction];
|
||||
}
|
||||
|
||||
OWSAssertDebug(quotedMessage);
|
||||
return quotedMessage;
|
||||
}
|
||||
|
||||
+ (nullable TSAttachment *)quotedAttachmentFromOriginalMessage:(TSMessage *)quotedMessage
|
||||
transaction:(SDSAnyWriteTransaction *)transaction
|
||||
{
|
||||
if (quotedMessage.attachmentIds.count > 0) {
|
||||
return [quotedMessage bodyAttachmentsWithTransaction:transaction.unwrapGrdbRead].firstObject;
|
||||
} else if (quotedMessage.linkPreview && quotedMessage.linkPreview.imageAttachmentId.length > 0) {
|
||||
return [TSAttachment anyFetchWithUniqueId:quotedMessage.linkPreview.imageAttachmentId transaction:transaction];
|
||||
} else if (quotedMessage.messageSticker && quotedMessage.messageSticker.attachmentId.length > 0) {
|
||||
return [TSAttachment anyFetchWithUniqueId:quotedMessage.messageSticker.attachmentId transaction:transaction];
|
||||
} else {
|
||||
return nil;
|
||||
}
|
||||
}
|
||||
|
||||
#pragma mark - Private
|
||||
|
||||
/// Builds a quoted message from the original source message
|
||||
+ (nullable TSQuotedMessage *)localQuotedMessageFromSourceMessage:(TSMessage *)quotedMessage
|
||||
quoteProto:(SSKProtoDataMessageQuote *)proto
|
||||
transaction:(SDSAnyWriteTransaction *)transaction
|
||||
{
|
||||
if (quotedMessage.isViewOnceMessage) {
|
||||
// We construct a quote that does not include any of the quoted message's renderable content.
|
||||
NSString *body = NSLocalizedString(@"PER_MESSAGE_EXPIRATION_NOT_VIEWABLE",
|
||||
@"inbox cell and notification text for an already viewed view-once media message.");
|
||||
return [[TSQuotedMessage alloc] initWithTimestamp:quotedMessage.timestamp
|
||||
authorAddress:proto.authorAddress
|
||||
body:body
|
||||
bodyRanges:nil
|
||||
bodySource:TSQuotedMessageContentSourceLocal
|
||||
receivedQuotedAttachmentInfo:nil];
|
||||
}
|
||||
|
||||
NSString *_Nullable body = nil;
|
||||
MessageBodyRanges *_Nullable bodyRanges = nil;
|
||||
BOOL hasAttachment = NO;
|
||||
TSQuotedMessageContentSource contentSource = TSQuotedMessageContentSourceUnknown;
|
||||
OWSAttachmentInfo *attachmentInfo = nil;
|
||||
|
||||
// Prefer to generate the text snippet locally if available.
|
||||
TSMessage *_Nullable quotedMessage = [InteractionFinder findMessageWithTimestamp:timestamp
|
||||
threadId:thread.uniqueId
|
||||
author:authorAddress
|
||||
transaction:transaction];
|
||||
if (quotedMessage.body.length > 0) {
|
||||
body = quotedMessage.body;
|
||||
bodyRanges = quotedMessage.bodyRanges;
|
||||
|
||||
if (quotedMessage) {
|
||||
contentSource = TSQuotedMessageContentSourceLocal;
|
||||
|
||||
if (quotedMessage.isViewOnceMessage) {
|
||||
// We construct a quote that does not include any of the
|
||||
// quoted message's renderable content.
|
||||
body = NSLocalizedString(
|
||||
@"PER_MESSAGE_EXPIRATION_OUTGOING_MESSAGE", @"Label for outgoing view-once messages.");
|
||||
// Legit usage of senderTimestamp - this class references the message it is quoting by it's sender timestamp
|
||||
return [[TSQuotedMessage alloc] initWithTimestamp:timestamp
|
||||
authorAddress:authorAddress
|
||||
body:body
|
||||
bodyRanges:nil
|
||||
bodySource:TSQuotedMessageContentSourceLocal
|
||||
receivedQuotedAttachmentInfos:@[]];
|
||||
}
|
||||
|
||||
if (quotedMessage.body.length > 0) {
|
||||
body = quotedMessage.body;
|
||||
bodyRanges = quotedMessage.bodyRanges;
|
||||
|
||||
} else if (quotedMessage.contactShare.name.displayName.length > 0) {
|
||||
// Contact share bodies are special-cased in OWSQuotedReplyModel
|
||||
// We need to account for that here.
|
||||
body = [@"👤 " stringByAppendingString:quotedMessage.contactShare.name.displayName];
|
||||
bodyRanges = nil;
|
||||
}
|
||||
} else {
|
||||
OWSLogWarn(@"Could not find quoted message: %llu", timestamp);
|
||||
contentSource = TSQuotedMessageContentSourceRemote;
|
||||
if (quoteProto.text.length > 0) {
|
||||
body = quoteProto.text;
|
||||
}
|
||||
if (quoteProto.bodyRanges.count > 0) {
|
||||
bodyRanges = [[MessageBodyRanges alloc] initWithProtos:quoteProto.bodyRanges];
|
||||
}
|
||||
} else if (quotedMessage.contactShare.name.displayName.length > 0) {
|
||||
// Contact share bodies are special-cased in OWSQuotedReplyModel
|
||||
// We need to account for that here.
|
||||
body = [@"👤 " stringByAppendingString:quotedMessage.contactShare.name.displayName];
|
||||
}
|
||||
|
||||
OWSAssertDebug(contentSource != TSQuotedMessageContentSourceUnknown);
|
||||
SSKProtoDataMessageQuoteQuotedAttachment *_Nullable firstAttachmentProto = proto.attachments.firstObject;
|
||||
|
||||
NSMutableArray<OWSAttachmentInfo *> *attachmentInfos = [NSMutableArray new];
|
||||
for (SSKProtoDataMessageQuoteQuotedAttachment *quotedAttachment in quoteProto.attachments) {
|
||||
hasAttachment = YES;
|
||||
OWSAttachmentInfo *attachmentInfo = [[OWSAttachmentInfo alloc] initWithAttachmentId:nil
|
||||
contentType:quotedAttachment.contentType
|
||||
sourceFilename:quotedAttachment.fileName];
|
||||
if (firstAttachmentProto) {
|
||||
TSAttachment *toQuote = [self quotedAttachmentFromOriginalMessage:quotedMessage transaction:transaction];
|
||||
BOOL shouldThumbnail = [TSAttachmentStream hasThumbnailForMimeType:toQuote.contentType];
|
||||
|
||||
// We prefer deriving any thumbnail locally rather than fetching one from the network.
|
||||
TSAttachmentStream *_Nullable localThumbnail =
|
||||
[self tryToDeriveLocalThumbnailWithTimestamp:timestamp
|
||||
threadId:thread.uniqueId
|
||||
authorAddress:authorAddress
|
||||
contentType:quotedAttachment.contentType
|
||||
transaction:transaction];
|
||||
if ([toQuote isKindOfClass:[TSAttachmentStream class]] && shouldThumbnail) {
|
||||
// We found an attachment stream on the original message! Use it as our quoted attachment
|
||||
TSAttachmentStream *thumbnail = [(TSAttachmentStream *)toQuote cloneAsThumbnail];
|
||||
[thumbnail anyInsertWithTransaction:transaction];
|
||||
|
||||
if (localThumbnail) {
|
||||
OWSLogDebug(@"Generated local thumbnail for quoted quoted message: %@:%llu", thread.uniqueId, timestamp);
|
||||
// It would be surprising if we could derive a local thumbnail when
|
||||
// the body had to be derived remotely.
|
||||
OWSAssertDebug(contentSource == TSQuotedMessageContentSourceLocal);
|
||||
attachmentInfo = [[OWSAttachmentInfo alloc] initWithAttachmentId:thumbnail.uniqueId
|
||||
ofType:OWSAttachmentInfoReferenceThumbnail
|
||||
contentType:toQuote.contentType
|
||||
sourceFilename:toQuote.sourceFilename];
|
||||
|
||||
[localThumbnail anyInsertWithTransaction:transaction];
|
||||
|
||||
attachmentInfo.thumbnailAttachmentStreamId = localThumbnail.uniqueId;
|
||||
} else if (quotedAttachment.thumbnail) {
|
||||
OWSLogDebug(@"Saving reference for fetching remote thumbnail for quoted message: %@:%llu",
|
||||
thread.uniqueId,
|
||||
timestamp);
|
||||
|
||||
contentSource = TSQuotedMessageContentSourceRemote;
|
||||
SSKProtoAttachmentPointer *thumbnailAttachmentProto = quotedAttachment.thumbnail;
|
||||
TSAttachmentPointer *_Nullable thumbnailPointer =
|
||||
[TSAttachmentPointer attachmentPointerFromProto:thumbnailAttachmentProto albumMessage:nil];
|
||||
if (thumbnailPointer) {
|
||||
[thumbnailPointer anyInsertWithTransaction:transaction];
|
||||
|
||||
attachmentInfo.thumbnailAttachmentPointerId = thumbnailPointer.uniqueId;
|
||||
} else {
|
||||
OWSFailDebug(@"Invalid thumbnail attachment.");
|
||||
}
|
||||
} else if ([toQuote isKindOfClass:[TSAttachmentPointer class]] && shouldThumbnail) {
|
||||
// No attachment stream, but we have a pointer. It's likely this media hasn't finished downloading yet.
|
||||
attachmentInfo = [[OWSAttachmentInfo alloc] initWithAttachmentId:toQuote.uniqueId
|
||||
ofType:OWSAttachmentInfoReferenceOriginal
|
||||
contentType:toQuote.contentType
|
||||
sourceFilename:toQuote.sourceFilename];
|
||||
} else if (toQuote) {
|
||||
// We have an attachment in the original message, but it doesn't support thumbnailing
|
||||
attachmentInfo = [[OWSAttachmentInfo alloc] initWithoutThumbnailWithContentType:toQuote.contentType
|
||||
sourceFilename:toQuote.sourceFilename];
|
||||
} else {
|
||||
OWSLogDebug(@"No thumbnail for quoted message: %@:%llu", thread.uniqueId, timestamp);
|
||||
// This could happen if a sender spoofs their quoted message proto.
|
||||
// Our quoted message will include no thumbnails.
|
||||
OWSFailDebug(@"Sender sent %lu quoted attachments. Local copy has none.", proto.attachments.count);
|
||||
}
|
||||
|
||||
[attachmentInfos addObject:attachmentInfo];
|
||||
|
||||
// For now, only support a single quoted attachment.
|
||||
break;
|
||||
}
|
||||
|
||||
if (body.length == 0 && !hasAttachment) {
|
||||
if (body.length == 0 && !attachmentInfo) {
|
||||
OWSFailDebug(@"quoted message has neither text nor attachment");
|
||||
return nil;
|
||||
}
|
||||
|
||||
// Legit usage of senderTimestamp - this class references the message it is quoting by it's sender timestamp
|
||||
return [[TSQuotedMessage alloc] initWithTimestamp:timestamp
|
||||
authorAddress:authorAddress
|
||||
SignalServiceAddress *address = nil;
|
||||
if ([quotedMessage isKindOfClass:[TSIncomingMessage class]]) {
|
||||
address = ((TSIncomingMessage *)quotedMessage).authorAddress;
|
||||
} else if ([quotedMessage isKindOfClass:[TSOutgoingMessage class]]) {
|
||||
address = [TSAccountManager localAddress];
|
||||
} else {
|
||||
OWSFailDebug(@"Received message of type: %@", NSStringFromClass(quotedMessage.class));
|
||||
return nil;
|
||||
}
|
||||
|
||||
return [[TSQuotedMessage alloc] initWithTimestamp:quotedMessage.timestamp
|
||||
authorAddress:address
|
||||
body:body
|
||||
bodyRanges:bodyRanges
|
||||
bodySource:contentSource
|
||||
receivedQuotedAttachmentInfos:attachmentInfos];
|
||||
bodySource:TSQuotedMessageContentSourceLocal
|
||||
receivedQuotedAttachmentInfo:attachmentInfo];
|
||||
}
|
||||
|
||||
+ (nullable TSAttachmentStream *)tryToDeriveLocalThumbnailWithTimestamp:(uint64_t)timestamp
|
||||
threadId:(NSString *)threadId
|
||||
authorAddress:(SignalServiceAddress *)authorAddress
|
||||
contentType:(NSString *)contentType
|
||||
transaction:(SDSAnyWriteTransaction *)transaction
|
||||
/// Builds a remote message from the proto payload
|
||||
/// @note Quoted messages constructed from proto material may not be representative of the original source content. This
|
||||
/// should be flagged to the user. (See: -[OWSQuotedReplyModel isRemotelySourced])
|
||||
+ (nullable TSQuotedMessage *)remoteQuotedMessageFromQuoteProto:(SSKProtoDataMessageQuote *)proto
|
||||
transaction:(SDSAnyWriteTransaction *)transaction
|
||||
{
|
||||
TSMessage *_Nullable quotedMessage = [InteractionFinder findMessageWithTimestamp:timestamp
|
||||
threadId:threadId
|
||||
author:authorAddress
|
||||
transaction:transaction];
|
||||
NSString *_Nullable body = nil;
|
||||
MessageBodyRanges *_Nullable bodyRanges = nil;
|
||||
OWSAttachmentInfo *attachmentInfo = nil;
|
||||
|
||||
if (!quotedMessage) {
|
||||
OWSLogWarn(@"Could not find quoted message: %llu", timestamp);
|
||||
return nil;
|
||||
if (proto.text.length > 0) {
|
||||
body = proto.text;
|
||||
}
|
||||
if (proto.bodyRanges.count > 0) {
|
||||
bodyRanges = [[MessageBodyRanges alloc] initWithProtos:proto.bodyRanges];
|
||||
}
|
||||
|
||||
TSAttachment *_Nullable attachmentToQuote = nil;
|
||||
if (quotedMessage.attachmentIds.count > 0) {
|
||||
attachmentToQuote = [quotedMessage bodyAttachmentsWithTransaction:transaction.unwrapGrdbRead].firstObject;
|
||||
} else if (quotedMessage.linkPreview && quotedMessage.linkPreview.imageAttachmentId.length > 0) {
|
||||
attachmentToQuote =
|
||||
[TSAttachment anyFetchWithUniqueId:quotedMessage.linkPreview.imageAttachmentId transaction:transaction];
|
||||
} else if (quotedMessage.messageSticker && quotedMessage.messageSticker.attachmentId.length > 0) {
|
||||
attachmentToQuote =
|
||||
[TSAttachment anyFetchWithUniqueId:quotedMessage.messageSticker.attachmentId transaction:transaction];
|
||||
// We're only interested in the first attachment
|
||||
SSKProtoAttachmentPointer *thumbnailProto = proto.attachments.firstObject.thumbnail;
|
||||
if (thumbnailProto) {
|
||||
TSAttachmentPointer *_Nullable attachment = [TSAttachmentPointer attachmentPointerFromProto:thumbnailProto
|
||||
albumMessage:nil];
|
||||
if (attachment) {
|
||||
[attachment anyInsertWithTransaction:transaction];
|
||||
attachmentInfo = [[OWSAttachmentInfo alloc] initWithAttachmentId:attachment.uniqueId
|
||||
ofType:OWSAttachmentInfoReferenceUntrustedPointer
|
||||
contentType:thumbnailProto.contentType
|
||||
sourceFilename:thumbnailProto.fileName];
|
||||
} else {
|
||||
attachmentInfo = [[OWSAttachmentInfo alloc] initWithoutThumbnailWithContentType:thumbnailProto.contentType
|
||||
sourceFilename:thumbnailProto.fileName];
|
||||
}
|
||||
}
|
||||
if (![attachmentToQuote isKindOfClass:[TSAttachmentStream class]]) {
|
||||
|
||||
if (body.length > 0 || attachmentInfo) {
|
||||
return [[TSQuotedMessage alloc] initWithTimestamp:proto.id
|
||||
authorAddress:proto.authorAddress
|
||||
body:body
|
||||
bodyRanges:bodyRanges
|
||||
bodySource:TSQuotedMessageContentSourceRemote
|
||||
receivedQuotedAttachmentInfo:attachmentInfo];
|
||||
} else {
|
||||
OWSFailDebug(@"Failed to construct a valid quoted message from remote proto content");
|
||||
return nil;
|
||||
}
|
||||
if (![TSAttachmentStream hasThumbnailForMimeType:contentType]) {
|
||||
return nil;
|
||||
}
|
||||
TSAttachmentStream *sourceStream = (TSAttachmentStream *)attachmentToQuote;
|
||||
return [sourceStream cloneAsThumbnail];
|
||||
}
|
||||
|
||||
#pragma mark - Attachment (not necessarily with a thumbnail)
|
||||
|
||||
- (nullable OWSAttachmentInfo *)firstAttachmentInfo
|
||||
- (BOOL)hasAttachment
|
||||
{
|
||||
return self.quotedAttachments.firstObject;
|
||||
return (self.quotedAttachment != nil);
|
||||
}
|
||||
|
||||
- (nullable TSAttachment *)fetchThumbnailWithTransaction:(SDSAnyReadTransaction *)transaction
|
||||
{
|
||||
NSString *attachmentId = self.quotedAttachment.rawAttachmentId;
|
||||
TSAttachment *_Nullable attachment = nil;
|
||||
if (attachmentId) {
|
||||
attachment = [TSAttachment anyFetchWithUniqueId:self.quotedAttachment.rawAttachmentId transaction:transaction];
|
||||
}
|
||||
return attachment;
|
||||
}
|
||||
|
||||
- (BOOL)isThumbnailOwned
|
||||
{
|
||||
return (self.quotedAttachment.attachmentType == OWSAttachmentInfoReferenceUntrustedPointer
|
||||
|| self.quotedAttachment.attachmentType == OWSAttachmentInfoReferenceThumbnail);
|
||||
}
|
||||
|
||||
- (nullable NSString *)contentType
|
||||
{
|
||||
OWSAttachmentInfo *firstAttachment = self.firstAttachmentInfo;
|
||||
|
||||
return firstAttachment.contentType;
|
||||
return self.quotedAttachment.contentType;
|
||||
}
|
||||
|
||||
- (nullable NSString *)sourceFilename
|
||||
{
|
||||
OWSAttachmentInfo *firstAttachment = self.firstAttachmentInfo;
|
||||
|
||||
return firstAttachment.sourceFilename;
|
||||
return self.quotedAttachment.sourceFilename;
|
||||
}
|
||||
|
||||
- (nullable NSString *)thumbnailAttachmentPointerId
|
||||
- (nullable NSString *)thumbnailAttachmentId
|
||||
{
|
||||
OWSAttachmentInfo *firstAttachment = self.firstAttachmentInfo;
|
||||
|
||||
return firstAttachment.thumbnailAttachmentPointerId;
|
||||
}
|
||||
|
||||
- (nullable NSString *)thumbnailAttachmentStreamId
|
||||
{
|
||||
OWSAttachmentInfo *firstAttachment = self.firstAttachmentInfo;
|
||||
|
||||
return firstAttachment.thumbnailAttachmentStreamId;
|
||||
return self.quotedAttachment.rawAttachmentId;
|
||||
}
|
||||
|
||||
- (void)setThumbnailAttachmentStream:(TSAttachmentStream *)attachmentStream
|
||||
{
|
||||
OWSAssertDebug([attachmentStream isKindOfClass:[TSAttachmentStream class]]);
|
||||
OWSAssertDebug(self.quotedAttachments.count == 1);
|
||||
|
||||
OWSAttachmentInfo *firstAttachment = self.firstAttachmentInfo;
|
||||
firstAttachment.thumbnailAttachmentStreamId = attachmentStream.uniqueId;
|
||||
}
|
||||
|
||||
- (NSArray<NSString *> *)thumbnailAttachmentStreamIds
|
||||
{
|
||||
NSMutableArray *streamIds = [NSMutableArray new];
|
||||
for (OWSAttachmentInfo *info in self.quotedAttachments) {
|
||||
if (info.thumbnailAttachmentStreamId) {
|
||||
[streamIds addObject:info.thumbnailAttachmentStreamId];
|
||||
}
|
||||
// If we're updating the attachmentId, it should be because we've downloaded the thumbnail from a remote source
|
||||
if (self.quotedAttachment.rawAttachmentId != attachmentStream.uniqueId) {
|
||||
OWSAssertDebug(self.quotedAttachment.attachmentType == OWSAttachmentInfoReferenceUntrustedPointer);
|
||||
}
|
||||
|
||||
return [streamIds copy];
|
||||
self.quotedAttachment.attachmentType = OWSAttachmentInfoReferenceThumbnail;
|
||||
self.quotedAttachment.rawAttachmentId = attachmentStream.uniqueId;
|
||||
}
|
||||
|
||||
// Before sending, persist a thumbnail attachment derived from the quoted attachment
|
||||
- (NSArray<TSAttachmentStream *> *)createThumbnailAttachmentsIfNecessaryWithTransaction:
|
||||
(SDSAnyWriteTransaction *)transaction
|
||||
- (nullable TSAttachmentStream *)createThumbnailIfNecessaryWithTransaction:(SDSAnyWriteTransaction *)transaction
|
||||
{
|
||||
NSMutableArray<TSAttachmentStream *> *thumbnailAttachments = [NSMutableArray new];
|
||||
|
||||
for (OWSAttachmentInfo *info in self.quotedAttachments) {
|
||||
|
||||
OWSAssertDebug(info.attachmentId);
|
||||
TSAttachment *attachment = [TSAttachment anyFetchWithUniqueId:info.attachmentId transaction:transaction];
|
||||
if (![attachment isKindOfClass:[TSAttachmentStream class]]) {
|
||||
continue;
|
||||
}
|
||||
TSAttachmentStream *sourceStream = (TSAttachmentStream *)attachment;
|
||||
|
||||
TSAttachmentStream *_Nullable thumbnailStream = [sourceStream cloneAsThumbnail];
|
||||
if (!thumbnailStream) {
|
||||
continue;
|
||||
}
|
||||
|
||||
[thumbnailStream anyInsertWithTransaction:transaction];
|
||||
info.thumbnailAttachmentStreamId = thumbnailStream.uniqueId;
|
||||
[thumbnailAttachments addObject:thumbnailStream];
|
||||
// We want to clone the existing attachment to a new attachment if necessary. This means:
|
||||
// - Fetching the attachment and making sure it's an attachment stream
|
||||
// - If we already own the attachment, we've already cloned it!
|
||||
// - Otherwise, we should copy the attachment stream to a new attachment
|
||||
// - Updating our state to now point to the new attachment
|
||||
NSString *_Nullable attachmentId = self.quotedAttachment.rawAttachmentId;
|
||||
if (!attachmentId) {
|
||||
return nil;
|
||||
}
|
||||
|
||||
return [thumbnailAttachments copy];
|
||||
TSAttachment *_Nullable attachment = [TSAttachment anyFetchWithUniqueId:attachmentId transaction:transaction];
|
||||
if (![attachment isKindOfClass:[TSAttachmentStream class]]) {
|
||||
// Nothing to clone
|
||||
return nil;
|
||||
}
|
||||
TSAttachmentStream *attachmentStream = (TSAttachmentStream *)attachment;
|
||||
TSAttachmentStream *_Nullable thumbnail = nil;
|
||||
|
||||
// We don't expect to be here in this state. Remote pointers are set via -setThumbnailAttachmentStream:
|
||||
// If we have a stream and we're still in this state, something went wrong.
|
||||
OWSAssertDebug(self.quotedAttachment.attachmentType != OWSAttachmentInfoReferenceUntrustedPointer);
|
||||
if (!self.isThumbnailOwned) {
|
||||
OWSLogInfo(@"Cloning attachment to thumbnail");
|
||||
thumbnail = [attachmentStream cloneAsThumbnail];
|
||||
[thumbnail anyInsertWithTransaction:transaction];
|
||||
self.quotedAttachment.rawAttachmentId = thumbnail.uniqueId;
|
||||
self.quotedAttachment.attachmentType = OWSAttachmentInfoReferenceThumbnail;
|
||||
} else {
|
||||
thumbnail = attachmentStream;
|
||||
}
|
||||
return thumbnail;
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
@ -1479,23 +1479,16 @@ NSString *const MessageSenderSpamChallengeResolvedException = @"SpamChallengeRes
|
||||
[attachmentIds addObjectsFromArray:message.attachmentIds];
|
||||
}
|
||||
|
||||
if (message.quotedMessage) {
|
||||
if (message.quotedMessage.thumbnailAttachmentId) {
|
||||
// We need to update the message record here to reflect the new attachments we may create.
|
||||
[message
|
||||
anyUpdateOutgoingMessageWithTransaction:transaction
|
||||
block:^(TSOutgoingMessage *message) {
|
||||
// Though we currently only ever expect at most one thumbnail, the
|
||||
// proto data model suggests this could change. The logic is intended
|
||||
// to work with multiple, but if we ever actually want to send
|
||||
// multiple, we should do more testing.
|
||||
NSArray<TSAttachmentStream *> *quotedThumbnailAttachments =
|
||||
[message.quotedMessage
|
||||
createThumbnailAttachmentsIfNecessaryWithTransaction:
|
||||
transaction];
|
||||
for (TSAttachmentStream *attachment in quotedThumbnailAttachments) {
|
||||
[attachmentIds addObject:attachment.uniqueId];
|
||||
}
|
||||
}];
|
||||
[message anyUpdateOutgoingMessageWithTransaction:transaction
|
||||
block:^(TSOutgoingMessage *message) {
|
||||
TSAttachmentStream *thumbnail = [message.quotedMessage
|
||||
createThumbnailIfNecessaryWithTransaction:transaction];
|
||||
if (thumbnail.uniqueId) {
|
||||
[attachmentIds addObject:thumbnail.uniqueId];
|
||||
}
|
||||
}];
|
||||
}
|
||||
|
||||
if (message.contactShare.avatarAttachmentId != nil) {
|
||||
|
||||
Loading…
Reference in New Issue
Block a user