session-ios/SignalServiceKit/src/Messages/Interactions/TSOutgoingMessage.m

1175 lines
49 KiB
Mathematica
Raw Normal View History

//
2019-01-14 17:00:24 +01:00
// Copyright (c) 2019 Open Whisper Systems. All rights reserved.
//
2015-12-07 03:31:43 +01:00
#import "TSOutgoingMessage.h"
#import "NSString+SSK.h"
2018-05-01 14:52:59 +02:00
#import "OWSContact.h"
2018-04-10 18:59:26 +02:00
#import "OWSMessageSender.h"
#import "OWSOutgoingSyncMessage.h"
2018-04-23 16:53:43 +02:00
#import "OWSPrimaryStorage.h"
#import "ProfileManagerProtocol.h"
#import "ProtoUtils.h"
#import "SSKEnvironment.h"
#import "SignalRecipient.h"
#import "TSAccountManager.h"
#import "TSAttachmentStream.h"
#import "TSContactThread.h"
#import "TSGroupThread.h"
#import "TSQuotedMessage.h"
2018-09-21 21:41:10 +02:00
#import <SignalCoreKit/NSDate+OWS.h>
#import <SignalServiceKit/SignalServiceKit-Swift.h>
#import <YapDatabase/YapDatabase.h>
#import <YapDatabase/YapDatabaseTransaction.h>
2015-12-07 03:31:43 +01:00
NS_ASSUME_NONNULL_BEGIN
BOOL AreRecipientUpdatesEnabled(void)
2019-02-20 16:11:09 +01:00
{
return NO;
}
NSString *const kTSOutgoingMessageSentRecipientAll = @"kTSOutgoingMessageSentRecipientAll";
2018-04-30 16:21:58 +02:00
NSString *NSStringForOutgoingMessageState(TSOutgoingMessageState value)
{
switch (value) {
case TSOutgoingMessageStateSending:
return @"TSOutgoingMessageStateSending";
case TSOutgoingMessageStateFailed:
return @"TSOutgoingMessageStateFailed";
case TSOutgoingMessageStateSent_OBSOLETE:
return @"TSOutgoingMessageStateSent_OBSOLETE";
case TSOutgoingMessageStateDelivered_OBSOLETE:
return @"TSOutgoingMessageStateDelivered_OBSOLETE";
case TSOutgoingMessageStateSent:
return @"TSOutgoingMessageStateSent";
}
}
NSString *NSStringForOutgoingMessageRecipientState(OWSOutgoingMessageRecipientState value)
{
switch (value) {
case OWSOutgoingMessageRecipientStateFailed:
return @"OWSOutgoingMessageRecipientStateFailed";
case OWSOutgoingMessageRecipientStateSending:
return @"OWSOutgoingMessageRecipientStateSending";
case OWSOutgoingMessageRecipientStateSkipped:
return @"OWSOutgoingMessageRecipientStateSkipped";
case OWSOutgoingMessageRecipientStateSent:
return @"OWSOutgoingMessageRecipientStateSent";
}
}
2018-04-23 16:30:51 +02:00
@interface TSOutgoingMessageRecipientState ()
@property (atomic) OWSOutgoingMessageRecipientState state;
@property (atomic, nullable) NSNumber *deliveryTimestamp;
@property (atomic, nullable) NSNumber *readTimestamp;
@property (atomic) BOOL wasSentByUD;
2018-04-23 16:30:51 +02:00
@end
#pragma mark -
@implementation TSOutgoingMessageRecipientState
@end
#pragma mark -
@interface TSOutgoingMessage ()
@property (atomic) BOOL hasSyncedTranscript;
@property (atomic) NSString *customMessage;
@property (atomic) NSString *mostRecentFailureText;
@property (atomic) BOOL isFromLinkedDevice;
@property (atomic) TSGroupMetaMessage groupMetaMessage;
@property (nonatomic, readonly) TSOutgoingMessageState legacyMessageState;
@property (nonatomic, readonly) BOOL legacyWasDelivered;
@property (nonatomic, readonly) BOOL hasLegacyMessageState;
2018-04-23 16:30:51 +02:00
@property (atomic, nullable) NSDictionary<NSString *, TSOutgoingMessageRecipientState *> *recipientStateMap;
@property (atomic) BOOL isCalculatingPoW;
@end
#pragma mark -
2015-12-07 03:31:43 +01:00
@implementation TSOutgoingMessage
- (nullable instancetype)initWithCoder:(NSCoder *)coder
{
2017-04-13 18:54:03 +02:00
self = [super initWithCoder:coder];
2017-04-13 18:54:03 +02:00
if (self) {
if (!_attachmentFilenameMap) {
_attachmentFilenameMap = [NSMutableDictionary new];
}
2018-04-23 16:30:51 +02:00
if (!self.recipientStateMap) {
[self migrateRecipientStateMapWithCoder:coder];
OWSAssertDebug(self.recipientStateMap);
}
2017-04-13 18:54:03 +02:00
}
2017-04-13 18:54:03 +02:00
return self;
}
2018-04-23 16:30:51 +02:00
- (void)migrateRecipientStateMapWithCoder:(NSCoder *)coder
{
OWSAssertDebug(!self.recipientStateMap);
OWSAssertDebug(coder);
2018-04-23 16:30:51 +02:00
// Determine the "overall message state."
TSOutgoingMessageState oldMessageState = TSOutgoingMessageStateFailed;
NSNumber *_Nullable messageStateValue = [coder decodeObjectForKey:@"messageState"];
if (messageStateValue) {
oldMessageState = (TSOutgoingMessageState)messageStateValue.intValue;
}
_hasLegacyMessageState = YES;
_legacyMessageState = oldMessageState;
2018-04-23 16:30:51 +02:00
OWSOutgoingMessageRecipientState defaultState;
switch (oldMessageState) {
case TSOutgoingMessageStateFailed:
defaultState = OWSOutgoingMessageRecipientStateFailed;
break;
case TSOutgoingMessageStateSending:
defaultState = OWSOutgoingMessageRecipientStateSending;
break;
case TSOutgoingMessageStateSent:
case TSOutgoingMessageStateSent_OBSOLETE:
case TSOutgoingMessageStateDelivered_OBSOLETE:
// Convert legacy values.
defaultState = OWSOutgoingMessageRecipientStateSent;
break;
}
// Try to leverage the "per-recipient state."
NSDictionary<NSString *, NSNumber *> *_Nullable recipientDeliveryMap =
[coder decodeObjectForKey:@"recipientDeliveryMap"];
NSDictionary<NSString *, NSNumber *> *_Nullable recipientReadMap = [coder decodeObjectForKey:@"recipientReadMap"];
NSArray<NSString *> *_Nullable sentRecipients = [coder decodeObjectForKey:@"sentRecipients"];
NSMutableDictionary<NSString *, TSOutgoingMessageRecipientState *> *recipientStateMap = [NSMutableDictionary new];
__block BOOL isGroupThread = NO;
2018-04-23 16:30:51 +02:00
// Our default recipient list is the current thread members.
2018-04-23 16:53:43 +02:00
__block NSArray<NSString *> *recipientIds = @[];
// To avoid deadlock while migrating these records, we use a dedicated
// migration connection. For legacy records (created more than ~9 months
// before the migration), we need to infer the recipient list for this
// message from the current thread membership. This inference isn't
// always accurate, so not using the same connection for both reads is
// acceptable.
[TSOutgoingMessage.dbMigrationConnection readWithBlock:^(YapDatabaseReadTransaction *transaction) {
TSThread *thread = [self threadWithTransaction:transaction];
recipientIds = [thread recipientIdentifiers];
isGroupThread = [thread isGroupThread];
2018-04-23 16:53:43 +02:00
}];
NSNumber *_Nullable wasDelivered = [coder decodeObjectForKey:@"wasDelivered"];
_legacyWasDelivered = wasDelivered && wasDelivered.boolValue;
BOOL wasDeliveredToContact = NO;
if (isGroupThread) {
2018-04-23 16:30:51 +02:00
// If we have a `sentRecipients` list, prefer that as it is more accurate.
if (sentRecipients) {
recipientIds = sentRecipients;
}
} else {
// Special-case messages in contact threads; if "was delivered", we know
// it was delivered to the contact.
wasDeliveredToContact = _legacyWasDelivered;
2018-04-23 16:30:51 +02:00
}
2018-04-23 16:30:51 +02:00
NSString *_Nullable singleGroupRecipient = [coder decodeObjectForKey:@"singleGroupRecipient"];
if (singleGroupRecipient) {
OWSFailDebug(@"unexpected single group recipient message.");
2018-04-23 16:30:51 +02:00
// If this is a "single group recipient message", treat it as such.
recipientIds = @[
singleGroupRecipient,
];
}
for (NSString *recipientId in recipientIds) {
TSOutgoingMessageRecipientState *recipientState = [TSOutgoingMessageRecipientState new];
NSNumber *_Nullable readTimestamp = recipientReadMap[recipientId];
NSNumber *_Nullable deliveryTimestamp = recipientDeliveryMap[recipientId];
if (readTimestamp) {
// If we have a read timestamp for this recipient, mark it as read.
recipientState.state = OWSOutgoingMessageRecipientStateSent;
recipientState.readTimestamp = readTimestamp;
// deliveryTimestamp might be nil here.
recipientState.deliveryTimestamp = deliveryTimestamp;
} else if (deliveryTimestamp) {
// If we have a delivery timestamp for this recipient, mark it as delivered.
recipientState.state = OWSOutgoingMessageRecipientStateSent;
recipientState.deliveryTimestamp = deliveryTimestamp;
} else if (wasDeliveredToContact) {
OWSAssertDebug(!isGroupThread);
recipientState.state = OWSOutgoingMessageRecipientStateSent;
// Use message time as an estimate of delivery time.
recipientState.deliveryTimestamp = @(self.timestamp);
2018-04-23 16:30:51 +02:00
} else if ([sentRecipients containsObject:recipientId]) {
// If this recipient is in `sentRecipients`, mark it as sent.
recipientState.state = OWSOutgoingMessageRecipientStateSent;
} else {
// Use the default state for this message.
recipientState.state = defaultState;
}
recipientStateMap[recipientId] = recipientState;
}
self.recipientStateMap = [recipientStateMap copy];
2018-04-23 16:53:43 +02:00
}
2018-04-23 16:30:51 +02:00
2018-04-23 16:53:43 +02:00
+ (YapDatabaseConnection *)dbMigrationConnection
{
2018-10-16 19:00:50 +02:00
return SSKEnvironment.shared.migrationDBConnection;
2018-04-23 16:30:51 +02:00
}
+ (instancetype)outgoingMessageInThread:(nullable TSThread *)thread
messageBody:(nullable NSString *)body
attachmentId:(nullable NSString *)attachmentId
{
2018-04-06 16:59:23 +02:00
return [self outgoingMessageInThread:thread
messageBody:body
attachmentId:attachmentId
expiresInSeconds:0
2019-01-15 21:52:08 +01:00
quotedMessage:nil
linkPreview:nil];
}
+ (instancetype)outgoingMessageInThread:(nullable TSThread *)thread
messageBody:(nullable NSString *)body
attachmentId:(nullable NSString *)attachmentId
expiresInSeconds:(uint32_t)expiresInSeconds
2018-04-06 16:59:23 +02:00
{
return [self outgoingMessageInThread:thread
messageBody:body
attachmentId:attachmentId
expiresInSeconds:expiresInSeconds
2019-01-15 21:52:08 +01:00
quotedMessage:nil
linkPreview:nil];
2018-04-06 16:59:23 +02:00
}
+ (instancetype)outgoingMessageInThread:(nullable TSThread *)thread
messageBody:(nullable NSString *)body
attachmentId:(nullable NSString *)attachmentId
expiresInSeconds:(uint32_t)expiresInSeconds
quotedMessage:(nullable TSQuotedMessage *)quotedMessage
2019-01-15 21:52:08 +01:00
linkPreview:(nullable OWSLinkPreview *)linkPreview
{
NSMutableArray<NSString *> *attachmentIds = [NSMutableArray new];
if (attachmentId) {
[attachmentIds addObject:attachmentId];
}
// MJK TODO remove SenderTimestamp?
return [[TSOutgoingMessage alloc] initOutgoingMessageWithTimestamp:[NSDate ows_millisecondTimeStamp]
inThread:thread
messageBody:body
attachmentIds:attachmentIds
expiresInSeconds:expiresInSeconds
expireStartedAt:0
isVoiceMessage:NO
groupMetaMessage:TSGroupMetaMessageUnspecified
quotedMessage:quotedMessage
2019-01-14 17:00:24 +01:00
contactShare:nil
2019-01-15 21:52:08 +01:00
linkPreview:linkPreview];
}
+ (instancetype)outgoingMessageInThread:(nullable TSThread *)thread
groupMetaMessage:(TSGroupMetaMessage)groupMetaMessage
expiresInSeconds:(uint32_t)expiresInSeconds;
{
// MJK TODO remove SenderTimestamp?
return [[TSOutgoingMessage alloc] initOutgoingMessageWithTimestamp:[NSDate ows_millisecondTimeStamp]
inThread:thread
messageBody:nil
attachmentIds:[NSMutableArray new]
expiresInSeconds:expiresInSeconds
expireStartedAt:0
isVoiceMessage:NO
groupMetaMessage:groupMetaMessage
quotedMessage:nil
2019-01-14 17:00:24 +01:00
contactShare:nil
linkPreview:nil];
}
- (instancetype)initOutgoingMessageWithTimestamp:(uint64_t)timestamp
inThread:(nullable TSThread *)thread
messageBody:(nullable NSString *)body
attachmentIds:(NSMutableArray<NSString *> *)attachmentIds
expiresInSeconds:(uint32_t)expiresInSeconds
expireStartedAt:(uint64_t)expireStartedAt
isVoiceMessage:(BOOL)isVoiceMessage
groupMetaMessage:(TSGroupMetaMessage)groupMetaMessage
quotedMessage:(nullable TSQuotedMessage *)quotedMessage
contactShare:(nullable OWSContact *)contactShare
2019-01-14 17:00:24 +01:00
linkPreview:(nullable OWSLinkPreview *)linkPreview
{
self = [super initMessageWithTimestamp:timestamp
inThread:thread
messageBody:body
attachmentIds:attachmentIds
expiresInSeconds:expiresInSeconds
expireStartedAt:expireStartedAt
quotedMessage:quotedMessage
2019-01-14 17:00:24 +01:00
contactShare:contactShare
linkPreview:linkPreview];
if (!self) {
return self;
}
_hasSyncedTranscript = NO;
_isCalculatingPoW = NO;
if ([thread isKindOfClass:TSGroupThread.class]) {
// Unless specified, we assume group messages are "Delivery" i.e. normal messages.
2018-08-31 18:43:05 +02:00
if (groupMetaMessage == TSGroupMetaMessageUnspecified) {
_groupMetaMessage = TSGroupMetaMessageDeliver;
} else {
_groupMetaMessage = groupMetaMessage;
}
} else {
OWSAssertDebug(groupMetaMessage == TSGroupMetaMessageUnspecified);
// Specifying a group meta message only makes sense for Group threads
2018-08-31 18:43:05 +02:00
_groupMetaMessage = TSGroupMetaMessageUnspecified;
}
_isVoiceMessage = isVoiceMessage;
2017-04-13 18:54:03 +02:00
_attachmentFilenameMap = [NSMutableDictionary new];
2015-12-07 03:31:43 +01:00
2018-04-24 16:36:17 +02:00
// New outgoing messages should immediately determine their
// recipient list from current thread state.
2018-04-23 16:30:51 +02:00
NSMutableDictionary<NSString *, TSOutgoingMessageRecipientState *> *recipientStateMap = [NSMutableDictionary new];
NSArray<NSString *> *recipientIds;
if ([self isKindOfClass:[OWSOutgoingSyncMessage class]]) {
NSString *_Nullable localNumber = [TSAccountManager localNumber];
OWSAssertDebug(localNumber);
recipientIds = @[
localNumber,
];
} else {
recipientIds = [thread recipientIdentifiers];
}
2018-04-23 16:30:51 +02:00
for (NSString *recipientId in recipientIds) {
TSOutgoingMessageRecipientState *recipientState = [TSOutgoingMessageRecipientState new];
recipientState.state = OWSOutgoingMessageRecipientStateSending;
recipientStateMap[recipientId] = recipientState;
}
self.recipientStateMap = [recipientStateMap copy];
2015-12-07 03:31:43 +01:00
return self;
}
2018-07-30 16:22:45 +02:00
- (void)dealloc
{
2018-08-01 15:47:54 +02:00
[self removeTemporaryAttachments];
2018-07-30 16:22:45 +02:00
}
// Each message has the responsibility for eagerly cleaning up its attachments.
// Normally this is done in [TSMessage removeWithTransaction], but that doesn't
// apply for "transient", unsaved messages (i.e. shouldBeSaved == NO). These
// messages should clean up their attachments upon deallocation.
2018-08-01 15:47:54 +02:00
- (void)removeTemporaryAttachments
2018-07-30 16:22:45 +02:00
{
if (self.shouldBeSaved) {
2018-08-01 15:47:54 +02:00
// Message is not transient; no need to clean up attachments.
2018-07-30 16:22:45 +02:00
return;
}
NSArray<NSString *> *_Nullable attachmentIds = self.attachmentIds;
if (attachmentIds.count < 1) {
return;
}
[self.dbReadWriteConnection asyncReadWriteWithBlock:^(YapDatabaseReadWriteTransaction *transaction) {
for (NSString *attachmentId in attachmentIds) {
// We need to fetch each attachment, since [TSAttachment removeWithTransaction:] does important work.
TSAttachment *_Nullable attachment =
[TSAttachment fetchObjectWithUniqueID:attachmentId transaction:transaction];
if (!attachment) {
OWSLogError(@"Couldn't load interaction's attachment for deletion.");
2018-07-30 16:22:45 +02:00
continue;
}
[attachment removeWithTransaction:transaction];
};
}];
}
#pragma mark -
2018-04-23 16:30:51 +02:00
- (TSOutgoingMessageState)messageState
{
TSOutgoingMessageState newMessageState =
[TSOutgoingMessage messageStateForRecipientStates:self.recipientStateMap.allValues];
if (self.hasLegacyMessageState) {
if (newMessageState == TSOutgoingMessageStateSent || self.legacyMessageState == TSOutgoingMessageStateSent) {
return TSOutgoingMessageStateSent;
}
}
return newMessageState;
}
- (BOOL)wasDeliveredToAnyRecipient
{
if ([self deliveredRecipientIds].count > 0) {
return YES;
}
return (self.hasLegacyMessageState && self.legacyWasDelivered && self.messageState == TSOutgoingMessageStateSent);
2018-04-23 16:30:51 +02:00
}
- (BOOL)wasSentToAnyRecipient
{
if ([self sentRecipientIds].count > 0) {
return YES;
}
return (self.hasLegacyMessageState && self.messageState == TSOutgoingMessageStateSent);
}
2018-04-23 16:30:51 +02:00
+ (TSOutgoingMessageState)messageStateForRecipientStates:(NSArray<TSOutgoingMessageRecipientState *> *)recipientStates
{
OWSAssertDebug(recipientStates);
2018-04-23 16:30:51 +02:00
// If there are any "sending" recipients, consider this message "sending".
BOOL hasFailed = NO;
for (TSOutgoingMessageRecipientState *recipientState in recipientStates) {
if (recipientState.state == OWSOutgoingMessageRecipientStateSending) {
return TSOutgoingMessageStateSending;
} else if (recipientState.state == OWSOutgoingMessageRecipientStateFailed) {
hasFailed = YES;
}
}
// If there are any "failed" recipients, consider this message "failed".
if (hasFailed) {
return TSOutgoingMessageStateFailed;
}
// Otherwise, consider the message "sent".
//
// NOTE: This includes messages with no recipients.
return TSOutgoingMessageStateSent;
}
- (BOOL)shouldBeSaved
{
2018-08-31 18:43:05 +02:00
if (self.groupMetaMessage == TSGroupMetaMessageDeliver || self.groupMetaMessage == TSGroupMetaMessageUnspecified) {
return YES;
2017-11-17 16:19:52 +01:00
}
return NO;
}
Explain send failures for text and media messages Motivation ---------- We were often swallowing errors or yielding generic errors when it would be better to provide specific errors. We also didn't create an attachment when attachments failed to send, making it impossible to show the user what was happening with an in-progress or failed attachment. Primary Changes --------------- - Funnel all message sending through MessageSender, and remove message sending from MessagesManager. - Record most recent sending error so we can expose it in the UI - Can resend attachments. - Update message status for attachments, just like text messages - Extracted UploadingService from MessagesManager - Saving attachment stream before uploading gives uniform API for send vs. resend - update status for downloading transcript attachments - TSAttachments have a local id, separate from the server allocated id This allows us to save the attachment before the allocation request. Which is is good because: 1. can show feedback to user faster. 2. allows us to show an error when allocation fails. Code Cleanup ------------ - Replaced a lot of global singleton access with injected dependencies to make for easier testing. - Never save group meta messages. Rather than checking before (hopefully) every save, do it in the save method. - Don't use callbacks for sync code. - Handle errors on writing attachment data - Fix old long broken tests that weren't even running. =( - Removed dead code - Use constants vs define - Port flaky travis fixes from Signal-iOS // FREEBIE
2016-10-14 23:00:29 +02:00
- (void)saveWithTransaction:(YapDatabaseReadWriteTransaction *)transaction
{
if (!self.shouldBeSaved) {
// There's no need to save this message, since it's not displayed to the user.
//
// Should we find a need to save this in the future, we need to exclude any non-serializable properties.
2019-01-30 16:36:11 +01:00
OWSLogDebug(@"Skipping save for transient outgoing message.");
return;
}
Explain send failures for text and media messages Motivation ---------- We were often swallowing errors or yielding generic errors when it would be better to provide specific errors. We also didn't create an attachment when attachments failed to send, making it impossible to show the user what was happening with an in-progress or failed attachment. Primary Changes --------------- - Funnel all message sending through MessageSender, and remove message sending from MessagesManager. - Record most recent sending error so we can expose it in the UI - Can resend attachments. - Update message status for attachments, just like text messages - Extracted UploadingService from MessagesManager - Saving attachment stream before uploading gives uniform API for send vs. resend - update status for downloading transcript attachments - TSAttachments have a local id, separate from the server allocated id This allows us to save the attachment before the allocation request. Which is is good because: 1. can show feedback to user faster. 2. allows us to show an error when allocation fails. Code Cleanup ------------ - Replaced a lot of global singleton access with injected dependencies to make for easier testing. - Never save group meta messages. Rather than checking before (hopefully) every save, do it in the save method. - Don't use callbacks for sync code. - Handle errors on writing attachment data - Fix old long broken tests that weren't even running. =( - Removed dead code - Use constants vs define - Port flaky travis fixes from Signal-iOS // FREEBIE
2016-10-14 23:00:29 +02:00
[super saveWithTransaction:transaction];
}
2018-05-03 01:15:26 +02:00
- (BOOL)shouldStartExpireTimerWithTransaction:(YapDatabaseReadTransaction *)transaction
{
2018-04-23 16:30:51 +02:00
// It's not clear if we should wait until _all_ recipients have reached "sent or later"
// (which could never occur if one group member is unregistered) or only wait until
// the first recipient has reached "sent or later" (which could cause partially delivered
// messages to expire). For now, we'll do the latter.
//
// TODO: Revisit this decision.
if (!self.isExpiringMessage) {
return NO;
} else if (self.messageState == TSOutgoingMessageStateSent) {
return YES;
2018-04-23 16:30:51 +02:00
} else {
if (self.expireStartedAt > 0) {
// Our initial migration to populate the recipient state map was incomplete. It's since been
// addressed, but it's possible there are edge cases where a previously sent message would
// no longer be considered sent.
// So here we take extra care not to stop any expiration that had previously started.
// This can also happen under normal cirumstances with an outgoing group message.
OWSLogWarn(@"expiration previously started");
return YES;
}
return NO;
}
}
- (BOOL)isSilent
{
return NO;
}
- (BOOL)isOnline
{
return NO;
}
2017-10-10 22:13:54 +02:00
- (OWSInteractionType)interactionType
{
return OWSInteractionType_OutgoingMessage;
}
2018-04-23 16:30:51 +02:00
- (NSArray<NSString *> *)recipientIds
{
2018-04-24 22:11:10 +02:00
return self.recipientStateMap.allKeys;
2018-04-23 16:30:51 +02:00
}
- (NSArray<NSString *> *)sendingRecipientIds
{
NSMutableArray<NSString *> *result = [NSMutableArray new];
for (NSString *recipientId in self.recipientStateMap) {
TSOutgoingMessageRecipientState *recipientState = self.recipientStateMap[recipientId];
if (recipientState.state == OWSOutgoingMessageRecipientStateSending) {
[result addObject:recipientId];
}
}
return result;
}
- (NSArray<NSString *> *)sentRecipientIds
{
NSMutableArray<NSString *> *result = [NSMutableArray new];
for (NSString *recipientId in self.recipientStateMap) {
TSOutgoingMessageRecipientState *recipientState = self.recipientStateMap[recipientId];
if (recipientState.state == OWSOutgoingMessageRecipientStateSent) {
[result addObject:recipientId];
}
}
return result;
}
2018-04-23 16:30:51 +02:00
- (NSArray<NSString *> *)deliveredRecipientIds
{
NSMutableArray<NSString *> *result = [NSMutableArray new];
for (NSString *recipientId in self.recipientStateMap) {
TSOutgoingMessageRecipientState *recipientState = self.recipientStateMap[recipientId];
if (recipientState.deliveryTimestamp != nil) {
[result addObject:recipientId];
}
}
return result;
}
- (NSArray<NSString *> *)readRecipientIds
{
NSMutableArray<NSString *> *result = [NSMutableArray new];
for (NSString *recipientId in self.recipientStateMap) {
TSOutgoingMessageRecipientState *recipientState = self.recipientStateMap[recipientId];
if (recipientState.readTimestamp != nil) {
[result addObject:recipientId];
}
}
return result;
}
- (NSUInteger)sentRecipientsCount
{
return [self.recipientStateMap.allValues
filteredArrayUsingPredicate:[NSPredicate
predicateWithBlock:^BOOL(TSOutgoingMessageRecipientState *recipientState,
NSDictionary<NSString *, id> *_Nullable bindings) {
return recipientState.state == OWSOutgoingMessageRecipientStateSent;
}]]
.count;
}
- (nullable TSOutgoingMessageRecipientState *)recipientStateForRecipientId:(NSString *)recipientId
{
OWSAssertDebug(recipientId.length > 0);
2018-04-23 16:30:51 +02:00
TSOutgoingMessageRecipientState *_Nullable result = self.recipientStateMap[recipientId];
OWSAssertDebug(result);
2018-04-25 16:17:04 +02:00
return [result copy];
2018-04-23 16:30:51 +02:00
}
2017-11-15 19:15:48 +01:00
#pragma mark - Update With... Methods
- (void)updateWithSendingError:(NSError *)error transaction:(YapDatabaseReadWriteTransaction *)transaction
{
OWSAssertDebug(error);
[self applyChangeToSelfAndLatestCopy:transaction
changeBlock:^(TSOutgoingMessage *message) {
// Mark any "sending" recipients as "failed."
for (TSOutgoingMessageRecipientState *recipientState in message.recipientStateMap
.allValues) {
if (recipientState.state == OWSOutgoingMessageRecipientStateSending) {
recipientState.state = OWSOutgoingMessageRecipientStateFailed;
2018-04-23 16:30:51 +02:00
}
}
[message setMostRecentFailureText:error.localizedDescription];
[message setIsCalculatingPoW:NO];
}];
}
2018-04-23 16:30:51 +02:00
- (void)updateWithAllSendingRecipientsMarkedAsFailedWithTansaction:(YapDatabaseReadWriteTransaction *)transaction
{
OWSAssertDebug(transaction);
2018-04-23 16:30:51 +02:00
[self applyChangeToSelfAndLatestCopy:transaction
changeBlock:^(TSOutgoingMessage *message) {
// Mark any "sending" recipients as "failed."
for (TSOutgoingMessageRecipientState *recipientState in message.recipientStateMap
.allValues) {
if (recipientState.state == OWSOutgoingMessageRecipientStateSending) {
recipientState.state = OWSOutgoingMessageRecipientStateFailed;
}
}
[message setIsCalculatingPoW:NO];
2018-04-23 16:30:51 +02:00
}];
}
2018-04-23 16:30:51 +02:00
- (void)updateWithMarkingAllUnsentRecipientsAsSendingWithTransaction:(YapDatabaseReadWriteTransaction *)transaction
{
OWSAssertDebug(transaction);
2017-11-15 19:15:48 +01:00
[self applyChangeToSelfAndLatestCopy:transaction
changeBlock:^(TSOutgoingMessage *message) {
2018-04-23 16:30:51 +02:00
// Mark any "sending" recipients as "failed."
for (TSOutgoingMessageRecipientState *recipientState in message.recipientStateMap
.allValues) {
if (recipientState.state == OWSOutgoingMessageRecipientStateFailed) {
recipientState.state = OWSOutgoingMessageRecipientStateSending;
}
}
2017-11-15 19:15:48 +01:00
}];
}
- (void)updateWithHasSyncedTranscript:(BOOL)hasSyncedTranscript
2017-11-14 19:41:01 +01:00
transaction:(YapDatabaseReadWriteTransaction *)transaction
{
2017-11-15 19:15:48 +01:00
[self applyChangeToSelfAndLatestCopy:transaction
changeBlock:^(TSOutgoingMessage *message) {
[message setHasSyncedTranscript:hasSyncedTranscript];
}];
}
- (void)updateWithCustomMessage:(NSString *)customMessage transaction:(YapDatabaseReadWriteTransaction *)transaction
{
OWSAssertDebug(customMessage);
OWSAssertDebug(transaction);
2017-11-15 19:15:48 +01:00
[self applyChangeToSelfAndLatestCopy:transaction
changeBlock:^(TSOutgoingMessage *message) {
[message setCustomMessage:customMessage];
}];
}
- (void)updateWithCustomMessage:(NSString *)customMessage
{
[self.dbReadWriteConnection readWriteWithBlock:^(YapDatabaseReadWriteTransaction *transaction) {
[self updateWithCustomMessage:customMessage transaction:transaction];
}];
}
- (void)saveIsCalculatingProofOfWork:(BOOL)isCalculatingPoW withTransaction:(YapDatabaseReadWriteTransaction *)transaction
{
OWSAssertDebug(transaction);
[self applyChangeToSelfAndLatestCopy:transaction changeBlock:^(TSOutgoingMessage *message) {
[message setIsCalculatingPoW:isCalculatingPoW];
}];
}
- (void)updateWithSentRecipient:(NSString *)recipientId
wasSentByUD:(BOOL)wasSentByUD
transaction:(YapDatabaseReadWriteTransaction *)transaction {
OWSAssertDebug(recipientId.length > 0);
OWSAssertDebug(transaction);
2017-11-15 19:15:48 +01:00
[self applyChangeToSelfAndLatestCopy:transaction
changeBlock:^(TSOutgoingMessage *message) {
2018-04-23 16:30:51 +02:00
TSOutgoingMessageRecipientState *_Nullable recipientState
= message.recipientStateMap[recipientId];
if (!recipientState) {
OWSFailDebug(@"Missing recipient state for recipient: %@", recipientId);
2018-04-23 16:30:51 +02:00
return;
2017-11-15 19:15:48 +01:00
}
2018-04-23 16:30:51 +02:00
recipientState.state = OWSOutgoingMessageRecipientStateSent;
2018-10-10 20:34:14 +02:00
recipientState.wasSentByUD = wasSentByUD;
[message setIsCalculatingPoW:NO];
2017-11-15 19:15:48 +01:00
}];
}
2018-04-23 16:30:51 +02:00
- (void)updateWithSkippedRecipient:(NSString *)recipientId transaction:(YapDatabaseReadWriteTransaction *)transaction
{
OWSAssertDebug(recipientId.length > 0);
OWSAssertDebug(transaction);
2017-11-15 19:15:48 +01:00
[self applyChangeToSelfAndLatestCopy:transaction
changeBlock:^(TSOutgoingMessage *message) {
2018-04-23 16:30:51 +02:00
TSOutgoingMessageRecipientState *_Nullable recipientState
= message.recipientStateMap[recipientId];
if (!recipientState) {
OWSFailDebug(@"Missing recipient state for recipient: %@", recipientId);
2018-04-23 16:30:51 +02:00
return;
}
recipientState.state = OWSOutgoingMessageRecipientStateSkipped;
[message setIsCalculatingPoW:NO];
2017-11-15 19:15:48 +01:00
}];
}
2018-04-23 16:30:51 +02:00
- (void)updateWithDeliveredRecipient:(NSString *)recipientId
deliveryTimestamp:(NSNumber *_Nullable)deliveryTimestamp
transaction:(YapDatabaseReadWriteTransaction *)transaction
{
OWSAssertDebug(recipientId.length > 0);
OWSAssertDebug(transaction);
// If delivery notification doesn't include timestamp, use "now" as an estimate.
if (!deliveryTimestamp) {
deliveryTimestamp = @([NSDate ows_millisecondTimeStamp]);
}
2017-11-15 19:15:48 +01:00
[self applyChangeToSelfAndLatestCopy:transaction
changeBlock:^(TSOutgoingMessage *message) {
2018-04-23 16:30:51 +02:00
TSOutgoingMessageRecipientState *_Nullable recipientState
= message.recipientStateMap[recipientId];
if (!recipientState) {
OWSFailDebug(@"Missing recipient state for delivered recipient: %@", recipientId);
2018-04-23 16:30:51 +02:00
return;
}
2018-04-24 22:38:35 +02:00
if (recipientState.state != OWSOutgoingMessageRecipientStateSent) {
OWSLogWarn(@"marking unsent message as delivered.");
2018-04-24 22:38:35 +02:00
}
2018-04-23 16:30:51 +02:00
recipientState.state = OWSOutgoingMessageRecipientStateSent;
recipientState.deliveryTimestamp = deliveryTimestamp;
[message setIsCalculatingPoW:NO];
2017-11-15 19:15:48 +01:00
}];
}
2018-04-23 16:30:51 +02:00
- (void)updateWithReadRecipientId:(NSString *)recipientId
readTimestamp:(uint64_t)readTimestamp
transaction:(YapDatabaseReadWriteTransaction *)transaction
2017-04-17 22:45:22 +02:00
{
OWSAssertDebug(recipientId.length > 0);
OWSAssertDebug(transaction);
2017-04-17 22:45:22 +02:00
2018-04-23 16:30:51 +02:00
[self applyChangeToSelfAndLatestCopy:transaction
changeBlock:^(TSOutgoingMessage *message) {
TSOutgoingMessageRecipientState *_Nullable recipientState
= message.recipientStateMap[recipientId];
if (!recipientState) {
OWSFailDebug(@"Missing recipient state for delivered recipient: %@", recipientId);
2018-04-23 16:30:51 +02:00
return;
}
2018-04-24 22:38:35 +02:00
if (recipientState.state != OWSOutgoingMessageRecipientStateSent) {
OWSLogWarn(@"marking unsent message as delivered.");
2018-04-24 22:38:35 +02:00
}
2018-04-23 16:30:51 +02:00
recipientState.state = OWSOutgoingMessageRecipientStateSent;
recipientState.readTimestamp = @(readTimestamp);
[message setIsCalculatingPoW:NO];
2018-04-23 16:30:51 +02:00
}];
2017-04-17 22:45:22 +02:00
}
2018-10-11 21:20:54 +02:00
- (void)updateWithWasSentFromLinkedDeviceWithUDRecipientIds:(nullable NSArray<NSString *> *)udRecipientIds
nonUdRecipientIds:(nullable NSArray<NSString *> *)nonUdRecipientIds
2019-02-20 15:52:19 +01:00
isSentUpdate:(BOOL)isSentUpdate
transaction:(YapDatabaseReadWriteTransaction *)transaction
{
OWSAssertDebug(transaction);
2018-04-23 16:30:51 +02:00
2018-10-11 21:20:54 +02:00
[self
applyChangeToSelfAndLatestCopy:transaction
changeBlock:^(TSOutgoingMessage *message) {
if (udRecipientIds.count > 0 || nonUdRecipientIds.count > 0) {
// If we have specific recipient info from the transcript,
// build a new recipient state map.
NSMutableDictionary<NSString *, TSOutgoingMessageRecipientState *> *recipientStateMap
= [NSMutableDictionary new];
for (NSString *recipientId in udRecipientIds) {
2018-10-12 19:48:18 +02:00
if (recipientStateMap[recipientId]) {
OWSFailDebug(
@"recipient appears more than once in recipient lists: %@", recipientId);
continue;
}
2018-10-11 21:20:54 +02:00
TSOutgoingMessageRecipientState *recipientState =
[TSOutgoingMessageRecipientState new];
recipientState.state = OWSOutgoingMessageRecipientStateSent;
recipientState.wasSentByUD = YES;
recipientStateMap[recipientId] = recipientState;
}
for (NSString *recipientId in nonUdRecipientIds) {
2018-10-12 19:48:18 +02:00
if (recipientStateMap[recipientId]) {
OWSFailDebug(
@"recipient appears more than once in recipient lists: %@", recipientId);
continue;
}
2018-10-11 21:20:54 +02:00
TSOutgoingMessageRecipientState *recipientState =
[TSOutgoingMessageRecipientState new];
recipientState.state = OWSOutgoingMessageRecipientStateSent;
recipientState.wasSentByUD = NO;
recipientStateMap[recipientId] = recipientState;
}
2019-02-20 15:52:19 +01:00
if (isSentUpdate) {
// If this is a "sent update", make sure that:
//
// a) "Sent updates" should never remove any recipients. We end up with the
// union of the existing and new recipients.
// b) "Sent updates" should never downgrade the "recipient state" for any
// recipients. Prefer existing "recipient state"; "sent updates" only
// add new recipients at the "sent" state.
2019-02-21 21:21:00 +01:00
//
// Therefore we retain all existing entries in the recipient state map.
2019-02-20 15:52:19 +01:00
[recipientStateMap addEntriesFromDictionary:self.recipientStateMap];
}
2018-10-11 21:20:54 +02:00
[message setRecipientStateMap:recipientStateMap];
} else {
2018-10-12 19:48:18 +02:00
// Otherwise assume this is a legacy message before UD was introduced, and mark
// any "sending" recipient as "sent". Note that this will apply to non-legacy
// messages with no recipients.
2018-10-11 21:20:54 +02:00
for (TSOutgoingMessageRecipientState *recipientState in message.recipientStateMap
.allValues) {
if (recipientState.state == OWSOutgoingMessageRecipientStateSending) {
recipientState.state = OWSOutgoingMessageRecipientStateSent;
}
}
}
[message setIsCalculatingPoW:NO];
if (!isSentUpdate) {
[message setIsFromLinkedDevice:YES];
}
2018-10-11 21:20:54 +02:00
}];
}
2018-04-24 22:38:35 +02:00
- (void)updateWithSendingToSingleGroupRecipient:(NSString *)singleGroupRecipient
transaction:(YapDatabaseReadWriteTransaction *)transaction
{
OWSAssertDebug(transaction);
OWSAssertDebug(singleGroupRecipient.length > 0);
2017-11-15 19:15:48 +01:00
[self applyChangeToSelfAndLatestCopy:transaction
changeBlock:^(TSOutgoingMessage *message) {
2018-04-23 16:30:51 +02:00
TSOutgoingMessageRecipientState *recipientState =
[TSOutgoingMessageRecipientState new];
recipientState.state = OWSOutgoingMessageRecipientStateSending;
[message setRecipientStateMap:@{
singleGroupRecipient : recipientState,
}];
2017-11-15 19:15:48 +01:00
}];
}
- (nullable NSNumber *)firstRecipientReadTimestamp
{
NSNumber *result = nil;
2018-04-23 16:30:51 +02:00
for (TSOutgoingMessageRecipientState *recipientState in self.recipientStateMap.allValues) {
if (!recipientState.readTimestamp) {
continue;
}
if (!result || (result.unsignedLongLongValue > recipientState.readTimestamp.unsignedLongLongValue)) {
result = recipientState.readTimestamp;
}
}
return result;
}
2018-04-23 16:30:51 +02:00
- (void)updateWithFakeMessageState:(TSOutgoingMessageState)messageState
transaction:(YapDatabaseReadWriteTransaction *)transaction
{
OWSAssertDebug(transaction);
2018-04-23 16:30:51 +02:00
[self applyChangeToSelfAndLatestCopy:transaction
changeBlock:^(TSOutgoingMessage *message) {
for (TSOutgoingMessageRecipientState *recipientState in message.recipientStateMap
.allValues) {
switch (messageState) {
case TSOutgoingMessageStateSending:
recipientState.state = OWSOutgoingMessageRecipientStateSending;
break;
case TSOutgoingMessageStateFailed:
recipientState.state = OWSOutgoingMessageRecipientStateFailed;
break;
case TSOutgoingMessageStateSent:
recipientState.state = OWSOutgoingMessageRecipientStateSent;
break;
default:
OWSFailDebug(@"unexpected message state.");
2018-04-23 16:30:51 +02:00
break;
}
}
}];
}
2018-10-11 21:20:54 +02:00
#pragma mark -
- (nullable SSKProtoDataMessageBuilder *)dataMessageBuilder
{
TSThread *thread = self.thread;
OWSAssertDebug(thread);
SSKProtoDataMessageBuilder *builder = [SSKProtoDataMessage builder];
[builder setTimestamp:self.timestamp];
2018-04-10 18:59:26 +02:00
if ([self.body lengthOfBytesUsingEncoding:NSUTF8StringEncoding] <= kOversizeTextMessageSizeThreshold) {
[builder setBody:self.body];
} else {
OWSFailDebug(@"message body length too long.");
2018-04-23 16:30:51 +02:00
NSString *truncatedBody = [self.body copy];
2018-04-10 18:59:26 +02:00
while ([truncatedBody lengthOfBytesUsingEncoding:NSUTF8StringEncoding] > kOversizeTextMessageSizeThreshold) {
OWSLogError(@"truncating body which is too long: %lu",
2018-05-07 18:32:31 +02:00
(unsigned long)[truncatedBody lengthOfBytesUsingEncoding:NSUTF8StringEncoding]);
2018-04-10 18:59:26 +02:00
truncatedBody = [truncatedBody substringToIndex:truncatedBody.length / 2];
}
[builder setBody:truncatedBody];
}
[builder setExpireTimer:self.expiresInSeconds];
// Group Messages
2016-08-29 00:31:27 +02:00
BOOL attachmentWasGroupAvatar = NO;
if ([thread isKindOfClass:[TSGroupThread class]]) {
TSGroupThread *gThread = (TSGroupThread *)thread;
SSKProtoGroupContextType groupMessageType;
switch (self.groupMetaMessage) {
2018-08-31 18:43:05 +02:00
case TSGroupMetaMessageQuit:
groupMessageType = SSKProtoGroupContextTypeQuit;
break;
2018-08-31 18:43:05 +02:00
case TSGroupMetaMessageUpdate:
case TSGroupMetaMessageNew:
groupMessageType = SSKProtoGroupContextTypeUpdate;
break;
default:
groupMessageType = SSKProtoGroupContextTypeDeliver;
break;
}
SSKProtoGroupContextBuilder *groupBuilder =
[SSKProtoGroupContext builderWithId:gThread.groupModel.groupId type:groupMessageType];
if (groupMessageType == SSKProtoGroupContextTypeUpdate) {
if (gThread.groupModel.groupImage != nil && self.attachmentIds.count == 1) {
attachmentWasGroupAvatar = YES;
SSKProtoAttachmentPointer *_Nullable attachmentProto =
[TSAttachmentStream buildProtoForAttachmentId:self.attachmentIds.firstObject];
if (!attachmentProto) {
OWSFailDebug(@"could not build protobuf.");
return nil;
}
[groupBuilder setAvatar:attachmentProto];
}
[groupBuilder setMembers:gThread.groupModel.groupMemberIds];
[groupBuilder setName:gThread.groupModel.groupName];
}
NSError *error;
SSKProtoGroupContext *_Nullable groupContextProto = [groupBuilder buildAndReturnError:&error];
if (error || !groupContextProto) {
OWSFailDebug(@"could not build protobuf: %@.", error);
return nil;
}
[builder setGroup:groupContextProto];
}
// Message Attachments
2016-08-29 00:31:27 +02:00
if (!attachmentWasGroupAvatar) {
NSMutableArray *attachments = [NSMutableArray new];
for (NSString *attachmentId in self.attachmentIds) {
SSKProtoAttachmentPointer *_Nullable attachmentProto =
[TSAttachmentStream buildProtoForAttachmentId:attachmentId];
if (!attachmentProto) {
OWSFailDebug(@"could not build protobuf.");
return nil;
}
[attachments addObject:attachmentProto];
}
[builder setAttachments:attachments];
}
2018-04-30 22:42:14 +02:00
// Quoted Reply
SSKProtoDataMessageQuoteBuilder *_Nullable quotedMessageBuilder = self.quotedMessageBuilder;
2018-04-30 22:42:14 +02:00
if (quotedMessageBuilder) {
NSError *error;
SSKProtoDataMessageQuote *_Nullable quoteProto = [quotedMessageBuilder buildAndReturnError:&error];
if (error || !quoteProto) {
OWSFailDebug(@"could not build protobuf: %@.", error);
return nil;
}
[builder setQuote:quoteProto];
2018-04-30 22:42:14 +02:00
}
// Contact Share
if (self.contactShare) {
SSKProtoDataMessageContact *_Nullable contactProto =
[OWSContacts protoForContact:self.contactShare];
if (contactProto) {
[builder addContact:contactProto];
} else {
OWSFailDebug(@"contactProto was unexpectedly nil");
}
2018-04-30 22:42:14 +02:00
}
2019-01-15 21:52:08 +01:00
// Link Preview
if (self.linkPreview) {
SSKProtoDataMessagePreviewBuilder *previewBuilder =
[SSKProtoDataMessagePreview builderWithUrl:self.linkPreview.urlString];
if (self.linkPreview.title.length > 0) {
[previewBuilder setTitle:self.linkPreview.title];
}
if (self.linkPreview.imageAttachmentId) {
SSKProtoAttachmentPointer *_Nullable attachmentProto =
[TSAttachmentStream buildProtoForAttachmentId:self.linkPreview.imageAttachmentId];
if (!attachmentProto) {
OWSFailDebug(@"Could not build link preview image protobuf.");
} else {
[previewBuilder setImage:attachmentProto];
}
}
NSError *error;
SSKProtoDataMessagePreview *_Nullable previewProto = [previewBuilder buildAndReturnError:&error];
if (error || !previewProto) {
OWSFailDebug(@"Could not build link preview protobuf: %@.", error);
} else {
2019-01-17 17:56:21 +01:00
[builder addPreview:previewProto];
2019-01-15 21:52:08 +01:00
}
}
2018-04-30 22:42:14 +02:00
return builder;
}
- (nullable SSKProtoDataMessageQuoteBuilder *)quotedMessageBuilder
2018-04-30 22:42:14 +02:00
{
if (!self.quotedMessage) {
return nil;
}
TSQuotedMessage *quotedMessage = self.quotedMessage;
SSKProtoDataMessageQuoteBuilder *quoteBuilder =
[SSKProtoDataMessageQuote builderWithId:quotedMessage.timestamp author:quotedMessage.authorId];
2018-04-30 22:42:14 +02:00
BOOL hasQuotedText = NO;
BOOL hasQuotedAttachment = NO;
if (self.quotedMessage.body.length > 0) {
hasQuotedText = YES;
[quoteBuilder setText:quotedMessage.body];
}
if (quotedMessage.quotedAttachments) {
for (OWSAttachmentInfo *attachment in quotedMessage.quotedAttachments) {
hasQuotedAttachment = YES;
SSKProtoDataMessageQuoteQuotedAttachmentBuilder *quotedAttachmentBuilder =
[SSKProtoDataMessageQuoteQuotedAttachment builder];
2018-04-30 22:42:14 +02:00
quotedAttachmentBuilder.contentType = attachment.contentType;
quotedAttachmentBuilder.fileName = attachment.sourceFilename;
if (attachment.thumbnailAttachmentStreamId) {
quotedAttachmentBuilder.thumbnail =
[TSAttachmentStream buildProtoForAttachmentId:attachment.thumbnailAttachmentStreamId];
}
2018-04-30 22:42:14 +02:00
NSError *error;
SSKProtoDataMessageQuoteQuotedAttachment *_Nullable quotedAttachmentMessage =
[quotedAttachmentBuilder buildAndReturnError:&error];
2018-08-03 20:52:55 +02:00
if (error || !quotedAttachmentMessage) {
OWSFailDebug(@"could not build protobuf: %@", error);
return nil;
}
[quoteBuilder addAttachments:quotedAttachmentMessage];
}
2018-04-30 22:42:14 +02:00
}
if (hasQuotedText || hasQuotedAttachment) {
return quoteBuilder;
} else {
OWSFailDebug(@"Invalid quoted message data.");
2018-04-30 22:42:14 +02:00
return nil;
}
}
// recipientId is nil when building "sent" sync messages for messages sent to groups.
- (nullable SSKProtoDataMessage *)buildDataMessage:(NSString *_Nullable)recipientId
{
OWSAssertDebug(self.thread);
SSKProtoDataMessageBuilder *_Nullable builder = [self dataMessageBuilder];
if (!builder) {
OWSFailDebug(@"could not build protobuf.");
return nil;
}
[ProtoUtils addLocalProfileKeyIfNecessary:self.thread recipientId:recipientId dataMessageBuilder:builder];
2018-05-15 22:10:42 +02:00
id<ProfileManagerProtocol> profileManager = SSKEnvironment.shared.profileManager;
2019-05-28 07:00:18 +02:00
NSString *displayName = profileManager.localProfileName;
if (displayName != nil) {
2019-05-29 01:41:41 +02:00
SSKProtoDataMessageLokiProfileBuilder *profileBuilder = [SSKProtoDataMessageLokiProfile builder];
[profileBuilder setDisplayName:displayName];
2019-06-25 08:17:05 +02:00
SSKProtoDataMessageLokiProfile *profile = [profileBuilder buildAndReturnError:nil];
[builder setProfile:profile];
}
NSError *error;
SSKProtoDataMessage *_Nullable dataProto = [builder buildAndReturnError:&error];
if (error || !dataProto) {
OWSFailDebug(@"could not build protobuf: %@", error);
return nil;
}
return dataProto;
}
- (SSKProtoContentBuilder *)contentBuilder:(SignalRecipient *)recipient
{
2019-05-10 05:38:00 +02:00
return SSKProtoContent.builder;
}
- (nullable NSData *)buildPlainTextData:(SignalRecipient *)recipient
{
NSError *error;
SSKProtoDataMessage *_Nullable dataMessage = [self buildDataMessage:recipient.recipientId];
2018-08-03 20:52:55 +02:00
if (error || !dataMessage) {
OWSFailDebug(@"could not build protobuf: %@", error);
return nil;
}
SSKProtoContentBuilder *contentBuilder = [self contentBuilder:recipient];
[contentBuilder setDataMessage:dataMessage];
2018-08-03 20:52:55 +02:00
NSData *_Nullable contentData = [contentBuilder buildSerializedDataAndReturnError:&error];
if (error || !contentData) {
OWSFailDebug(@"could not serialize protobuf: %@", error);
return nil;
}
return contentData;
}
- (BOOL)shouldSyncTranscript
{
2019-02-20 16:11:09 +01:00
return YES;
}
2018-04-30 16:21:58 +02:00
- (NSString *)statusDescription
{
NSMutableString *result = [NSMutableString new];
[result appendFormat:@"[status: %@", NSStringForOutgoingMessageState(self.messageState)];
for (NSString *recipientId in self.recipientStateMap) {
TSOutgoingMessageRecipientState *recipientState = self.recipientStateMap[recipientId];
[result appendFormat:@", %@: %@", recipientId, NSStringForOutgoingMessageRecipientState(recipientState.state)];
}
[result appendString:@"]"];
return [result copy];
}
- (uint)ttl {
2019-05-22 06:29:59 +02:00
// Time to live for all messages (except friend request messages) should be 1 day
// TODO: Change this to return a value that the user chose
2019-05-23 07:50:37 +02:00
return 1 * kDayInMs;
}
2015-12-07 03:31:43 +01:00
@end
NS_ASSUME_NONNULL_END