Merge branch 'charlesmchen/outgoingMessageState'
This commit is contained in:
commit
3291ca66c1
@ -165,6 +165,8 @@
|
||||
3496744F2076ACD000080B5F /* LongTextViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3496744E2076ACCE00080B5F /* LongTextViewController.swift */; };
|
||||
34A55F3720485465002CC6DE /* OWS2FARegistrationViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 34A55F3520485464002CC6DE /* OWS2FARegistrationViewController.m */; };
|
||||
34A910601FFEB114000C4745 /* OWSBackup.m in Sources */ = {isa = PBXBuildFile; fileRef = 34A9105F1FFEB114000C4745 /* OWSBackup.m */; };
|
||||
34ABB2C42090C59700C727A6 /* OWSResaveCollectionDBMigration.m in Sources */ = {isa = PBXBuildFile; fileRef = 34ABB2C22090C59600C727A6 /* OWSResaveCollectionDBMigration.m */; };
|
||||
34ABB2C52090C59700C727A6 /* OWSResaveCollectionDBMigration.h in Headers */ = {isa = PBXBuildFile; fileRef = 34ABB2C32090C59700C727A6 /* OWSResaveCollectionDBMigration.h */; };
|
||||
34B0796D1FCF46B100E248C2 /* MainAppContext.m in Sources */ = {isa = PBXBuildFile; fileRef = 34B0796B1FCF46B000E248C2 /* MainAppContext.m */; };
|
||||
34B3F8751E8DF1700035BE1A /* CallViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 34B3F83B1E8DF1700035BE1A /* CallViewController.swift */; };
|
||||
34B3F8771E8DF1700035BE1A /* ContactsPicker.swift in Sources */ = {isa = PBXBuildFile; fileRef = 34B3F83E1E8DF1700035BE1A /* ContactsPicker.swift */; };
|
||||
@ -214,6 +216,8 @@
|
||||
34D2CCDA2062E7D000CB1A14 /* OWSScreenLockUI.m in Sources */ = {isa = PBXBuildFile; fileRef = 34D2CCD92062E7D000CB1A14 /* OWSScreenLockUI.m */; };
|
||||
34D2CCDF206939B400CB1A14 /* DebugUIMessagesAction.m in Sources */ = {isa = PBXBuildFile; fileRef = 34D2CCDB206939B100CB1A14 /* DebugUIMessagesAction.m */; };
|
||||
34D2CCE0206939B400CB1A14 /* DebugUIMessagesAssetLoader.m in Sources */ = {isa = PBXBuildFile; fileRef = 34D2CCDC206939B200CB1A14 /* DebugUIMessagesAssetLoader.m */; };
|
||||
34D5872F208E2C4200D2255A /* OWS109OutgoingMessageState.m in Sources */ = {isa = PBXBuildFile; fileRef = 34D5872D208E2C4100D2255A /* OWS109OutgoingMessageState.m */; };
|
||||
34D58730208E2C4200D2255A /* OWS109OutgoingMessageState.h in Headers */ = {isa = PBXBuildFile; fileRef = 34D5872E208E2C4100D2255A /* OWS109OutgoingMessageState.h */; };
|
||||
34D5CCA91EAE3D30005515DB /* AvatarViewHelper.m in Sources */ = {isa = PBXBuildFile; fileRef = 34D5CCA81EAE3D30005515DB /* AvatarViewHelper.m */; };
|
||||
34D8C0271ED3673300188D7C /* DebugUIMessages.m in Sources */ = {isa = PBXBuildFile; fileRef = 34D8C0241ED3673300188D7C /* DebugUIMessages.m */; };
|
||||
34D8C0281ED3673300188D7C /* DebugUITableViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 34D8C0261ED3673300188D7C /* DebugUITableViewController.m */; };
|
||||
@ -758,6 +762,8 @@
|
||||
34A55F3620485464002CC6DE /* OWS2FARegistrationViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = OWS2FARegistrationViewController.h; sourceTree = "<group>"; };
|
||||
34A9105E1FFEB113000C4745 /* OWSBackup.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = OWSBackup.h; sourceTree = "<group>"; };
|
||||
34A9105F1FFEB114000C4745 /* OWSBackup.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = OWSBackup.m; sourceTree = "<group>"; };
|
||||
34ABB2C22090C59600C727A6 /* OWSResaveCollectionDBMigration.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = OWSResaveCollectionDBMigration.m; sourceTree = "<group>"; };
|
||||
34ABB2C32090C59700C727A6 /* OWSResaveCollectionDBMigration.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = OWSResaveCollectionDBMigration.h; sourceTree = "<group>"; };
|
||||
34B0796B1FCF46B000E248C2 /* MainAppContext.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MainAppContext.m; sourceTree = "<group>"; };
|
||||
34B0796C1FCF46B000E248C2 /* MainAppContext.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MainAppContext.h; sourceTree = "<group>"; };
|
||||
34B0796E1FD07B1E00E248C2 /* SignalShareExtension.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = SignalShareExtension.entitlements; sourceTree = "<group>"; };
|
||||
@ -847,6 +853,8 @@
|
||||
34D2CCDD206939B200CB1A14 /* DebugUIMessagesAction.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = DebugUIMessagesAction.h; sourceTree = "<group>"; };
|
||||
34D2CCDE206939B400CB1A14 /* DebugUIMessagesAssetLoader.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = DebugUIMessagesAssetLoader.h; sourceTree = "<group>"; };
|
||||
34D2CCE220693A1700CB1A14 /* DebugUIMessagesUtils.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = DebugUIMessagesUtils.h; sourceTree = "<group>"; };
|
||||
34D5872D208E2C4100D2255A /* OWS109OutgoingMessageState.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = OWS109OutgoingMessageState.m; sourceTree = "<group>"; };
|
||||
34D5872E208E2C4100D2255A /* OWS109OutgoingMessageState.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = OWS109OutgoingMessageState.h; sourceTree = "<group>"; };
|
||||
34D5CCA71EAE3D30005515DB /* AvatarViewHelper.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = AvatarViewHelper.h; sourceTree = "<group>"; };
|
||||
34D5CCA81EAE3D30005515DB /* AvatarViewHelper.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = AvatarViewHelper.m; sourceTree = "<group>"; };
|
||||
34D8C0231ED3673300188D7C /* DebugUIMessages.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = DebugUIMessages.h; sourceTree = "<group>"; };
|
||||
@ -1525,10 +1533,14 @@
|
||||
4503F1C1204711D200CEE724 /* OWS107LegacySounds.m */,
|
||||
4598198C204E2F28009414F2 /* OWS108CallLoggingPreference.h */,
|
||||
4598198D204E2F28009414F2 /* OWS108CallLoggingPreference.m */,
|
||||
34D5872E208E2C4100D2255A /* OWS109OutgoingMessageState.h */,
|
||||
34D5872D208E2C4100D2255A /* OWS109OutgoingMessageState.m */,
|
||||
346129931FD1E30000532771 /* OWSDatabaseMigration.h */,
|
||||
346129941FD1E30000532771 /* OWSDatabaseMigration.m */,
|
||||
346129E51FD5C0C600532771 /* OWSDatabaseMigrationRunner.h */,
|
||||
346129E41FD5C0C600532771 /* OWSDatabaseMigrationRunner.m */,
|
||||
34ABB2C32090C59700C727A6 /* OWSResaveCollectionDBMigration.h */,
|
||||
34ABB2C22090C59600C727A6 /* OWSResaveCollectionDBMigration.m */,
|
||||
);
|
||||
path = migrations;
|
||||
sourceTree = "<group>";
|
||||
@ -2351,6 +2363,7 @@
|
||||
346129E71FD5C0C600532771 /* OWSDatabaseMigrationRunner.h in Headers */,
|
||||
344D6CEA20069E070042AF96 /* SelectRecipientViewController.h in Headers */,
|
||||
34480B521FD0A7A400BC14EF /* OWSLogger.h in Headers */,
|
||||
34ABB2C52090C59700C727A6 /* OWSResaveCollectionDBMigration.h in Headers */,
|
||||
459B775D207BA4810071D0AB /* OWSQuotedReplyModel.h in Headers */,
|
||||
34612A001FD5F31400532771 /* OWS105AttachmentFilePaths.h in Headers */,
|
||||
346129F61FD5F31400532771 /* OWS103EnableVideoCalling.h in Headers */,
|
||||
@ -2379,6 +2392,7 @@
|
||||
34480B611FD0A98800BC14EF /* UIColor+OWS.h in Headers */,
|
||||
453518961FC63DBF00210559 /* SignalMessaging.h in Headers */,
|
||||
3461295A1FD1D74C00532771 /* Environment.h in Headers */,
|
||||
34D58730208E2C4200D2255A /* OWS109OutgoingMessageState.h in Headers */,
|
||||
4598198E204E2F28009414F2 /* OWS108CallLoggingPreference.h in Headers */,
|
||||
34480B631FD0A98800BC14EF /* UIView+OWS.h in Headers */,
|
||||
451F8A4B1FD715E1005CB9DA /* OWSGroupAvatarBuilder.h in Headers */,
|
||||
@ -3045,6 +3059,7 @@
|
||||
451F8A3D1FD713CA005CB9DA /* ThreadViewHelper.m in Sources */,
|
||||
346129AD1FD1F34E00532771 /* ImageCache.swift in Sources */,
|
||||
452C7CA72037628B003D51A5 /* Weak.swift in Sources */,
|
||||
34D5872F208E2C4200D2255A /* OWS109OutgoingMessageState.m in Sources */,
|
||||
451F8A341FD710C3005CB9DA /* ConversationSearcher.swift in Sources */,
|
||||
346129341FD1A88700532771 /* OWSSwiftUtils.swift in Sources */,
|
||||
346129FE1FD5F31400532771 /* OWS106EnsureProfileComplete.swift in Sources */,
|
||||
@ -3089,6 +3104,7 @@
|
||||
346129721FD1D74C00532771 /* SignalKeyingStorage.m in Sources */,
|
||||
34480B561FD0A7A400BC14EF /* DebugLogger.m in Sources */,
|
||||
459B775C207BA46C0071D0AB /* OWSQuotedReplyModel.m in Sources */,
|
||||
34ABB2C42090C59700C727A6 /* OWSResaveCollectionDBMigration.m in Sources */,
|
||||
4551DB5A205C562300C8AE75 /* Collection+OWS.swift in Sources */,
|
||||
3461293C1FD1D46A00532771 /* OWSMath.m in Sources */,
|
||||
451F8A391FD711D6005CB9DA /* ContactsViewHelper.m in Sources */,
|
||||
@ -3425,7 +3441,11 @@
|
||||
"DEBUG=1",
|
||||
"$(inherited)",
|
||||
);
|
||||
"GCC_PREPROCESSOR_DEFINITIONS[arch=*]" = "DEBUG=1 $(inherited) SSK_BUILDING_FOR_TESTS=1";
|
||||
"GCC_PREPROCESSOR_DEFINITIONS[arch=*]" = (
|
||||
"DEBUG=1",
|
||||
"$(inherited)",
|
||||
"SSK_BUILDING_FOR_TESTS=1",
|
||||
);
|
||||
GCC_TREAT_IMPLICIT_FUNCTION_DECLARATIONS_AS_ERRORS = YES;
|
||||
GCC_TREAT_INCOMPATIBLE_POINTER_TYPE_WARNINGS_AS_ERRORS = YES;
|
||||
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
|
||||
|
||||
@ -499,7 +499,7 @@ NS_ASSUME_NONNULL_BEGIN
|
||||
// Ignore taps on links in outgoing messages that haven't been sent yet, as
|
||||
// this interferes with "tap to retry".
|
||||
TSOutgoingMessage *outgoingMessage = (TSOutgoingMessage *)self.viewItem.interaction;
|
||||
shouldIgnoreEvents = outgoingMessage.messageState != TSOutgoingMessageStateSentToService;
|
||||
shouldIgnoreEvents = outgoingMessage.messageState != TSOutgoingMessageStateSent;
|
||||
}
|
||||
[self.class loadForTextDisplay:self.bodyTextView
|
||||
text:self.displayableBodyText.displayText
|
||||
@ -1026,7 +1026,7 @@ NS_ASSUME_NONNULL_BEGIN
|
||||
return NO;
|
||||
}
|
||||
TSOutgoingMessage *outgoingMessage = (TSOutgoingMessage *)self.viewItem.interaction;
|
||||
return outgoingMessage.messageState == TSOutgoingMessageStateAttemptingOut;
|
||||
return outgoingMessage.messageState == TSOutgoingMessageStateSending;
|
||||
}
|
||||
|
||||
- (OWSMessagesBubbleImageFactory *)bubbleFactory
|
||||
@ -1085,9 +1085,9 @@ NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
if (self.viewItem.interaction.interactionType == OWSInteractionType_OutgoingMessage) {
|
||||
TSOutgoingMessage *outgoingMessage = (TSOutgoingMessage *)self.viewItem.interaction;
|
||||
if (outgoingMessage.messageState == TSOutgoingMessageStateUnsent) {
|
||||
if (outgoingMessage.messageState == TSOutgoingMessageStateFailed) {
|
||||
return;
|
||||
} else if (outgoingMessage.messageState == TSOutgoingMessageStateAttemptingOut) {
|
||||
} else if (outgoingMessage.messageState == TSOutgoingMessageStateSending) {
|
||||
// Ignore taps on outgoing messages being sent.
|
||||
return;
|
||||
}
|
||||
|
||||
@ -108,7 +108,7 @@ NS_ASSUME_NONNULL_BEGIN
|
||||
return NO;
|
||||
}
|
||||
TSOutgoingMessage *outgoingMessage = (TSOutgoingMessage *)self.viewItem.interaction;
|
||||
return outgoingMessage.messageState == TSOutgoingMessageStateUnsent;
|
||||
return outgoingMessage.messageState == TSOutgoingMessageStateFailed;
|
||||
}
|
||||
|
||||
- (UIImage *)failedSendBadge
|
||||
@ -316,7 +316,7 @@ NS_ASSUME_NONNULL_BEGIN
|
||||
if (!self.viewItem.shouldHideRecipientStatus || hasExpirationTimer) {
|
||||
TSOutgoingMessage *outgoingMessage = (TSOutgoingMessage *)message;
|
||||
NSString *statusMessage =
|
||||
[MessageRecipientStatusUtils statusMessageWithOutgoingMessage:outgoingMessage referenceView:self];
|
||||
[MessageRecipientStatusUtils receiptMessageWithOutgoingMessage:outgoingMessage referenceView:self];
|
||||
attributedText = [[NSAttributedString alloc] initWithString:statusMessage attributes:@{}];
|
||||
}
|
||||
} else if (self.viewItem.isGroupThread) {
|
||||
@ -505,10 +505,10 @@ NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
if (self.viewItem.interaction.interactionType == OWSInteractionType_OutgoingMessage) {
|
||||
TSOutgoingMessage *outgoingMessage = (TSOutgoingMessage *)self.viewItem.interaction;
|
||||
if (outgoingMessage.messageState == TSOutgoingMessageStateUnsent) {
|
||||
if (outgoingMessage.messageState == TSOutgoingMessageStateFailed) {
|
||||
[self.delegate didTapFailedOutgoingMessage:outgoingMessage];
|
||||
return;
|
||||
} else if (outgoingMessage.messageState == TSOutgoingMessageStateAttemptingOut) {
|
||||
} else if (outgoingMessage.messageState == TSOutgoingMessageStateSending) {
|
||||
// Ignore taps on outgoing messages being sent.
|
||||
return;
|
||||
}
|
||||
@ -527,10 +527,10 @@ NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
if (self.viewItem.interaction.interactionType == OWSInteractionType_OutgoingMessage) {
|
||||
TSOutgoingMessage *outgoingMessage = (TSOutgoingMessage *)self.viewItem.interaction;
|
||||
if (outgoingMessage.messageState == TSOutgoingMessageStateUnsent) {
|
||||
if (outgoingMessage.messageState == TSOutgoingMessageStateFailed) {
|
||||
// Ignore long press on unsent messages.
|
||||
return;
|
||||
} else if (outgoingMessage.messageState == TSOutgoingMessageStateAttemptingOut) {
|
||||
} else if (outgoingMessage.messageState == TSOutgoingMessageStateSending) {
|
||||
// Ignore long press on outgoing messages being sent.
|
||||
return;
|
||||
}
|
||||
|
||||
@ -4651,7 +4651,7 @@ typedef enum : NSUInteger {
|
||||
|
||||
// Update the "shouldShowDate" property of the view items.
|
||||
OWSInteractionType lastInteractionType = OWSInteractionType_Unknown;
|
||||
MessageRecipientStatus lastRecipientStatus = MessageRecipientStatusUploading;
|
||||
MessageReceiptStatus lastReceiptStatus = MessageReceiptStatusUploading;
|
||||
NSString *_Nullable lastIncomingSenderId = nil;
|
||||
for (ConversationViewItem *viewItem in viewItems.reverseObjectEnumerator) {
|
||||
BOOL shouldHideRecipientStatus = NO;
|
||||
@ -4660,20 +4660,21 @@ typedef enum : NSUInteger {
|
||||
|
||||
if (interactionType == OWSInteractionType_OutgoingMessage) {
|
||||
TSOutgoingMessage *outgoingMessage = (TSOutgoingMessage *)viewItem.interaction;
|
||||
MessageRecipientStatus recipientStatus =
|
||||
[MessageRecipientStatusUtils recipientStatusWithOutgoingMessage:outgoingMessage];
|
||||
MessageReceiptStatus receiptStatus =
|
||||
[MessageRecipientStatusUtils recipientStatusWithOutgoingMessage:outgoingMessage
|
||||
referenceView:self.view];
|
||||
|
||||
if (outgoingMessage.messageState == TSOutgoingMessageStateUnsent) {
|
||||
if (outgoingMessage.messageState == TSOutgoingMessageStateFailed) {
|
||||
// always show "failed to send" status
|
||||
shouldHideRecipientStatus = NO;
|
||||
} else {
|
||||
shouldHideRecipientStatus
|
||||
= (interactionType == lastInteractionType && recipientStatus == lastRecipientStatus);
|
||||
= (interactionType == lastInteractionType && receiptStatus == lastReceiptStatus);
|
||||
}
|
||||
|
||||
shouldHideBubbleTail = interactionType == lastInteractionType;
|
||||
|
||||
lastRecipientStatus = recipientStatus;
|
||||
lastReceiptStatus = receiptStatus;
|
||||
} else if (interactionType == OWSInteractionType_IncomingMessage) {
|
||||
TSIncomingMessage *incomingMessage = (TSIncomingMessage *)viewItem.interaction;
|
||||
NSString *incomingSenderId = incomingMessage.authorId;
|
||||
|
||||
@ -662,8 +662,8 @@ NSString *NSStringForOWSMessageCellType(OWSMessageCellType cellType)
|
||||
} else if (action == self.replyActionSelector) {
|
||||
if ([self.interaction isKindOfClass:[TSOutgoingMessage class]]) {
|
||||
TSOutgoingMessage *outgoingMessage = (TSOutgoingMessage *)self.interaction;
|
||||
if (outgoingMessage.messageState == TSOutgoingMessageStateUnsent
|
||||
|| outgoingMessage.messageState == TSOutgoingMessageStateAttemptingOut) {
|
||||
if (outgoingMessage.messageState == TSOutgoingMessageStateFailed
|
||||
|| outgoingMessage.messageState == TSOutgoingMessageStateSending) {
|
||||
// Don't let users reply to messages which aren't yet delivered to the service.
|
||||
return NO;
|
||||
}
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@ -193,13 +193,14 @@ class MessageDetailViewController: OWSViewController, MediaGalleryDataSourceDele
|
||||
|
||||
let isGroupThread = thread.isGroupThread()
|
||||
|
||||
let recipientStatusGroups: [MessageRecipientStatus] = [
|
||||
let recipientStatusGroups: [MessageReceiptStatus] = [
|
||||
.read,
|
||||
.uploading,
|
||||
.delivered,
|
||||
.sent,
|
||||
.sending,
|
||||
.failed
|
||||
.failed,
|
||||
.skipped
|
||||
]
|
||||
for recipientStatusGroup in recipientStatusGroups {
|
||||
var groupRows = [UIView]()
|
||||
@ -212,8 +213,15 @@ class MessageDetailViewController: OWSViewController, MediaGalleryDataSourceDele
|
||||
groupRows.append(divider)
|
||||
}
|
||||
|
||||
for recipientId in thread.recipientIdentifiers {
|
||||
let (recipientStatus, shortStatusMessage, _) = MessageRecipientStatusUtils.recipientStatusAndStatusMessage(outgoingMessage: outgoingMessage, recipientId: recipientId, referenceView: self.view)
|
||||
let messageRecipientIds = outgoingMessage.recipientIds()
|
||||
|
||||
for recipientId in messageRecipientIds {
|
||||
guard let recipientState = outgoingMessage.recipientState(forRecipientId: recipientId) else {
|
||||
owsFail("\(self.logTag) no message status for recipient: \(recipientId).")
|
||||
continue
|
||||
}
|
||||
|
||||
let (recipientStatus, shortStatusMessage, _) = MessageRecipientStatusUtils.recipientStatusAndStatusMessage(outgoingMessage: outgoingMessage, recipientState: recipientState, referenceView: self.view)
|
||||
|
||||
guard recipientStatus == recipientStatusGroup else {
|
||||
continue
|
||||
@ -541,8 +549,8 @@ class MessageDetailViewController: OWSViewController, MediaGalleryDataSourceDele
|
||||
updateContent()
|
||||
}
|
||||
|
||||
private func string(for messageRecipientStatus: MessageRecipientStatus) -> String {
|
||||
switch messageRecipientStatus {
|
||||
private func string(for messageReceiptStatus: MessageReceiptStatus) -> String {
|
||||
switch messageReceiptStatus {
|
||||
case .uploading:
|
||||
return NSLocalizedString("MESSAGE_METADATA_VIEW_MESSAGE_STATUS_UPLOADING",
|
||||
comment: "Status label for messages which are uploading.")
|
||||
@ -560,7 +568,10 @@ class MessageDetailViewController: OWSViewController, MediaGalleryDataSourceDele
|
||||
comment: "Status label for messages which are read.")
|
||||
case .failed:
|
||||
return NSLocalizedString("MESSAGE_METADATA_VIEW_MESSAGE_STATUS_FAILED",
|
||||
comment: "Status label for messages which are failed.")
|
||||
comment: "Status label for messages which are failed.")
|
||||
case .skipped:
|
||||
return NSLocalizedString("MESSAGE_METADATA_VIEW_MESSAGE_STATUS_SKIPPED",
|
||||
comment: "Status label for messages which were skipped.")
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -6,222 +6,154 @@ import Foundation
|
||||
import SignalServiceKit
|
||||
import SignalMessaging
|
||||
|
||||
@objc enum MessageRecipientStatus: Int {
|
||||
@objc enum MessageReceiptStatus: Int {
|
||||
case uploading
|
||||
case sending
|
||||
case sent
|
||||
case delivered
|
||||
case read
|
||||
case failed
|
||||
case skipped
|
||||
}
|
||||
|
||||
// Our per-recipient status messages are "biased towards success"
|
||||
// and reflect the most successful known state for that recipient.
|
||||
//
|
||||
// Our per-message status messages are "biased towards failure"
|
||||
// and reflect the least successful known state for that message.
|
||||
//
|
||||
// Why?
|
||||
//
|
||||
// When showing the per-recipient status, we want to show the message
|
||||
// as "read" even if delivery failed to another recipient of the same
|
||||
// message.
|
||||
//
|
||||
// When showing the per-message status, we want to show the message
|
||||
// as "failed" if delivery failed to any recipient, even if another
|
||||
// receipient has read the message.
|
||||
//
|
||||
// Note also that for legacy reasons we have redundant and possibly
|
||||
// conflicting state. Examples:
|
||||
//
|
||||
// * We could have an entry in the recipientReadMap for a message
|
||||
// that has no entries in its recipientDeliveryMap.
|
||||
// * We could have an entry in the recipientReadMap or recipientDeliveryMap
|
||||
// for a message whose status is "attempting out" or "unsent".
|
||||
// * We could have a message whose wasDelivered property is false but
|
||||
// which has entries in its recipientDeliveryMap or recipientReadMap.
|
||||
// * Etc.
|
||||
//
|
||||
// To resolve this ambiguity, we apply a "bias" towards success or
|
||||
// failure.
|
||||
class MessageRecipientStatusUtils: NSObject {
|
||||
@objc
|
||||
public class MessageRecipientStatusUtils: NSObject {
|
||||
// MARK: Initializers
|
||||
|
||||
@available(*, unavailable, message:"do not instantiate this class.")
|
||||
private override init() {
|
||||
}
|
||||
|
||||
// This method is per-recipient and "biased towards success".
|
||||
// See comments above.
|
||||
public class func recipientStatus(outgoingMessage: TSOutgoingMessage,
|
||||
recipientId: String,
|
||||
referenceView: UIView) -> MessageRecipientStatus {
|
||||
let (messageRecipientStatus, _, _) = recipientStatusAndStatusMessage(outgoingMessage: outgoingMessage,
|
||||
recipientId: recipientId,
|
||||
// This method is per-recipient.
|
||||
class func recipientStatus(outgoingMessage: TSOutgoingMessage,
|
||||
recipientState: TSOutgoingMessageRecipientState,
|
||||
referenceView: UIView) -> MessageReceiptStatus {
|
||||
let (messageReceiptStatus, _, _) = recipientStatusAndStatusMessage(outgoingMessage: outgoingMessage,
|
||||
recipientState: recipientState,
|
||||
referenceView: referenceView)
|
||||
return messageRecipientStatus
|
||||
return messageReceiptStatus
|
||||
}
|
||||
|
||||
// This method is per-recipient and "biased towards success".
|
||||
// See comments above.
|
||||
// This method is per-recipient.
|
||||
@objc
|
||||
public class func shortStatusMessage(outgoingMessage: TSOutgoingMessage,
|
||||
recipientId: String,
|
||||
recipientState: TSOutgoingMessageRecipientState,
|
||||
referenceView: UIView) -> String {
|
||||
let (_, shortStatusMessage, _) = recipientStatusAndStatusMessage(outgoingMessage: outgoingMessage,
|
||||
recipientId: recipientId,
|
||||
recipientState: recipientState,
|
||||
referenceView: referenceView)
|
||||
return shortStatusMessage
|
||||
}
|
||||
|
||||
// This method is per-recipient and "biased towards success".
|
||||
// See comments above.
|
||||
// This method is per-recipient.
|
||||
@objc
|
||||
public class func longStatusMessage(outgoingMessage: TSOutgoingMessage,
|
||||
recipientId: String,
|
||||
recipientState: TSOutgoingMessageRecipientState,
|
||||
referenceView: UIView) -> String {
|
||||
let (_, _, longStatusMessage) = recipientStatusAndStatusMessage(outgoingMessage: outgoingMessage,
|
||||
recipientId: recipientId,
|
||||
recipientState: recipientState,
|
||||
referenceView: referenceView)
|
||||
return longStatusMessage
|
||||
}
|
||||
|
||||
// This method is per-recipient and "biased towards success".
|
||||
// See comments above.
|
||||
public class func recipientStatusAndStatusMessage(outgoingMessage: TSOutgoingMessage,
|
||||
recipientId: String,
|
||||
referenceView: UIView) -> (status: MessageRecipientStatus, shortStatusMessage: String, longStatusMessage: String) {
|
||||
// Legacy messages don't have "recipient read" state or "per-recipient delivery" state,
|
||||
// so we fall back to `TSOutgoingMessageState` which is not per-recipient and therefore
|
||||
// might be misleading.
|
||||
// This method is per-recipient.
|
||||
class func recipientStatusAndStatusMessage(outgoingMessage: TSOutgoingMessage,
|
||||
recipientState: TSOutgoingMessageRecipientState,
|
||||
referenceView: UIView) -> (status: MessageReceiptStatus, shortStatusMessage: String, longStatusMessage: String) {
|
||||
|
||||
let recipientReadMap = outgoingMessage.recipientReadMap
|
||||
if let readTimestamp = recipientReadMap[recipientId] {
|
||||
assert(outgoingMessage.messageState == .sentToService)
|
||||
let timestampString = DateUtil.formatPastTimestampRelativeToNow(readTimestamp.uint64Value,
|
||||
isRTL:referenceView.isRTL())
|
||||
let shortStatusMessage = timestampString
|
||||
let longStatusMessage = NSLocalizedString("MESSAGE_STATUS_READ", comment:"message footer for read messages").rtlSafeAppend(" ", referenceView:referenceView)
|
||||
.rtlSafeAppend(timestampString, referenceView:referenceView)
|
||||
return (status:.read, shortStatusMessage:shortStatusMessage, longStatusMessage:longStatusMessage)
|
||||
}
|
||||
|
||||
let recipientDeliveryMap = outgoingMessage.recipientDeliveryMap
|
||||
if let deliveryTimestamp = recipientDeliveryMap[recipientId] {
|
||||
assert(outgoingMessage.messageState == .sentToService)
|
||||
let timestampString = DateUtil.formatPastTimestampRelativeToNow(deliveryTimestamp.uint64Value,
|
||||
isRTL:referenceView.isRTL())
|
||||
let shortStatusMessage = timestampString
|
||||
let longStatusMessage = NSLocalizedString("MESSAGE_STATUS_DELIVERED",
|
||||
comment:"message status for message delivered to their recipient.").rtlSafeAppend(" ", referenceView:referenceView)
|
||||
.rtlSafeAppend(timestampString, referenceView:referenceView)
|
||||
return (status:.delivered, shortStatusMessage:shortStatusMessage, longStatusMessage:longStatusMessage)
|
||||
}
|
||||
|
||||
if outgoingMessage.wasDelivered {
|
||||
let statusMessage = NSLocalizedString("MESSAGE_STATUS_DELIVERED",
|
||||
comment:"message status for message delivered to their recipient.")
|
||||
return (status:.delivered, shortStatusMessage:statusMessage, longStatusMessage:statusMessage)
|
||||
}
|
||||
|
||||
if outgoingMessage.messageState == .unsent {
|
||||
let shortStatusMessage = NSLocalizedString("MESSAGE_STATUS_FAILED_SHORT", comment:"status message for failed messages")
|
||||
let longStatusMessage = NSLocalizedString("MESSAGE_STATUS_FAILED", comment:"message footer for failed messages")
|
||||
switch recipientState.state {
|
||||
case .failed:
|
||||
let shortStatusMessage = NSLocalizedString("MESSAGE_STATUS_FAILED_SHORT", comment: "status message for failed messages")
|
||||
let longStatusMessage = NSLocalizedString("MESSAGE_STATUS_FAILED", comment: "message footer for failed messages")
|
||||
return (status:.failed, shortStatusMessage:shortStatusMessage, longStatusMessage:longStatusMessage)
|
||||
} else if outgoingMessage.messageState == .sentToService ||
|
||||
outgoingMessage.wasSent(toRecipient:recipientId) {
|
||||
case .sending:
|
||||
if outgoingMessage.hasAttachments() {
|
||||
assert(outgoingMessage.messageState == .sending)
|
||||
|
||||
let statusMessage = NSLocalizedString("MESSAGE_STATUS_UPLOADING",
|
||||
comment: "message footer while attachment is uploading")
|
||||
return (status:.uploading, shortStatusMessage:statusMessage, longStatusMessage:statusMessage)
|
||||
} else {
|
||||
assert(outgoingMessage.messageState == .sending)
|
||||
|
||||
let statusMessage = NSLocalizedString("MESSAGE_STATUS_SENDING",
|
||||
comment: "message status while message is sending.")
|
||||
return (status:.sending, shortStatusMessage:statusMessage, longStatusMessage:statusMessage)
|
||||
}
|
||||
case .sent:
|
||||
if let readTimestamp = recipientState.readTimestamp {
|
||||
let timestampString = DateUtil.formatPastTimestampRelativeToNow(readTimestamp.uint64Value,
|
||||
isRTL: referenceView.isRTL())
|
||||
let shortStatusMessage = timestampString
|
||||
let longStatusMessage = NSLocalizedString("MESSAGE_STATUS_READ", comment: "message footer for read messages").rtlSafeAppend(" ", referenceView: referenceView)
|
||||
.rtlSafeAppend(timestampString, referenceView: referenceView)
|
||||
return (status:.read, shortStatusMessage:shortStatusMessage, longStatusMessage:longStatusMessage)
|
||||
}
|
||||
if let deliveryTimestamp = recipientState.deliveryTimestamp {
|
||||
let timestampString = DateUtil.formatPastTimestampRelativeToNow(deliveryTimestamp.uint64Value,
|
||||
isRTL: referenceView.isRTL())
|
||||
let shortStatusMessage = timestampString
|
||||
let longStatusMessage = NSLocalizedString("MESSAGE_STATUS_DELIVERED",
|
||||
comment: "message status for message delivered to their recipient.").rtlSafeAppend(" ", referenceView: referenceView)
|
||||
.rtlSafeAppend(timestampString, referenceView: referenceView)
|
||||
return (status:.delivered, shortStatusMessage:shortStatusMessage, longStatusMessage:longStatusMessage)
|
||||
}
|
||||
let statusMessage =
|
||||
NSLocalizedString("MESSAGE_STATUS_SENT",
|
||||
comment:"message footer for sent messages")
|
||||
comment: "message footer for sent messages")
|
||||
return (status:.sent, shortStatusMessage:statusMessage, longStatusMessage:statusMessage)
|
||||
} else if outgoingMessage.hasAttachments() {
|
||||
assert(outgoingMessage.messageState == .attemptingOut)
|
||||
|
||||
let statusMessage = NSLocalizedString("MESSAGE_STATUS_UPLOADING",
|
||||
comment:"message footer while attachment is uploading")
|
||||
return (status:.uploading, shortStatusMessage:statusMessage, longStatusMessage:statusMessage)
|
||||
} else {
|
||||
assert(outgoingMessage.messageState == .attemptingOut)
|
||||
|
||||
let statusMessage = NSLocalizedString("MESSAGE_STATUS_SENDING",
|
||||
comment:"message status while message is sending.")
|
||||
return (status:.sending, shortStatusMessage:statusMessage, longStatusMessage:statusMessage)
|
||||
case .skipped:
|
||||
let statusMessage = NSLocalizedString("MESSAGE_STATUS_RECIPIENT_SKIPPED",
|
||||
comment: "message status if message delivery to a recipient is skipped. We skip delivering group messages to users who have left the group or deactivated their Signal account.")
|
||||
return (status:.skipped, shortStatusMessage:statusMessage, longStatusMessage:statusMessage)
|
||||
}
|
||||
}
|
||||
|
||||
// This method is per-message and "biased towards failure".
|
||||
// See comments above.
|
||||
public class func statusMessage(outgoingMessage: TSOutgoingMessage,
|
||||
referenceView: UIView) -> String {
|
||||
// This method is per-message.
|
||||
internal class func receiptStatusAndMessage(outgoingMessage: TSOutgoingMessage,
|
||||
referenceView: UIView) -> (status: MessageReceiptStatus, message: String) {
|
||||
|
||||
switch outgoingMessage.messageState {
|
||||
case .unsent:
|
||||
case .failed:
|
||||
// Use the "long" version of this message here.
|
||||
return NSLocalizedString("MESSAGE_STATUS_FAILED", comment:"message footer for failed messages")
|
||||
case .attemptingOut:
|
||||
return (.failed, NSLocalizedString("MESSAGE_STATUS_FAILED", comment: "message footer for failed messages"))
|
||||
case .sending:
|
||||
if outgoingMessage.hasAttachments() {
|
||||
return NSLocalizedString("MESSAGE_STATUS_UPLOADING",
|
||||
comment:"message footer while attachment is uploading")
|
||||
return (.uploading, NSLocalizedString("MESSAGE_STATUS_UPLOADING",
|
||||
comment: "message footer while attachment is uploading"))
|
||||
} else {
|
||||
return NSLocalizedString("MESSAGE_STATUS_SENDING",
|
||||
comment:"message status while message is sending.")
|
||||
return (.sending, NSLocalizedString("MESSAGE_STATUS_SENDING",
|
||||
comment: "message status while message is sending."))
|
||||
}
|
||||
case .sentToService:
|
||||
let recipientReadMap = outgoingMessage.recipientReadMap
|
||||
if recipientReadMap.count > 0 {
|
||||
return NSLocalizedString("MESSAGE_STATUS_READ", comment:"message footer for read messages")
|
||||
case .sent:
|
||||
if outgoingMessage.readRecipientIds().count > 0 {
|
||||
return (.read, NSLocalizedString("MESSAGE_STATUS_READ", comment: "message footer for read messages"))
|
||||
}
|
||||
|
||||
let recipientDeliveryMap = outgoingMessage.recipientDeliveryMap
|
||||
if recipientDeliveryMap.count > 0 {
|
||||
return NSLocalizedString("MESSAGE_STATUS_DELIVERED",
|
||||
comment:"message status for message delivered to their recipient.")
|
||||
if outgoingMessage.deliveredRecipientIds().count > 0 {
|
||||
return (.delivered, NSLocalizedString("MESSAGE_STATUS_DELIVERED",
|
||||
comment: "message status for message delivered to their recipient."))
|
||||
}
|
||||
|
||||
if outgoingMessage.wasDelivered {
|
||||
return NSLocalizedString("MESSAGE_STATUS_DELIVERED",
|
||||
comment:"message status for message delivered to their recipient.")
|
||||
}
|
||||
|
||||
return NSLocalizedString("MESSAGE_STATUS_SENT",
|
||||
comment:"message footer for sent messages")
|
||||
return (.sent, NSLocalizedString("MESSAGE_STATUS_SENT",
|
||||
comment: "message footer for sent messages"))
|
||||
default:
|
||||
owsFail("Message has unexpected status: \(outgoingMessage.messageState).")
|
||||
return NSLocalizedString("MESSAGE_STATUS_SENT",
|
||||
comment:"message footer for sent messages")
|
||||
owsFail("\(self.logTag) Message has unexpected status: \(outgoingMessage.messageState).")
|
||||
return (.sent, NSLocalizedString("MESSAGE_STATUS_SENT",
|
||||
comment: "message footer for sent messages"))
|
||||
}
|
||||
}
|
||||
|
||||
// This method is per-message and "biased towards failure".
|
||||
// See comments above.
|
||||
public class func recipientStatus(outgoingMessage: TSOutgoingMessage) -> MessageRecipientStatus {
|
||||
switch outgoingMessage.messageState {
|
||||
case .unsent:
|
||||
return .failed
|
||||
case .attemptingOut:
|
||||
if outgoingMessage.hasAttachments() {
|
||||
return .uploading
|
||||
} else {
|
||||
return .sending
|
||||
}
|
||||
case .sentToService:
|
||||
let recipientReadMap = outgoingMessage.recipientReadMap
|
||||
if recipientReadMap.count > 0 {
|
||||
return .read
|
||||
}
|
||||
// This method is per-message.
|
||||
public class func receiptMessage(outgoingMessage: TSOutgoingMessage,
|
||||
referenceView: UIView) -> String {
|
||||
let (_, message ) = receiptStatusAndMessage(outgoingMessage: outgoingMessage,
|
||||
referenceView: referenceView)
|
||||
return message
|
||||
}
|
||||
|
||||
let recipientDeliveryMap = outgoingMessage.recipientDeliveryMap
|
||||
if recipientDeliveryMap.count > 0 {
|
||||
return .delivered
|
||||
}
|
||||
|
||||
if outgoingMessage.wasDelivered {
|
||||
return .delivered
|
||||
}
|
||||
|
||||
return .sent
|
||||
default:
|
||||
owsFail("Message has unexpected status: \(outgoingMessage.messageState).")
|
||||
|
||||
return .sent
|
||||
}
|
||||
// This method is per-message.
|
||||
class func recipientStatus(outgoingMessage: TSOutgoingMessage, referenceView: UIView) -> MessageReceiptStatus {
|
||||
let (status, _ ) = receiptStatusAndMessage(outgoingMessage: outgoingMessage,
|
||||
referenceView: referenceView)
|
||||
return status
|
||||
}
|
||||
}
|
||||
|
||||
@ -0,0 +1,13 @@
|
||||
//
|
||||
// Copyright (c) 2018 Open Whisper Systems. All rights reserved.
|
||||
//
|
||||
|
||||
#import "OWSResaveCollectionDBMigration.h"
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
@interface OWS109OutgoingMessageState : OWSResaveCollectionDBMigration
|
||||
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
@ -0,0 +1,46 @@
|
||||
//
|
||||
// Copyright (c) 2018 Open Whisper Systems. All rights reserved.
|
||||
//
|
||||
|
||||
#import "OWS109OutgoingMessageState.h"
|
||||
#import <SignalServiceKit/OWSPrimaryStorage.h>
|
||||
#import <SignalServiceKit/TSOutgoingMessage.h>
|
||||
#import <YapDatabase/YapDatabaseTransaction.h>
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
// Increment a similar constant for every future DBMigration
|
||||
static NSString *const OWS109OutgoingMessageStateMigrationId = @"109";
|
||||
|
||||
@implementation OWS109OutgoingMessageState
|
||||
|
||||
+ (NSString *)migrationId
|
||||
{
|
||||
return OWS109OutgoingMessageStateMigrationId;
|
||||
}
|
||||
|
||||
// Override parent migration
|
||||
- (void)runUpWithCompletion:(OWSDatabaseMigrationCompletion)completion
|
||||
{
|
||||
OWSAssert(completion);
|
||||
|
||||
|
||||
OWSDatabaseConnection *dbConnection = (OWSDatabaseConnection *)self.primaryStorage.newDatabaseConnection;
|
||||
|
||||
[self resaveDBCollection:TSOutgoingMessage.collection
|
||||
filter:^(id entity) {
|
||||
return [entity isKindOfClass:[TSOutgoingMessage class]];
|
||||
}
|
||||
dbConnection:dbConnection
|
||||
completion:^{
|
||||
DDLogInfo(@"Completed migration %@", self.uniqueId);
|
||||
|
||||
[self save];
|
||||
|
||||
completion();
|
||||
}];
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
@ -10,6 +10,7 @@
|
||||
#import "OWS105AttachmentFilePaths.h"
|
||||
#import "OWS107LegacySounds.h"
|
||||
#import "OWS108CallLoggingPreference.h"
|
||||
#import "OWS109OutgoingMessageState.h"
|
||||
#import "OWSDatabaseMigration.h"
|
||||
#import <SignalMessaging/SignalMessaging-Swift.h>
|
||||
#import <SignalServiceKit/AppContext.h>
|
||||
@ -42,7 +43,8 @@ NS_ASSUME_NONNULL_BEGIN
|
||||
[[OWS105AttachmentFilePaths alloc] initWithPrimaryStorage:primaryStorage],
|
||||
[[OWS106EnsureProfileComplete alloc] initWithPrimaryStorage:primaryStorage],
|
||||
[[OWS107LegacySounds alloc] initWithPrimaryStorage:primaryStorage],
|
||||
[[OWS108CallLoggingPreference alloc] initWithPrimaryStorage:primaryStorage]
|
||||
[[OWS108CallLoggingPreference alloc] initWithPrimaryStorage:primaryStorage],
|
||||
[[OWS109OutgoingMessageState alloc] initWithPrimaryStorage:primaryStorage]
|
||||
];
|
||||
}
|
||||
|
||||
|
||||
@ -0,0 +1,24 @@
|
||||
//
|
||||
// Copyright (c) 2018 Open Whisper Systems. All rights reserved.
|
||||
//
|
||||
|
||||
#import "OWSDatabaseMigration.h"
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
typedef BOOL (^DBRecordFilterBlock)(id record);
|
||||
|
||||
@class YapDatabaseConnection;
|
||||
|
||||
// Base class for migrations that resave all or a subset of
|
||||
// records in a database collection.
|
||||
@interface OWSResaveCollectionDBMigration : OWSDatabaseMigration
|
||||
|
||||
- (void)resaveDBCollection:(NSString *)collection
|
||||
filter:(nullable DBRecordFilterBlock)filter
|
||||
dbConnection:(YapDatabaseConnection *)dbConnection
|
||||
completion:(OWSDatabaseMigrationCompletion)completion;
|
||||
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
@ -0,0 +1,80 @@
|
||||
//
|
||||
// Copyright (c) 2018 Open Whisper Systems. All rights reserved.
|
||||
//
|
||||
|
||||
#import "OWSResaveCollectionDBMigration.h"
|
||||
#import <YapDatabase/YapDatabaseConnection.h>
|
||||
#import <YapDatabase/YapDatabaseTransaction.h>
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
@implementation OWSResaveCollectionDBMigration
|
||||
|
||||
- (void)resaveDBCollection:(NSString *)collection
|
||||
filter:(nullable DBRecordFilterBlock)filter
|
||||
dbConnection:(YapDatabaseConnection *)dbConnection
|
||||
completion:(OWSDatabaseMigrationCompletion)completion
|
||||
{
|
||||
OWSAssert(collection.length > 0);
|
||||
OWSAssert(dbConnection);
|
||||
OWSAssert(completion);
|
||||
|
||||
NSMutableArray<NSString *> *recordIds = [NSMutableArray new];
|
||||
[dbConnection asyncReadWriteWithBlock:^(YapDatabaseReadWriteTransaction *_Nonnull transaction) {
|
||||
[recordIds addObjectsFromArray:[transaction allKeysInCollection:collection]];
|
||||
DDLogInfo(@"%@ Migrating %zd records from: %@.", self.logTag, recordIds.count, collection);
|
||||
}
|
||||
completionQueue:dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0)
|
||||
completionBlock:^{
|
||||
[self resaveBatch:recordIds
|
||||
collection:collection
|
||||
filter:filter
|
||||
dbConnection:dbConnection
|
||||
completion:completion];
|
||||
}];
|
||||
}
|
||||
|
||||
- (void)resaveBatch:(NSMutableArray<NSString *> *)recordIds
|
||||
collection:(NSString *)collection
|
||||
filter:(nullable DBRecordFilterBlock)filter
|
||||
dbConnection:(YapDatabaseConnection *)dbConnection
|
||||
completion:(OWSDatabaseMigrationCompletion)completion
|
||||
{
|
||||
OWSAssert(recordIds);
|
||||
OWSAssert(collection.length > 0);
|
||||
OWSAssert(dbConnection);
|
||||
OWSAssert(completion);
|
||||
|
||||
DDLogVerbose(@"%@ %s: %zd", self.logTag, __PRETTY_FUNCTION__, recordIds.count);
|
||||
|
||||
if (recordIds.count < 1) {
|
||||
completion();
|
||||
return;
|
||||
}
|
||||
|
||||
[dbConnection asyncReadWriteWithBlock:^(YapDatabaseReadWriteTransaction *_Nonnull transaction) {
|
||||
const int kBatchSize = 1000;
|
||||
for (int i = 0; i < kBatchSize && recordIds.count > 0; i++) {
|
||||
NSString *messageId = [recordIds lastObject];
|
||||
[recordIds removeLastObject];
|
||||
id record = [transaction objectForKey:messageId inCollection:collection];
|
||||
if (filter && !filter(record)) {
|
||||
continue;
|
||||
}
|
||||
TSYapDatabaseObject *entity = (TSYapDatabaseObject *)record;
|
||||
[entity saveWithTransaction:transaction];
|
||||
}
|
||||
}
|
||||
completionBlock:^{
|
||||
// Process the next batch.
|
||||
[self resaveBatch:recordIds
|
||||
collection:collection
|
||||
filter:filter
|
||||
dbConnection:dbConnection
|
||||
completion:completion];
|
||||
}];
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
@ -45,9 +45,9 @@ public class OWSMessagesBubbleImageFactory: NSObject {
|
||||
return self.incoming
|
||||
} else if let outgoingMessage = message as? TSOutgoingMessage {
|
||||
switch outgoingMessage.messageState {
|
||||
case .unsent:
|
||||
case .failed:
|
||||
return outgoingFailed
|
||||
case .attemptingOut:
|
||||
case .sending:
|
||||
return currentlyOutgoing
|
||||
default:
|
||||
return outgoing
|
||||
@ -75,9 +75,9 @@ public class OWSMessagesBubbleImageFactory: NSObject {
|
||||
return OWSMessagesBubbleImageFactory.bubbleColorIncoming
|
||||
} else if let outgoingMessage = message as? TSOutgoingMessage {
|
||||
switch outgoingMessage.messageState {
|
||||
case .unsent:
|
||||
case .failed:
|
||||
return OWSMessagesBubbleImageFactory.bubbleColorOutgoingUnsent
|
||||
case .attemptingOut:
|
||||
case .sending:
|
||||
return OWSMessagesBubbleImageFactory.bubbleColorOutgoingSending
|
||||
default:
|
||||
return OWSMessagesBubbleImageFactory.bubbleColorOutgoingSent
|
||||
|
||||
@ -1,10 +1,12 @@
|
||||
//
|
||||
// Copyright (c) 2017 Open Whisper Systems. All rights reserved.
|
||||
// Copyright (c) 2018 Open Whisper Systems. All rights reserved.
|
||||
//
|
||||
|
||||
#import "OWSOutgoingSentMessageTranscript.h"
|
||||
#import "OWSSignalServiceProtos.pb.h"
|
||||
#import "TSAccountManager.h"
|
||||
#import "TSOutgoingMessage.h"
|
||||
#import "TSThread.h"
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
@ -48,8 +50,12 @@ NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
OWSSignalServiceProtosSyncMessageSentBuilder *sentBuilder = [OWSSignalServiceProtosSyncMessageSentBuilder new];
|
||||
[sentBuilder setTimestamp:self.message.timestamp];
|
||||
[sentBuilder setDestination:self.message.recipientIdentifier];
|
||||
[sentBuilder setMessage:[self.message buildDataMessage:self.message.recipientIdentifier]];
|
||||
|
||||
NSString *_Nullable localNumber = [TSAccountManager localNumber];
|
||||
OWSAssert(localNumber.length > 0);
|
||||
OWSAssert([localNumber isEqualToString:self.message.thread.contactIdentifier]);
|
||||
[sentBuilder setDestination:localNumber];
|
||||
[sentBuilder setMessage:[self.message buildDataMessage:localNumber]];
|
||||
[sentBuilder setExpirationStartTimestamp:self.message.timestamp];
|
||||
|
||||
[syncMessageBuilder setSentBuilder:sentBuilder];
|
||||
|
||||
@ -11,14 +11,30 @@ typedef NS_ENUM(NSInteger, TSOutgoingMessageState) {
|
||||
// a) Enqueued for sending.
|
||||
// b) Waiting on attachment upload(s).
|
||||
// c) Being sent to the service.
|
||||
TSOutgoingMessageStateAttemptingOut,
|
||||
TSOutgoingMessageStateSending,
|
||||
// The failure state.
|
||||
TSOutgoingMessageStateUnsent,
|
||||
// These two enum values have been combined into TSOutgoingMessageStateSentToService.
|
||||
TSOutgoingMessageStateFailed,
|
||||
// These two enum values have been combined into TSOutgoingMessageStateSent.
|
||||
TSOutgoingMessageStateSent_OBSOLETE,
|
||||
TSOutgoingMessageStateDelivered_OBSOLETE,
|
||||
// The message has been sent to the service.
|
||||
TSOutgoingMessageStateSentToService,
|
||||
TSOutgoingMessageStateSent,
|
||||
};
|
||||
|
||||
// Used
|
||||
typedef NS_ENUM(NSInteger, OWSOutgoingMessageRecipientState) {
|
||||
// Message could not be sent to recipient.
|
||||
OWSOutgoingMessageRecipientStateFailed = 0,
|
||||
// Message is being sent to the recipient (enqueued, uploading or sending).
|
||||
OWSOutgoingMessageRecipientStateSending,
|
||||
// The message was not sent because the recipient is not valid.
|
||||
// For example, this recipient may have left the group.
|
||||
OWSOutgoingMessageRecipientStateSkipped,
|
||||
// The message has been sent to the service. It may also have been delivered or read.
|
||||
OWSOutgoingMessageRecipientStateSent,
|
||||
|
||||
OWSOutgoingMessageRecipientStateMin = OWSOutgoingMessageRecipientStateFailed,
|
||||
OWSOutgoingMessageRecipientStateMax = OWSOutgoingMessageRecipientStateSent,
|
||||
};
|
||||
|
||||
typedef NS_ENUM(NSInteger, TSGroupMetaMessage) {
|
||||
@ -35,6 +51,18 @@ typedef NS_ENUM(NSInteger, TSGroupMetaMessage) {
|
||||
@class OWSSignalServiceProtosDataMessageBuilder;
|
||||
@class SignalRecipient;
|
||||
|
||||
@interface TSOutgoingMessageRecipientState : MTLModel
|
||||
|
||||
@property (atomic, readonly) OWSOutgoingMessageRecipientState state;
|
||||
// This property should only be set if state == .sent.
|
||||
@property (atomic, nullable, readonly) NSNumber *deliveryTimestamp;
|
||||
// This property should only be set if state == .sent.
|
||||
@property (atomic, nullable, readonly) NSNumber *readTimestamp;
|
||||
|
||||
@end
|
||||
|
||||
#pragma mark -
|
||||
|
||||
@interface TSOutgoingMessage : TSMessage
|
||||
|
||||
- (instancetype)initMessageWithTimestamp:(uint64_t)timestamp
|
||||
@ -75,11 +103,7 @@ typedef NS_ENUM(NSInteger, TSGroupMetaMessage) {
|
||||
+ (instancetype)outgoingMessageInThread:(nullable TSThread *)thread
|
||||
groupMetaMessage:(TSGroupMetaMessage)groupMetaMessage;
|
||||
|
||||
@property (atomic, readonly) TSOutgoingMessageState messageState;
|
||||
|
||||
// The message has been sent to the service and received by at least one recipient client.
|
||||
// A recipient may have more than one client, and group message may have more than one recipient.
|
||||
@property (atomic, readonly) BOOL wasDelivered;
|
||||
@property (readonly) TSOutgoingMessageState messageState;
|
||||
|
||||
@property (atomic, readonly) BOOL hasSyncedTranscript;
|
||||
@property (atomic, readonly) NSString *customMessage;
|
||||
@ -89,27 +113,13 @@ typedef NS_ENUM(NSInteger, TSGroupMetaMessage) {
|
||||
|
||||
@property (atomic, readonly) TSGroupMetaMessage groupMetaMessage;
|
||||
|
||||
// If set, this group message should only be sent to a single recipient.
|
||||
@property (atomic, readonly) NSString *singleGroupRecipient;
|
||||
|
||||
@property (nonatomic, readonly) BOOL isVoiceMessage;
|
||||
|
||||
// This property won't be accurate for legacy messages.
|
||||
@property (atomic, readonly) BOOL isFromLinkedDevice;
|
||||
|
||||
// Map of "recipient id"-to-"delivery time" of the recipients who have received the message.
|
||||
@property (atomic, readonly) NSDictionary<NSString *, NSNumber *> *recipientDeliveryMap;
|
||||
|
||||
// Map of "recipient id"-to-"read time" of the recipients who have read the message.
|
||||
@property (atomic, readonly) NSDictionary<NSString *, NSNumber *> *recipientReadMap;
|
||||
|
||||
@property (nonatomic, readonly) BOOL isSilent;
|
||||
|
||||
/**
|
||||
* Signal Identifier (e.g. e164 number) or nil if in a group thread.
|
||||
*/
|
||||
- (nullable NSString *)recipientIdentifier;
|
||||
|
||||
/**
|
||||
* The data representation of this message, to be encrypted, before being sent.
|
||||
*/
|
||||
@ -143,37 +153,76 @@ typedef NS_ENUM(NSInteger, TSGroupMetaMessage) {
|
||||
|
||||
- (BOOL)shouldBeSaved;
|
||||
|
||||
// All recipients of this message.
|
||||
- (NSArray<NSString *> *)recipientIds;
|
||||
|
||||
// All recipients of this message who we are currently trying to send to (queued, uploading or during send).
|
||||
- (NSArray<NSString *> *)sendingRecipientIds;
|
||||
|
||||
// All recipients of this message to whom it has been sent and delivered.
|
||||
- (NSArray<NSString *> *)deliveredRecipientIds;
|
||||
|
||||
// All recipients of this message to whom it has been sent, delivered and read.
|
||||
- (NSArray<NSString *> *)readRecipientIds;
|
||||
|
||||
// Number of recipients of this message to whom it has been sent.
|
||||
- (NSUInteger)sentRecipientsCount;
|
||||
|
||||
- (nullable TSOutgoingMessageRecipientState *)recipientStateForRecipientId:(NSString *)recipientId;
|
||||
|
||||
#pragma mark - Update With... Methods
|
||||
|
||||
- (void)updateWithMessageState:(TSOutgoingMessageState)messageState;
|
||||
- (void)updateWithMessageState:(TSOutgoingMessageState)messageState
|
||||
transaction:(YapDatabaseReadWriteTransaction *)transaction;
|
||||
// This method is used to record a successful send to one recipient.
|
||||
- (void)updateWithSentRecipient:(NSString *)recipientId transaction:(YapDatabaseReadWriteTransaction *)transaction;
|
||||
|
||||
// This method is used to record a skipped send to one recipient.
|
||||
- (void)updateWithSkippedRecipient:(NSString *)recipientId transaction:(YapDatabaseReadWriteTransaction *)transaction;
|
||||
|
||||
// On app launch, all "sending" recipients should be marked as "failed".
|
||||
- (void)updateWithAllSendingRecipientsMarkedAsFailedWithTansaction:(YapDatabaseReadWriteTransaction *)transaction;
|
||||
|
||||
// When we start a message send, all "failed" recipients should be marked as "sending".
|
||||
- (void)updateWithMarkingAllUnsentRecipientsAsSendingWithTransaction:(YapDatabaseReadWriteTransaction *)transaction;
|
||||
|
||||
// This method is used to forge the message state for fake messages.
|
||||
//
|
||||
// NOTE: This method should only be used by Debug UI, etc.
|
||||
- (void)updateWithFakeMessageState:(TSOutgoingMessageState)messageState
|
||||
transaction:(YapDatabaseReadWriteTransaction *)transaction;
|
||||
|
||||
// This method is used to record a failed send to all "sending" recipients.
|
||||
- (void)updateWithSendingError:(NSError *)error;
|
||||
|
||||
- (void)updateWithHasSyncedTranscript:(BOOL)hasSyncedTranscript
|
||||
transaction:(YapDatabaseReadWriteTransaction *)transaction;
|
||||
- (void)updateWithCustomMessage:(NSString *)customMessage transaction:(YapDatabaseReadWriteTransaction *)transaction;
|
||||
- (void)updateWithCustomMessage:(NSString *)customMessage;
|
||||
|
||||
// This method is used to record a successful delivery to one recipient.
|
||||
//
|
||||
// deliveryTimestamp is an optional parameter, since legacy
|
||||
// delivery receipts don't have a "delivery timestamp". Those
|
||||
// messages repurpose the "timestamp" field to indicate when the
|
||||
// corresponding message was originally sent.
|
||||
- (void)updateWithDeliveredToRecipientId:(NSString *)recipientId
|
||||
deliveryTimestamp:(NSNumber *_Nullable)deliveryTimestamp
|
||||
transaction:(YapDatabaseReadWriteTransaction *)transaction;
|
||||
- (void)updateWithDeliveredRecipient:(NSString *)recipientId
|
||||
deliveryTimestamp:(NSNumber *_Nullable)deliveryTimestamp
|
||||
transaction:(YapDatabaseReadWriteTransaction *)transaction;
|
||||
|
||||
- (void)updateWithWasSentFromLinkedDeviceWithTransaction:(YapDatabaseReadWriteTransaction *)transaction;
|
||||
- (void)updateWithSingleGroupRecipient:(NSString *)singleGroupRecipient
|
||||
transaction:(YapDatabaseReadWriteTransaction *)transaction;
|
||||
|
||||
// This method is used to rewrite the recipient list with a single recipient.
|
||||
// It is used to reply to a "group info request", which should only be
|
||||
// delivered to the requestor.
|
||||
- (void)updateWithSendingToSingleGroupRecipient:(NSString *)singleGroupRecipient
|
||||
transaction:(YapDatabaseReadWriteTransaction *)transaction;
|
||||
|
||||
// This method is used to record a successful "read" by one recipient.
|
||||
- (void)updateWithReadRecipientId:(NSString *)recipientId
|
||||
readTimestamp:(uint64_t)readTimestamp
|
||||
transaction:(YapDatabaseReadWriteTransaction *)transaction;
|
||||
|
||||
- (nullable NSNumber *)firstRecipientReadTimestamp;
|
||||
|
||||
#pragma mark - Sent Recipients
|
||||
|
||||
- (NSUInteger)sentRecipientsCount;
|
||||
- (BOOL)wasSentToRecipient:(NSString *)contactId;
|
||||
- (void)updateWithSentRecipient:(NSString *)contactId transaction:(YapDatabaseReadWriteTransaction *)transaction;
|
||||
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
|
||||
@ -6,6 +6,7 @@
|
||||
#import "NSDate+OWS.h"
|
||||
#import "OWSMessageSender.h"
|
||||
#import "OWSOutgoingSyncMessage.h"
|
||||
#import "OWSPrimaryStorage.h"
|
||||
#import "OWSSignalServiceProtos.pb.h"
|
||||
#import "ProtoBuf+OWS.h"
|
||||
#import "SignalRecipient.h"
|
||||
@ -21,28 +22,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 +54,6 @@ NSString *const kTSOutgoingMessageSentRecipientAll = @"kTSOutgoingMessageSentRec
|
||||
|
||||
@implementation TSOutgoingMessage
|
||||
|
||||
@synthesize sentRecipients = _sentRecipients;
|
||||
|
||||
- (instancetype)initWithCoder:(NSCoder *)coder
|
||||
{
|
||||
self = [super initWithCoder:coder];
|
||||
@ -60,22 +62,113 @@ 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.
|
||||
__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) {
|
||||
recipientIds = [[self threadWithTransaction:transaction] 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) {
|
||||
OWSFail(@"%@ unexpected single group recipient message.", self.logTag);
|
||||
// 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];
|
||||
}
|
||||
|
||||
+ (YapDatabaseConnection *)dbMigrationConnection
|
||||
{
|
||||
static YapDatabaseConnection *connection = nil;
|
||||
static dispatch_once_t onceToken;
|
||||
dispatch_once(&onceToken, ^{
|
||||
connection = [[OWSPrimaryStorage sharedManager] newDatabaseConnection];
|
||||
});
|
||||
return connection;
|
||||
}
|
||||
|
||||
+ (instancetype)outgoingMessageInThread:(nullable TSThread *)thread
|
||||
messageBody:(nullable NSString *)body
|
||||
attachmentId:(nullable NSString *)attachmentId
|
||||
@ -156,8 +249,6 @@ NSString *const kTSOutgoingMessageSentRecipientAll = @"kTSOutgoingMessageSentRec
|
||||
return self;
|
||||
}
|
||||
|
||||
_messageState = TSOutgoingMessageStateAttemptingOut;
|
||||
_sentRecipients = [NSArray new];
|
||||
_hasSyncedTranscript = NO;
|
||||
|
||||
if ([thread isKindOfClass:TSGroupThread.class]) {
|
||||
@ -177,9 +268,50 @@ NSString *const kTSOutgoingMessageSentRecipientAll = @"kTSOutgoingMessageSentRec
|
||||
|
||||
_attachmentFilenameMap = [NSMutableDictionary new];
|
||||
|
||||
// New outgoing messages should immediately determine their
|
||||
// recipient list from current thread state.
|
||||
NSMutableDictionary<NSString *, TSOutgoingMessageRecipientState *> *recipientStateMap = [NSMutableDictionary new];
|
||||
NSArray<NSString *> *recipientIds = [thread recipientIdentifiers];
|
||||
for (NSString *recipientId in recipientIds) {
|
||||
TSOutgoingMessageRecipientState *recipientState = [TSOutgoingMessageRecipientState new];
|
||||
recipientState.state = OWSOutgoingMessageRecipientStateSending;
|
||||
recipientStateMap[recipientId] = recipientState;
|
||||
}
|
||||
self.recipientStateMap = [recipientStateMap copy];
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
- (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 +335,29 @@ NSString *const kTSOutgoingMessageSentRecipientAll = @"kTSOutgoingMessageSentRec
|
||||
[super saveWithTransaction:transaction];
|
||||
}
|
||||
|
||||
- (nullable NSString *)recipientIdentifier
|
||||
- (BOOL)hasSentToAnyRecipient
|
||||
{
|
||||
return self.thread.contactIdentifier;
|
||||
for (TSOutgoingMessageRecipientState *recipientState in self.recipientStateMap.allValues) {
|
||||
if (recipientState.state == OWSOutgoingMessageRecipientStateSent) {
|
||||
return YES;
|
||||
}
|
||||
}
|
||||
return NO;
|
||||
}
|
||||
|
||||
- (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 {
|
||||
return self.hasSentToAnyRecipient;
|
||||
}
|
||||
}
|
||||
|
||||
@ -233,6 +371,67 @@ NSString *const kTSOutgoingMessageSentRecipientAll = @"kTSOutgoingMessageSentRec
|
||||
return OWSInteractionType_OutgoingMessage;
|
||||
}
|
||||
|
||||
- (NSArray<NSString *> *)recipientIds
|
||||
{
|
||||
return self.recipientStateMap.allKeys;
|
||||
}
|
||||
|
||||
- (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 copy];
|
||||
}
|
||||
|
||||
#pragma mark - Update With... Methods
|
||||
|
||||
- (void)updateWithSendingError:(NSError *)error
|
||||
@ -242,27 +441,47 @@ 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
|
||||
{
|
||||
[self.dbReadWriteConnection readWriteWithBlock:^(YapDatabaseReadWriteTransaction *transaction) {
|
||||
[self updateWithMessageState:messageState transaction:transaction];
|
||||
}];
|
||||
}
|
||||
|
||||
- (void)updateWithMessageState:(TSOutgoingMessageState)messageState
|
||||
transaction:(YapDatabaseReadWriteTransaction *)transaction
|
||||
- (void)updateWithAllSendingRecipientsMarkedAsFailedWithTansaction:(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 == OWSOutgoingMessageRecipientStateSending) {
|
||||
recipientState.state = OWSOutgoingMessageRecipientStateFailed;
|
||||
}
|
||||
}
|
||||
}];
|
||||
}
|
||||
|
||||
- (void)updateWithMarkingAllUnsentRecipientsAsSendingWithTransaction:(YapDatabaseReadWriteTransaction *)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 == OWSOutgoingMessageRecipientStateFailed) {
|
||||
recipientState.state = OWSOutgoingMessageRecipientStateSending;
|
||||
}
|
||||
}
|
||||
}];
|
||||
}
|
||||
|
||||
@ -293,104 +512,62 @@ 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);
|
||||
|
||||
[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;
|
||||
}];
|
||||
}
|
||||
|
||||
- (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;
|
||||
}];
|
||||
}
|
||||
|
||||
- (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];
|
||||
}];
|
||||
}
|
||||
|
||||
#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
|
||||
{
|
||||
OWSAssert(self.sentRecipients);
|
||||
|
||||
return self.sentRecipients.count;
|
||||
}
|
||||
|
||||
- (void)updateWithSentRecipient:(NSString *)contactId transaction:(YapDatabaseReadWriteTransaction *)transaction
|
||||
{
|
||||
OWSAssert(transaction);
|
||||
[self applyChangeToSelfAndLatestCopy:transaction
|
||||
changeBlock:^(TSOutgoingMessage *message) {
|
||||
[message addSentRecipient:contactId];
|
||||
TSOutgoingMessageRecipientState *_Nullable recipientState
|
||||
= message.recipientStateMap[recipientId];
|
||||
if (!recipientState) {
|
||||
OWSFail(@"%@ Missing recipient state for delivered recipient: %@",
|
||||
self.logTag,
|
||||
recipientId);
|
||||
return;
|
||||
}
|
||||
if (recipientState.state != OWSOutgoingMessageRecipientStateSent) {
|
||||
DDLogWarn(@"%@ marking unsent message as delivered.", self.logTag);
|
||||
}
|
||||
recipientState.state = OWSOutgoingMessageRecipientStateSent;
|
||||
recipientState.deliveryTimestamp = deliveryTimestamp;
|
||||
}];
|
||||
}
|
||||
|
||||
@ -403,25 +580,96 @@ NSString *const kTSOutgoingMessageSentRecipientAll = @"kTSOutgoingMessageSentRec
|
||||
|
||||
[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 *_Nullable recipientState
|
||||
= message.recipientStateMap[recipientId];
|
||||
if (!recipientState) {
|
||||
OWSFail(@"%@ Missing recipient state for delivered recipient: %@",
|
||||
self.logTag,
|
||||
recipientId);
|
||||
return;
|
||||
}
|
||||
if (recipientState.state != OWSOutgoingMessageRecipientStateSent) {
|
||||
DDLogWarn(@"%@ marking unsent message as delivered.", self.logTag);
|
||||
}
|
||||
recipientState.state = OWSOutgoingMessageRecipientStateSent;
|
||||
recipientState.readTimestamp = @(readTimestamp);
|
||||
}];
|
||||
}
|
||||
|
||||
- (void)updateWithWasSentFromLinkedDeviceWithTransaction:(YapDatabaseReadWriteTransaction *)transaction
|
||||
{
|
||||
OWSAssert(transaction);
|
||||
|
||||
[self applyChangeToSelfAndLatestCopy:transaction
|
||||
changeBlock:^(TSOutgoingMessage *message) {
|
||||
// Mark any "sending" recipients as "sent."
|
||||
for (TSOutgoingMessageRecipientState *recipientState in message.recipientStateMap
|
||||
.allValues) {
|
||||
if (recipientState.state == OWSOutgoingMessageRecipientStateSending) {
|
||||
recipientState.state = OWSOutgoingMessageRecipientStateSent;
|
||||
}
|
||||
}
|
||||
[message setIsFromLinkedDevice:YES];
|
||||
}];
|
||||
}
|
||||
|
||||
- (void)updateWithSendingToSingleGroupRecipient:(NSString *)singleGroupRecipient
|
||||
transaction:(YapDatabaseReadWriteTransaction *)transaction
|
||||
{
|
||||
OWSAssert(transaction);
|
||||
OWSAssert(singleGroupRecipient.length > 0);
|
||||
|
||||
[self applyChangeToSelfAndLatestCopy:transaction
|
||||
changeBlock:^(TSOutgoingMessage *message) {
|
||||
TSOutgoingMessageRecipientState *recipientState =
|
||||
[TSOutgoingMessageRecipientState new];
|
||||
recipientState.state = OWSOutgoingMessageRecipientStateSending;
|
||||
[message setRecipientStateMap:@{
|
||||
singleGroupRecipient : recipientState,
|
||||
}];
|
||||
}];
|
||||
}
|
||||
|
||||
- (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;
|
||||
}
|
||||
}
|
||||
}];
|
||||
}
|
||||
#pragma mark -
|
||||
|
||||
- (OWSSignalServiceProtosDataMessageBuilder *)dataMessageBuilder
|
||||
@ -437,7 +685,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,
|
||||
|
||||
@ -44,9 +44,8 @@ static NSString *const OWSFailedMessagesJobMessageStateIndex = @"index_outoing_m
|
||||
|
||||
NSMutableArray<NSString *> *messageIds = [NSMutableArray new];
|
||||
|
||||
NSString *formattedString = [NSString stringWithFormat:@"WHERE %@ == %d",
|
||||
OWSFailedMessagesJobMessageStateColumn,
|
||||
(int)TSOutgoingMessageStateAttemptingOut];
|
||||
NSString *formattedString = [NSString
|
||||
stringWithFormat:@"WHERE %@ == %d", OWSFailedMessagesJobMessageStateColumn, (int)TSOutgoingMessageStateSending];
|
||||
YapDatabaseQuery *query = [YapDatabaseQuery queryWithFormat:formattedString];
|
||||
[[transaction ext:OWSFailedMessagesJobMessageStateIndex]
|
||||
enumerateKeysMatchingQuery:query
|
||||
@ -83,8 +82,8 @@ static NSString *const OWSFailedMessagesJobMessageStateIndex = @"index_outoing_m
|
||||
readWriteWithBlock:^(YapDatabaseReadWriteTransaction *_Nonnull transaction) {
|
||||
[self enumerateAttemptingOutMessagesWithBlock:^(TSOutgoingMessage *message) {
|
||||
// sanity check
|
||||
OWSAssert(message.messageState == TSOutgoingMessageStateAttemptingOut);
|
||||
if (message.messageState != TSOutgoingMessageStateAttemptingOut) {
|
||||
OWSAssert(message.messageState == TSOutgoingMessageStateSending);
|
||||
if (message.messageState != TSOutgoingMessageStateSending) {
|
||||
DDLogError(@"%@ Refusing to mark as unsent message with state: %d",
|
||||
self.logTag,
|
||||
(int)message.messageState);
|
||||
@ -92,8 +91,8 @@ static NSString *const OWSFailedMessagesJobMessageStateIndex = @"index_outoing_m
|
||||
}
|
||||
|
||||
DDLogDebug(@"%@ marking message as unsent: %@", self.logTag, message.uniqueId);
|
||||
[message updateWithMessageState:TSOutgoingMessageStateUnsent transaction:transaction];
|
||||
OWSAssert(message.messageState == TSOutgoingMessageStateUnsent);
|
||||
[message updateWithAllSendingRecipientsMarkedAsFailedWithTansaction:transaction];
|
||||
OWSAssert(message.messageState == TSOutgoingMessageStateFailed);
|
||||
|
||||
count++;
|
||||
}
|
||||
|
||||
@ -262,9 +262,9 @@ NS_ASSUME_NONNULL_BEGIN
|
||||
timestamp);
|
||||
}
|
||||
for (TSOutgoingMessage *outgoingMessage in messages) {
|
||||
[outgoingMessage updateWithDeliveredToRecipientId:recipientId
|
||||
deliveryTimestamp:deliveryTimestamp
|
||||
transaction:transaction];
|
||||
[outgoingMessage updateWithDeliveredRecipient:recipientId
|
||||
deliveryTimestamp:deliveryTimestamp
|
||||
transaction:transaction];
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -891,7 +891,7 @@ NS_ASSUME_NONNULL_BEGIN
|
||||
[TSOutgoingMessage outgoingMessageInThread:gThread groupMetaMessage:TSGroupMessageUpdate];
|
||||
[message updateWithCustomMessage:updateGroupInfo transaction:transaction];
|
||||
// Only send this group update to the requester.
|
||||
[message updateWithSingleGroupRecipient:envelope.source transaction:transaction];
|
||||
[message updateWithSendingToSingleGroupRecipient:envelope.source transaction:transaction];
|
||||
|
||||
[self sendGroupUpdateForThread:gThread message:message];
|
||||
}
|
||||
|
||||
@ -186,7 +186,8 @@ void AssertIsOnSendingQueue()
|
||||
|
||||
- (void)didSucceed
|
||||
{
|
||||
[self.message updateWithMessageState:TSOutgoingMessageStateSentToService];
|
||||
OWSAssert(self.message.messageState == TSOutgoingMessageStateSent);
|
||||
|
||||
self.successHandler();
|
||||
}
|
||||
|
||||
@ -311,7 +312,8 @@ NSString *const OWSMessageSenderRateLimitedException = @"RateLimitedException";
|
||||
|
||||
// All outgoing messages should be saved at the time they are enqueued.
|
||||
[message saveWithTransaction:transaction];
|
||||
[message updateWithMessageState:TSOutgoingMessageStateAttemptingOut transaction:transaction];
|
||||
// When we start a message send, all "failed" recipients should be marked as "sending".
|
||||
[message updateWithMarkingAllUnsentRecipientsAsSendingWithTransaction:transaction];
|
||||
}];
|
||||
|
||||
NSOperationQueue *sendingQueue = [self sendingQueueForMessage:message];
|
||||
@ -417,11 +419,15 @@ NSString *const OWSMessageSenderRateLimitedException = @"RateLimitedException";
|
||||
});
|
||||
}
|
||||
|
||||
- (NSArray<SignalRecipient *> *)getRecipients:(NSArray<NSString *> *)identifiers error:(NSError **)error
|
||||
- (NSArray<SignalRecipient *> *)getRecipientsForRecipientIds:(NSArray<NSString *> *)recipientIds error:(NSError **)error
|
||||
{
|
||||
OWSAssert(error);
|
||||
|
||||
error = nil;
|
||||
|
||||
NSMutableArray<SignalRecipient *> *recipients = [NSMutableArray new];
|
||||
|
||||
for (NSString *recipientId in identifiers) {
|
||||
for (NSString *recipientId in recipientIds) {
|
||||
SignalRecipient *existingRecipient = [SignalRecipient recipientWithTextSecureIdentifier:recipientId];
|
||||
|
||||
if (existingRecipient) {
|
||||
@ -451,11 +457,36 @@ NSString *const OWSMessageSenderRateLimitedException = @"RateLimitedException";
|
||||
TSThread *thread = message.thread;
|
||||
|
||||
if ([thread isKindOfClass:[TSGroupThread class]]) {
|
||||
|
||||
TSGroupThread *gThread = (TSGroupThread *)thread;
|
||||
|
||||
// Send to the intersection of:
|
||||
//
|
||||
// * "sending" recipients of the message.
|
||||
// * members of the group.
|
||||
//
|
||||
// I.e. try to send a message IFF:
|
||||
//
|
||||
// * The recipient was in the group when the message was first tried to be sent.
|
||||
// * The recipient is still in the group.
|
||||
// * The recipient is in the "sending" state.
|
||||
NSMutableSet<NSString *> *obsoleteRecipientIds = [NSMutableSet setWithArray:message.sendingRecipientIds];
|
||||
[obsoleteRecipientIds minusSet:[NSSet setWithArray:gThread.groupModel.groupMemberIds]];
|
||||
if (obsoleteRecipientIds.count > 0) {
|
||||
[self.dbConnection readWriteWithBlock:^(YapDatabaseReadWriteTransaction *transaction) {
|
||||
for (NSString *recipientId in obsoleteRecipientIds) {
|
||||
// Mark this recipient as "skipped".
|
||||
[message updateWithSkippedRecipient:recipientId transaction:transaction];
|
||||
}
|
||||
}];
|
||||
}
|
||||
|
||||
NSMutableSet<NSString *> *sendingRecipientIds = [NSMutableSet setWithArray:message.sendingRecipientIds];
|
||||
[sendingRecipientIds intersectSet:[NSSet setWithArray:gThread.groupModel.groupMemberIds]];
|
||||
|
||||
NSError *error;
|
||||
NSArray<SignalRecipient *> *recipients =
|
||||
[self getRecipients:gThread.groupModel.groupMemberIds error:&error];
|
||||
[self getRecipientsForRecipientIds:sendingRecipientIds.allObjects error:&error];
|
||||
|
||||
if (recipients.count == 0) {
|
||||
if (!error) {
|
||||
@ -491,7 +522,7 @@ NSString *const OWSMessageSenderRateLimitedException = @"RateLimitedException";
|
||||
// you might, for example, have a pending outgoing message when
|
||||
// you block them.
|
||||
OWSAssert(recipientContactId.length > 0);
|
||||
if ([_blockingManager isRecipientIdBlocked:recipientContactId]) {
|
||||
if ([self.blockingManager isRecipientIdBlocked:recipientContactId]) {
|
||||
DDLogInfo(@"%@ skipping 1:1 send to blocked contact: %@", self.logTag, recipientContactId);
|
||||
NSError *error = OWSErrorMakeMessageSendFailedToBlockListError();
|
||||
// No need to retry - the user will continue to be blocked.
|
||||
@ -560,10 +591,6 @@ NSString *const OWSMessageSenderRateLimitedException = @"RateLimitedException";
|
||||
thread:thread
|
||||
attempts:OWSMessageSenderRetryAttempts
|
||||
success:^{
|
||||
DDLogInfo(@"%@ Marking group message as sent to recipient: %@", self.logTag, recipient.uniqueId);
|
||||
[self.dbConnection readWriteWithBlock:^(YapDatabaseReadWriteTransaction *transaction) {
|
||||
[message updateWithSentRecipient:recipient.uniqueId transaction:transaction];
|
||||
}];
|
||||
[futureSource trySetResult:@1];
|
||||
}
|
||||
failure:^(NSError *error) {
|
||||
@ -580,6 +607,7 @@ NSString *const OWSMessageSenderRateLimitedException = @"RateLimitedException";
|
||||
failure:(RetryableFailureHandler)failureHandler
|
||||
{
|
||||
[self saveGroupMessage:message inThread:thread];
|
||||
|
||||
NSMutableArray<TOCFuture *> *futures = [NSMutableArray array];
|
||||
|
||||
for (SignalRecipient *recipient in recipients) {
|
||||
@ -589,17 +617,6 @@ NSString *const OWSMessageSenderRateLimitedException = @"RateLimitedException";
|
||||
if ([recipientId isEqualToString:[TSAccountManager localNumber]]) {
|
||||
continue;
|
||||
}
|
||||
// We don't need to sent the message to all group members if
|
||||
// it has a "single group recipient".
|
||||
if (message.singleGroupRecipient && ![message.singleGroupRecipient isEqualToString:recipientId]) {
|
||||
continue;
|
||||
}
|
||||
if ([message wasSentToRecipient:recipientId]) {
|
||||
// Skip recipients we have already sent this message to (on an
|
||||
// earlier retry, perhaps).
|
||||
DDLogInfo(@"%@ Skipping group message recipient; already sent: %@", self.logTag, recipient.uniqueId);
|
||||
continue;
|
||||
}
|
||||
|
||||
// ...otherwise we send.
|
||||
[futures addObject:[self sendMessageFuture:message recipient:recipient thread:thread]];
|
||||
@ -679,6 +696,11 @@ NSString *const OWSMessageSenderRateLimitedException = @"RateLimitedException";
|
||||
thread:(TSThread *)thread
|
||||
{
|
||||
[self.dbConnection asyncReadWriteWithBlock:^(YapDatabaseReadWriteTransaction *transaction) {
|
||||
if (thread.isGroupThread) {
|
||||
// Mark as "skipped" group members who no longer have signal accounts.
|
||||
[message updateWithSkippedRecipient:recipient.recipientId transaction:transaction];
|
||||
}
|
||||
|
||||
[recipient removeWithTransaction:transaction];
|
||||
[[TSInfoMessage userNotRegisteredMessageInThread:thread]
|
||||
saveWithTransaction:transaction];
|
||||
@ -904,7 +926,11 @@ NSString *const OWSMessageSenderRateLimitedException = @"RateLimitedException";
|
||||
}
|
||||
|
||||
dispatch_async([OWSDispatch sendingQueue], ^{
|
||||
[recipient save];
|
||||
[self.dbConnection readWriteWithBlock:^(YapDatabaseReadWriteTransaction *transaction) {
|
||||
[recipient saveWithTransaction:transaction];
|
||||
[message updateWithSentRecipient:recipient.uniqueId transaction:transaction];
|
||||
}];
|
||||
|
||||
[self handleMessageSentLocally:message];
|
||||
successHandler();
|
||||
});
|
||||
|
||||
Loading…
Reference in New Issue
Block a user