|
|
|
@ -21,28 +21,31 @@ NS_ASSUME_NONNULL_BEGIN
|
|
|
|
|
|
|
|
|
|
NSString *const kTSOutgoingMessageSentRecipientAll = @"kTSOutgoingMessageSentRecipientAll";
|
|
|
|
|
|
|
|
|
|
@interface TSOutgoingMessageRecipientState ()
|
|
|
|
|
|
|
|
|
|
@property (atomic) OWSOutgoingMessageRecipientState state;
|
|
|
|
|
@property (atomic, nullable) NSNumber *deliveryTimestamp;
|
|
|
|
|
@property (atomic, nullable) NSNumber *readTimestamp;
|
|
|
|
|
|
|
|
|
|
@end
|
|
|
|
|
|
|
|
|
|
#pragma mark -
|
|
|
|
|
|
|
|
|
|
@implementation TSOutgoingMessageRecipientState
|
|
|
|
|
|
|
|
|
|
@end
|
|
|
|
|
|
|
|
|
|
#pragma mark -
|
|
|
|
|
|
|
|
|
|
@interface TSOutgoingMessage ()
|
|
|
|
|
|
|
|
|
|
@property (atomic) TSOutgoingMessageState messageState;
|
|
|
|
|
@property (atomic) BOOL hasSyncedTranscript;
|
|
|
|
|
@property (atomic) NSString *customMessage;
|
|
|
|
|
@property (atomic) NSString *mostRecentFailureText;
|
|
|
|
|
@property (atomic) BOOL wasDelivered;
|
|
|
|
|
@property (atomic) NSString *singleGroupRecipient;
|
|
|
|
|
@property (atomic) BOOL isFromLinkedDevice;
|
|
|
|
|
|
|
|
|
|
// For outgoing, non-legacy group messages sent from this client, this
|
|
|
|
|
// contains the list of recipients to whom the message has been sent.
|
|
|
|
|
//
|
|
|
|
|
// This collection can also be tested to avoid repeat delivery to the
|
|
|
|
|
// same recipient.
|
|
|
|
|
@property (atomic) NSArray<NSString *> *sentRecipients;
|
|
|
|
|
|
|
|
|
|
@property (atomic) TSGroupMetaMessage groupMetaMessage;
|
|
|
|
|
|
|
|
|
|
@property (atomic) NSDictionary<NSString *, NSNumber *> *recipientDeliveryMap;
|
|
|
|
|
|
|
|
|
|
@property (atomic) NSDictionary<NSString *, NSNumber *> *recipientReadMap;
|
|
|
|
|
@property (atomic, nullable) NSDictionary<NSString *, TSOutgoingMessageRecipientState *> *recipientStateMap;
|
|
|
|
|
|
|
|
|
|
@end
|
|
|
|
|
|
|
|
|
@ -50,8 +53,6 @@ NSString *const kTSOutgoingMessageSentRecipientAll = @"kTSOutgoingMessageSentRec
|
|
|
|
|
|
|
|
|
|
@implementation TSOutgoingMessage
|
|
|
|
|
|
|
|
|
|
@synthesize sentRecipients = _sentRecipients;
|
|
|
|
|
|
|
|
|
|
- (instancetype)initWithCoder:(NSCoder *)coder
|
|
|
|
|
{
|
|
|
|
|
self = [super initWithCoder:coder];
|
|
|
|
@ -60,22 +61,95 @@ NSString *const kTSOutgoingMessageSentRecipientAll = @"kTSOutgoingMessageSentRec
|
|
|
|
|
if (!_attachmentFilenameMap) {
|
|
|
|
|
_attachmentFilenameMap = [NSMutableDictionary new];
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Migrate message state.
|
|
|
|
|
if (_messageState == TSOutgoingMessageStateSent_OBSOLETE) {
|
|
|
|
|
_messageState = TSOutgoingMessageStateSentToService;
|
|
|
|
|
} else if (_messageState == TSOutgoingMessageStateDelivered_OBSOLETE) {
|
|
|
|
|
_messageState = TSOutgoingMessageStateSentToService;
|
|
|
|
|
_wasDelivered = YES;
|
|
|
|
|
}
|
|
|
|
|
if (!_sentRecipients) {
|
|
|
|
|
_sentRecipients = [NSArray new];
|
|
|
|
|
|
|
|
|
|
if (!self.recipientStateMap) {
|
|
|
|
|
[self migrateRecipientStateMapWithCoder:coder];
|
|
|
|
|
OWSAssert(self.recipientStateMap);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return self;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
- (void)migrateRecipientStateMapWithCoder:(NSCoder *)coder
|
|
|
|
|
{
|
|
|
|
|
OWSAssert(!self.recipientStateMap);
|
|
|
|
|
OWSAssert(coder);
|
|
|
|
|
|
|
|
|
|
// Determine the "overall message state."
|
|
|
|
|
TSOutgoingMessageState oldMessageState = TSOutgoingMessageStateFailed;
|
|
|
|
|
NSNumber *_Nullable messageStateValue = [coder decodeObjectForKey:@"messageState"];
|
|
|
|
|
if (messageStateValue) {
|
|
|
|
|
oldMessageState = (TSOutgoingMessageState)messageStateValue.intValue;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
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];
|
|
|
|
|
// Our default recipient list is the current thread members.
|
|
|
|
|
NSArray<NSString *> *recipientIds = [self.thread recipientIdentifiers];
|
|
|
|
|
if (sentRecipients) {
|
|
|
|
|
// If we have a `sentRecipients` list, prefer that as it is more accurate.
|
|
|
|
|
recipientIds = sentRecipients;
|
|
|
|
|
}
|
|
|
|
|
NSString *_Nullable singleGroupRecipient = [coder decodeObjectForKey:@"singleGroupRecipient"];
|
|
|
|
|
if (singleGroupRecipient) {
|
|
|
|
|
// 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 ([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];
|
|
|
|
|
|
|
|
|
|
[self updateMessageState];
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
+ (instancetype)outgoingMessageInThread:(nullable TSThread *)thread
|
|
|
|
|
messageBody:(nullable NSString *)body
|
|
|
|
|
attachmentId:(nullable NSString *)attachmentId
|
|
|
|
@ -156,8 +230,6 @@ NSString *const kTSOutgoingMessageSentRecipientAll = @"kTSOutgoingMessageSentRec
|
|
|
|
|
return self;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
_messageState = TSOutgoingMessageStateAttemptingOut;
|
|
|
|
|
_sentRecipients = [NSArray new];
|
|
|
|
|
_hasSyncedTranscript = NO;
|
|
|
|
|
|
|
|
|
|
if ([thread isKindOfClass:TSGroupThread.class]) {
|
|
|
|
@ -177,9 +249,55 @@ NSString *const kTSOutgoingMessageSentRecipientAll = @"kTSOutgoingMessageSentRec
|
|
|
|
|
|
|
|
|
|
_attachmentFilenameMap = [NSMutableDictionary new];
|
|
|
|
|
|
|
|
|
|
NSMutableDictionary<NSString *, TSOutgoingMessageRecipientState *> *recipientStateMap = [NSMutableDictionary new];
|
|
|
|
|
NSArray<NSString *> *recipientIds = [self.thread recipientIdentifiers];
|
|
|
|
|
for (NSString *recipientId in recipientIds) {
|
|
|
|
|
TSOutgoingMessageRecipientState *recipientState = [TSOutgoingMessageRecipientState new];
|
|
|
|
|
recipientState.state = OWSOutgoingMessageRecipientStateSending;
|
|
|
|
|
recipientStateMap[recipientId] = recipientState;
|
|
|
|
|
}
|
|
|
|
|
self.recipientStateMap = [recipientStateMap copy];
|
|
|
|
|
|
|
|
|
|
[self updateMessageState];
|
|
|
|
|
|
|
|
|
|
return self;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
- (void)updateMessageState
|
|
|
|
|
{
|
|
|
|
|
// self.messageState = [TSOutgoingMessage messageStateForRecipientStates:self.recipientStateMap.allValues];
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
- (TSOutgoingMessageState)messageState
|
|
|
|
|
{
|
|
|
|
|
return [TSOutgoingMessage messageStateForRecipientStates:self.recipientStateMap.allValues];
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
+ (TSOutgoingMessageState)messageStateForRecipientStates:(NSArray<TSOutgoingMessageRecipientState *> *)recipientStates
|
|
|
|
|
{
|
|
|
|
|
OWSAssert(recipientStates);
|
|
|
|
|
|
|
|
|
|
// 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
|
|
|
|
|
{
|
|
|
|
|
if (self.groupMetaMessage == TSGroupMessageDeliver || self.groupMetaMessage == TSGroupMessageUnspecified) {
|
|
|
|
@ -203,23 +321,39 @@ NSString *const kTSOutgoingMessageSentRecipientAll = @"kTSOutgoingMessageSentRec
|
|
|
|
|
[super saveWithTransaction:transaction];
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
- (nullable NSString *)recipientIdentifier
|
|
|
|
|
- (OWSOutgoingMessageRecipientState)maxMessageState
|
|
|
|
|
{
|
|
|
|
|
OWSOutgoingMessageRecipientState result = OWSOutgoingMessageRecipientStateMin;
|
|
|
|
|
for (TSOutgoingMessageRecipientState *recipientState in self.recipientStateMap.allValues) {
|
|
|
|
|
result = MAX(recipientState.state, result);
|
|
|
|
|
}
|
|
|
|
|
return result;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
- (OWSOutgoingMessageRecipientState)minMessageState
|
|
|
|
|
{
|
|
|
|
|
return self.thread.contactIdentifier;
|
|
|
|
|
OWSOutgoingMessageRecipientState result = OWSOutgoingMessageRecipientStateMax;
|
|
|
|
|
for (TSOutgoingMessageRecipientState *recipientState in self.recipientStateMap.allValues) {
|
|
|
|
|
result = MIN(recipientState.state, result);
|
|
|
|
|
}
|
|
|
|
|
return result;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
- (BOOL)shouldStartExpireTimer:(YapDatabaseReadTransaction *)transaction
|
|
|
|
|
{
|
|
|
|
|
switch (self.messageState) {
|
|
|
|
|
case TSOutgoingMessageStateSentToService:
|
|
|
|
|
return self.isExpiringMessage;
|
|
|
|
|
case TSOutgoingMessageStateAttemptingOut:
|
|
|
|
|
case TSOutgoingMessageStateUnsent:
|
|
|
|
|
return NO;
|
|
|
|
|
case TSOutgoingMessageStateSent_OBSOLETE:
|
|
|
|
|
case TSOutgoingMessageStateDelivered_OBSOLETE:
|
|
|
|
|
OWSFail(@"%@ Obsolete message state.", self.logTag);
|
|
|
|
|
return self.isExpiringMessage;
|
|
|
|
|
// 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.recipientStateMap.count < 1) {
|
|
|
|
|
return YES;
|
|
|
|
|
} else {
|
|
|
|
|
return self.maxMessageState >= OWSOutgoingMessageRecipientStateSent;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
@ -233,6 +367,67 @@ NSString *const kTSOutgoingMessageSentRecipientAll = @"kTSOutgoingMessageSentRec
|
|
|
|
|
return OWSInteractionType_OutgoingMessage;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
- (NSArray<NSString *> *)recipientIds
|
|
|
|
|
{
|
|
|
|
|
return [self.recipientStateMap.allKeys copy];
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
- (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 *> *)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
|
|
|
|
|
{
|
|
|
|
|
OWSAssert(recipientId.length > 0);
|
|
|
|
|
|
|
|
|
|
TSOutgoingMessageRecipientState *_Nullable result = self.recipientStateMap[recipientId];
|
|
|
|
|
OWSAssert(result);
|
|
|
|
|
return result;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#pragma mark - Update With... Methods
|
|
|
|
|
|
|
|
|
|
- (void)updateWithSendingError:(NSError *)error
|
|
|
|
@ -242,27 +437,49 @@ NSString *const kTSOutgoingMessageSentRecipientAll = @"kTSOutgoingMessageSentRec
|
|
|
|
|
[self.dbReadWriteConnection readWriteWithBlock:^(YapDatabaseReadWriteTransaction *transaction) {
|
|
|
|
|
[self applyChangeToSelfAndLatestCopy:transaction
|
|
|
|
|
changeBlock:^(TSOutgoingMessage *message) {
|
|
|
|
|
[message setMessageState:TSOutgoingMessageStateUnsent];
|
|
|
|
|
// Mark any "sending" recipients as "failed."
|
|
|
|
|
for (TSOutgoingMessageRecipientState *recipientState in message.recipientStateMap
|
|
|
|
|
.allValues) {
|
|
|
|
|
if (recipientState.state == OWSOutgoingMessageRecipientStateSending) {
|
|
|
|
|
recipientState.state = OWSOutgoingMessageRecipientStateFailed;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
[message setMostRecentFailureText:error.localizedDescription];
|
|
|
|
|
}];
|
|
|
|
|
}];
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
- (void)updateWithMessageState:(TSOutgoingMessageState)messageState
|
|
|
|
|
- (void)updateWithAllSendingRecipientsMarkedAsFailedWithTansaction:(YapDatabaseReadWriteTransaction *)transaction
|
|
|
|
|
{
|
|
|
|
|
[self.dbReadWriteConnection readWriteWithBlock:^(YapDatabaseReadWriteTransaction *transaction) {
|
|
|
|
|
[self updateWithMessageState:messageState transaction:transaction];
|
|
|
|
|
}];
|
|
|
|
|
OWSAssert(transaction);
|
|
|
|
|
|
|
|
|
|
[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 updateMessageState];
|
|
|
|
|
}];
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
- (void)updateWithMessageState:(TSOutgoingMessageState)messageState
|
|
|
|
|
transaction:(YapDatabaseReadWriteTransaction *)transaction
|
|
|
|
|
- (void)updateWithMarkingAllUnsentRecipientsAsSendingWithTransaction:(YapDatabaseReadWriteTransaction *)transaction
|
|
|
|
|
{
|
|
|
|
|
OWSAssert(transaction);
|
|
|
|
|
|
|
|
|
|
[self applyChangeToSelfAndLatestCopy:transaction
|
|
|
|
|
changeBlock:^(TSOutgoingMessage *message) {
|
|
|
|
|
[message setMessageState:messageState];
|
|
|
|
|
// Mark any "sending" recipients as "failed."
|
|
|
|
|
for (TSOutgoingMessageRecipientState *recipientState in message.recipientStateMap
|
|
|
|
|
.allValues) {
|
|
|
|
|
if (recipientState.state == OWSOutgoingMessageRecipientStateFailed) {
|
|
|
|
|
recipientState.state = OWSOutgoingMessageRecipientStateSending;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
[message updateMessageState];
|
|
|
|
|
}];
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
@ -293,135 +510,167 @@ NSString *const kTSOutgoingMessageSentRecipientAll = @"kTSOutgoingMessageSentRec
|
|
|
|
|
}];
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
- (void)updateWithDeliveredToRecipientId:(NSString *)recipientId
|
|
|
|
|
deliveryTimestamp:(NSNumber *_Nullable)deliveryTimestamp
|
|
|
|
|
transaction:(YapDatabaseReadWriteTransaction *)transaction
|
|
|
|
|
- (void)updateWithSentRecipient:(NSString *)recipientId transaction:(YapDatabaseReadWriteTransaction *)transaction
|
|
|
|
|
{
|
|
|
|
|
OWSAssert(recipientId.length > 0);
|
|
|
|
|
OWSAssert(transaction);
|
|
|
|
|
|
|
|
|
|
// TODO: I suspect we're double-calling this method.
|
|
|
|
|
|
|
|
|
|
[self applyChangeToSelfAndLatestCopy:transaction
|
|
|
|
|
changeBlock:^(TSOutgoingMessage *message) {
|
|
|
|
|
|
|
|
|
|
if (deliveryTimestamp) {
|
|
|
|
|
NSMutableDictionary<NSString *, NSNumber *> *recipientDeliveryMap
|
|
|
|
|
= (message.recipientDeliveryMap ? [message.recipientDeliveryMap mutableCopy]
|
|
|
|
|
: [NSMutableDictionary new]);
|
|
|
|
|
recipientDeliveryMap[recipientId] = deliveryTimestamp;
|
|
|
|
|
message.recipientDeliveryMap = [recipientDeliveryMap copy];
|
|
|
|
|
TSOutgoingMessageRecipientState *_Nullable recipientState
|
|
|
|
|
= message.recipientStateMap[recipientId];
|
|
|
|
|
if (!recipientState) {
|
|
|
|
|
OWSFail(@"%@ Missing recipient state for recipient: %@", self.logTag, recipientId);
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
[message setWasDelivered:YES];
|
|
|
|
|
recipientState.state = OWSOutgoingMessageRecipientStateSent;
|
|
|
|
|
[message updateMessageState];
|
|
|
|
|
}];
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
- (void)updateWithWasSentFromLinkedDeviceWithTransaction:(YapDatabaseReadWriteTransaction *)transaction
|
|
|
|
|
- (void)updateWithSkippedRecipient:(NSString *)recipientId transaction:(YapDatabaseReadWriteTransaction *)transaction
|
|
|
|
|
{
|
|
|
|
|
OWSAssert(recipientId.length > 0);
|
|
|
|
|
OWSAssert(transaction);
|
|
|
|
|
|
|
|
|
|
[self applyChangeToSelfAndLatestCopy:transaction
|
|
|
|
|
changeBlock:^(TSOutgoingMessage *message) {
|
|
|
|
|
[message setMessageState:TSOutgoingMessageStateSentToService];
|
|
|
|
|
[message setWasDelivered:YES];
|
|
|
|
|
[message setIsFromLinkedDevice:YES];
|
|
|
|
|
TSOutgoingMessageRecipientState *_Nullable recipientState
|
|
|
|
|
= message.recipientStateMap[recipientId];
|
|
|
|
|
if (!recipientState) {
|
|
|
|
|
OWSFail(@"%@ Missing recipient state for recipient: %@", self.logTag, recipientId);
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
recipientState.state = OWSOutgoingMessageRecipientStateSkipped;
|
|
|
|
|
[message updateMessageState];
|
|
|
|
|
}];
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
- (void)updateWithSingleGroupRecipient:(NSString *)singleGroupRecipient
|
|
|
|
|
transaction:(YapDatabaseReadWriteTransaction *)transaction
|
|
|
|
|
- (void)updateWithDeliveredRecipient:(NSString *)recipientId
|
|
|
|
|
deliveryTimestamp:(NSNumber *_Nullable)deliveryTimestamp
|
|
|
|
|
transaction:(YapDatabaseReadWriteTransaction *)transaction
|
|
|
|
|
{
|
|
|
|
|
OWSAssert(recipientId.length > 0);
|
|
|
|
|
OWSAssert(transaction);
|
|
|
|
|
OWSAssert(singleGroupRecipient.length > 0);
|
|
|
|
|
|
|
|
|
|
[self applyChangeToSelfAndLatestCopy:transaction
|
|
|
|
|
changeBlock:^(TSOutgoingMessage *message) {
|
|
|
|
|
[message setSingleGroupRecipient:singleGroupRecipient];
|
|
|
|
|
TSOutgoingMessageRecipientState *_Nullable recipientState
|
|
|
|
|
= message.recipientStateMap[recipientId];
|
|
|
|
|
if (!recipientState) {
|
|
|
|
|
OWSFail(@"%@ Missing recipient state for delivered recipient: %@",
|
|
|
|
|
self.logTag,
|
|
|
|
|
recipientId);
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
recipientState.state = OWSOutgoingMessageRecipientStateSent;
|
|
|
|
|
recipientState.deliveryTimestamp = deliveryTimestamp;
|
|
|
|
|
[message updateMessageState];
|
|
|
|
|
}];
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#pragma mark - Sent Recipients
|
|
|
|
|
|
|
|
|
|
- (NSArray<NSString *> *)sentRecipients
|
|
|
|
|
{
|
|
|
|
|
@synchronized(self)
|
|
|
|
|
{
|
|
|
|
|
return _sentRecipients;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
- (void)setSentRecipients:(NSArray<NSString *> *)sentRecipients
|
|
|
|
|
{
|
|
|
|
|
@synchronized(self)
|
|
|
|
|
{
|
|
|
|
|
_sentRecipients = [sentRecipients copy];
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
- (void)addSentRecipient:(NSString *)contactId
|
|
|
|
|
{
|
|
|
|
|
@synchronized(self)
|
|
|
|
|
{
|
|
|
|
|
OWSAssert(_sentRecipients);
|
|
|
|
|
OWSAssert(contactId.length > 0);
|
|
|
|
|
|
|
|
|
|
NSMutableArray *sentRecipients = [_sentRecipients mutableCopy];
|
|
|
|
|
[sentRecipients addObject:contactId];
|
|
|
|
|
_sentRecipients = [sentRecipients copy];
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
- (BOOL)wasSentToRecipient:(NSString *)contactId
|
|
|
|
|
{
|
|
|
|
|
OWSAssert(self.sentRecipients);
|
|
|
|
|
OWSAssert(contactId.length > 0);
|
|
|
|
|
|
|
|
|
|
return [self.sentRecipients containsObject:contactId];
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
- (NSUInteger)sentRecipientsCount
|
|
|
|
|
- (void)updateWithReadRecipientId:(NSString *)recipientId
|
|
|
|
|
readTimestamp:(uint64_t)readTimestamp
|
|
|
|
|
transaction:(YapDatabaseReadWriteTransaction *)transaction
|
|
|
|
|
{
|
|
|
|
|
OWSAssert(self.sentRecipients);
|
|
|
|
|
OWSAssert(recipientId.length > 0);
|
|
|
|
|
OWSAssert(transaction);
|
|
|
|
|
|
|
|
|
|
return self.sentRecipients.count;
|
|
|
|
|
[self applyChangeToSelfAndLatestCopy:transaction
|
|
|
|
|
changeBlock:^(TSOutgoingMessage *message) {
|
|
|
|
|
TSOutgoingMessageRecipientState *_Nullable recipientState
|
|
|
|
|
= message.recipientStateMap[recipientId];
|
|
|
|
|
if (!recipientState) {
|
|
|
|
|
OWSFail(@"%@ Missing recipient state for delivered recipient: %@",
|
|
|
|
|
self.logTag,
|
|
|
|
|
recipientId);
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
recipientState.state = OWSOutgoingMessageRecipientStateSent;
|
|
|
|
|
recipientState.readTimestamp = @(readTimestamp);
|
|
|
|
|
[message updateMessageState];
|
|
|
|
|
}];
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
- (void)updateWithSentRecipient:(NSString *)contactId transaction:(YapDatabaseReadWriteTransaction *)transaction
|
|
|
|
|
- (void)updateWithWasSentFromLinkedDeviceWithTransaction:(YapDatabaseReadWriteTransaction *)transaction
|
|
|
|
|
{
|
|
|
|
|
OWSAssert(transaction);
|
|
|
|
|
|
|
|
|
|
[self applyChangeToSelfAndLatestCopy:transaction
|
|
|
|
|
changeBlock:^(TSOutgoingMessage *message) {
|
|
|
|
|
[message addSentRecipient:contactId];
|
|
|
|
|
// Mark any "sending" recipients as "sent."
|
|
|
|
|
for (TSOutgoingMessageRecipientState *recipientState in message.recipientStateMap
|
|
|
|
|
.allValues) {
|
|
|
|
|
if (recipientState.state == OWSOutgoingMessageRecipientStateSending) {
|
|
|
|
|
recipientState.state = OWSOutgoingMessageRecipientStateSent;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
[message setIsFromLinkedDevice:YES];
|
|
|
|
|
[message updateMessageState];
|
|
|
|
|
}];
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
- (void)updateWithReadRecipientId:(NSString *)recipientId
|
|
|
|
|
readTimestamp:(uint64_t)readTimestamp
|
|
|
|
|
transaction:(YapDatabaseReadWriteTransaction *)transaction
|
|
|
|
|
- (void)updateWithSingleGroupRecipient:(NSString *)singleGroupRecipient
|
|
|
|
|
transaction:(YapDatabaseReadWriteTransaction *)transaction
|
|
|
|
|
{
|
|
|
|
|
OWSAssert(recipientId.length > 0);
|
|
|
|
|
OWSAssert(transaction);
|
|
|
|
|
OWSAssert(singleGroupRecipient.length > 0);
|
|
|
|
|
|
|
|
|
|
[self applyChangeToSelfAndLatestCopy:transaction
|
|
|
|
|
changeBlock:^(TSOutgoingMessage *message) {
|
|
|
|
|
NSMutableDictionary<NSString *, NSNumber *> *recipientReadMap
|
|
|
|
|
= (message.recipientReadMap ? [message.recipientReadMap mutableCopy]
|
|
|
|
|
: [NSMutableDictionary new]);
|
|
|
|
|
recipientReadMap[recipientId] = @(readTimestamp);
|
|
|
|
|
message.recipientReadMap = [recipientReadMap copy];
|
|
|
|
|
TSOutgoingMessageRecipientState *recipientState =
|
|
|
|
|
[TSOutgoingMessageRecipientState new];
|
|
|
|
|
recipientState.state = OWSOutgoingMessageRecipientStateSending;
|
|
|
|
|
[message setRecipientStateMap:@{
|
|
|
|
|
singleGroupRecipient : recipientState,
|
|
|
|
|
}];
|
|
|
|
|
[message updateMessageState];
|
|
|
|
|
}];
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
- (nullable NSNumber *)firstRecipientReadTimestamp
|
|
|
|
|
{
|
|
|
|
|
NSNumber *result = nil;
|
|
|
|
|
for (NSNumber *timestamp in self.recipientReadMap.allValues) {
|
|
|
|
|
if (!result || (result.unsignedLongLongValue > timestamp.unsignedLongLongValue)) {
|
|
|
|
|
result = timestamp;
|
|
|
|
|
for (TSOutgoingMessageRecipientState *recipientState in self.recipientStateMap.allValues) {
|
|
|
|
|
if (!recipientState.readTimestamp) {
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
if (!result || (result.unsignedLongLongValue > recipientState.readTimestamp.unsignedLongLongValue)) {
|
|
|
|
|
result = recipientState.readTimestamp;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return result;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
- (void)updateWithFakeMessageState:(TSOutgoingMessageState)messageState
|
|
|
|
|
transaction:(YapDatabaseReadWriteTransaction *)transaction
|
|
|
|
|
{
|
|
|
|
|
OWSAssert(transaction);
|
|
|
|
|
|
|
|
|
|
[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:
|
|
|
|
|
OWSFail(@"%@ unexpected message state.", self.logTag);
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
[message updateMessageState];
|
|
|
|
|
}];
|
|
|
|
|
}
|
|
|
|
|
#pragma mark -
|
|
|
|
|
|
|
|
|
|
- (OWSSignalServiceProtosDataMessageBuilder *)dataMessageBuilder
|
|
|
|
@ -437,7 +686,7 @@ NSString *const kTSOutgoingMessageSentRecipientAll = @"kTSOutgoingMessageSentRec
|
|
|
|
|
[builder setBody:self.body];
|
|
|
|
|
} else {
|
|
|
|
|
OWSFail(@"%@ message body length too long.", self.logTag);
|
|
|
|
|
NSString *truncatedBody = self.body;
|
|
|
|
|
NSString *truncatedBody = [self.body copy];
|
|
|
|
|
while ([truncatedBody lengthOfBytesUsingEncoding:NSUTF8StringEncoding] > kOversizeTextMessageSizeThreshold) {
|
|
|
|
|
DDLogError(@"%@ truncating body which is too long: %tu",
|
|
|
|
|
self.logTag,
|
|
|
|
|