From c5c464378245d4adbf7c01e2f402f138e5e58cb6 Mon Sep 17 00:00:00 2001 From: Matthew Chen Date: Mon, 12 Jun 2017 11:51:25 -0400 Subject: [PATCH 1/5] Rework how messages are marked read. // FREEBIE --- src/Contacts/TSThread.h | 1 - src/Contacts/TSThread.m | 9 +--- src/Devices/OWSReadReceiptsProcessor.m | 34 ++++++++++++++- src/Messages/Interactions/TSErrorMessage.h | 2 - src/Messages/Interactions/TSErrorMessage.m | 17 ++++---- src/Messages/Interactions/TSIncomingMessage.h | 14 ------- src/Messages/Interactions/TSIncomingMessage.m | 42 ++++++++----------- src/Messages/Interactions/TSInfoMessage.h | 2 - src/Messages/Interactions/TSInfoMessage.m | 17 ++++---- src/Messages/OWSIncomingMessageReadObserver.m | 7 ++-- src/Messages/OWSReadTracking.h | 6 +-- src/Messages/TSCall.m | 20 ++++----- src/Messages/TSMessagesManager.m | 3 +- 13 files changed, 87 insertions(+), 87 deletions(-) diff --git a/src/Contacts/TSThread.h b/src/Contacts/TSThread.h index 1a433684c..7a25f0882 100644 --- a/src/Contacts/TSThread.h +++ b/src/Contacts/TSThread.h @@ -72,7 +72,6 @@ NS_ASSUME_NONNULL_BEGIN - (BOOL)hasSafetyNumbers; -- (void)markAllAsRead; - (void)markAllAsReadWithTransaction:(YapDatabaseReadWriteTransaction *)transaction; /** diff --git a/src/Contacts/TSThread.m b/src/Contacts/TSThread.m index fb0e55d2e..7ef6677cb 100644 --- a/src/Contacts/TSThread.m +++ b/src/Contacts/TSThread.m @@ -228,20 +228,13 @@ NS_ASSUME_NONNULL_BEGIN - (void)markAllAsReadWithTransaction:(YapDatabaseReadWriteTransaction *)transaction { for (id message in [self unseenMessagesWithTransaction:transaction]) { - [message markAsReadLocallyWithTransaction:transaction]; + [message markAsReadWithTransaction:transaction sendReadReceipt:YES]; } // Just to be defensive, we'll also check for unread messages. OWSAssert([self unseenMessagesWithTransaction:transaction].count < 1); } -- (void)markAllAsRead -{ - [self.dbConnection readWriteWithBlock:^(YapDatabaseReadWriteTransaction *_Nonnull transaction) { - [self markAllAsReadWithTransaction:transaction]; - }]; -} - - (TSInteraction *) lastInteraction { __block TSInteraction *last; [TSStorageManager.sharedManager.dbConnection readWithBlock:^(YapDatabaseReadTransaction *transaction){ diff --git a/src/Devices/OWSReadReceiptsProcessor.m b/src/Devices/OWSReadReceiptsProcessor.m index 30d070ef7..8816a4cb4 100644 --- a/src/Devices/OWSReadReceiptsProcessor.m +++ b/src/Devices/OWSReadReceiptsProcessor.m @@ -7,7 +7,10 @@ #import "OWSReadReceipt.h" #import "OWSSignalServiceProtos.pb.h" #import "TSContactThread.h" +#import "TSDatabaseView.h" #import "TSIncomingMessage.h" +#import "TSStorageManager.h" +#import NS_ASSUME_NONNULL_BEGIN @@ -82,7 +85,36 @@ NSString *const OWSReadReceiptsProcessorMarkedMessageAsReadNotification = TSIncomingMessage *message = [TSIncomingMessage findMessageWithAuthorId:readReceipt.senderId timestamp:readReceipt.timestamp]; if (message) { - [message markAsReadFromReadReceipt]; + OWSAssert(message.thread); + + NSMutableArray> *interactionToMarkAsRead = [NSMutableArray new]; + [self.storageManager.dbConnection readWriteWithBlock:^(YapDatabaseReadWriteTransaction *transaction) { + [[transaction ext:TSUnseenDatabaseViewExtensionName] + enumerateRowsInGroup:message.uniqueThreadId + usingBlock:^(NSString *collection, + NSString *key, + id object, + id metadata, + NSUInteger index, + BOOL *stop) { + + TSInteraction *interaction = object; + if (interaction.timestampForSorting < message.timestampForSorting) { + *stop = YES; + return; + } + + id possiblyRead = (id)object; + OWSAssert(!possiblyRead.read); + [interactionToMarkAsRead addObject:possiblyRead]; + }]; + + for (id possiblyRead in interactionToMarkAsRead) { + // Don't send a read receipt in response to a read receipt. + [possiblyRead markAsReadWithTransaction:transaction sendReadReceipt:NO]; + } + }]; + [OWSDisappearingMessagesJob setExpirationForMessage:message expirationStartedAt:readReceipt.timestamp]; // If it was previously saved, no need to keep it around any longer. [readReceipt remove]; diff --git a/src/Messages/Interactions/TSErrorMessage.h b/src/Messages/Interactions/TSErrorMessage.h index b68a9ea82..21dfa59a9 100644 --- a/src/Messages/Interactions/TSErrorMessage.h +++ b/src/Messages/Interactions/TSErrorMessage.h @@ -58,8 +58,6 @@ typedef NS_ENUM(int32_t, TSErrorMessageType) { @property (nonatomic, readonly) TSErrorMessageType errorType; @property (nullable, nonatomic, readonly) NSString *recipientId; -- (void)markAsReadLocallyWithTransaction:(YapDatabaseReadWriteTransaction *)transaction; - @end NS_ASSUME_NONNULL_END diff --git a/src/Messages/Interactions/TSErrorMessage.m b/src/Messages/Interactions/TSErrorMessage.m index 0623f84f4..aa269701a 100644 --- a/src/Messages/Interactions/TSErrorMessage.m +++ b/src/Messages/Interactions/TSErrorMessage.m @@ -167,19 +167,20 @@ NSUInteger TSErrorMessageSchemaVersion = 1; return NO; } -- (void)markAsReadLocally -{ - [self.dbConnection readWriteWithBlock:^(YapDatabaseReadWriteTransaction *transaction) { - [self markAsReadLocallyWithTransaction:transaction]; - }]; -} - -- (void)markAsReadLocallyWithTransaction:(YapDatabaseReadWriteTransaction *)transaction +- (void)markAsReadWithTransaction:(YapDatabaseReadWriteTransaction *)transaction sendReadReceipt:(BOOL)sendReadReceipt { OWSAssert(transaction); + + if (_read) { + return; + } + DDLogInfo(@"%@ marking as read uniqueId: %@ which has timestamp: %llu", self.tag, self.uniqueId, self.timestamp); _read = YES; [self saveWithTransaction:transaction]; + [self touchThreadWithTransaction:transaction]; + + // Ignore sendReadReceipt; it doesn't apply to error messages. } #pragma mark - Logging diff --git a/src/Messages/Interactions/TSIncomingMessage.h b/src/Messages/Interactions/TSIncomingMessage.h index 3b254920a..262852131 100644 --- a/src/Messages/Interactions/TSIncomingMessage.h +++ b/src/Messages/Interactions/TSIncomingMessage.h @@ -108,20 +108,6 @@ extern NSString *const TSIncomingMessageWasReadOnThisDeviceNotification; // This will be 0 for messages created before we were tracking sourceDeviceId @property (nonatomic, readonly) UInt32 sourceDeviceId; -/* - * Marks a message as having been read on this device (as opposed to responding to a remote read receipt). - * - */ -- (void)markAsReadLocally; -// TODO possible to remove? -- (void)markAsReadLocallyWithTransaction:(YapDatabaseReadWriteTransaction *)transaction; - -/** - * Similar to markAsReadWithTransaction, but doesn't send out read receipts. - * Used for *responding* to a remote read receipt. - */ -- (void)markAsReadFromReadReceipt; - @end NS_ASSUME_NONNULL_END diff --git a/src/Messages/Interactions/TSIncomingMessage.m b/src/Messages/Interactions/TSIncomingMessage.m index 6e9db8409..91cd7f6bb 100644 --- a/src/Messages/Interactions/TSIncomingMessage.m +++ b/src/Messages/Interactions/TSIncomingMessage.m @@ -3,6 +3,8 @@ // #import "TSIncomingMessage.h" +#import "OWSDisappearingMessagesConfiguration.h" +#import "OWSDisappearingMessagesJob.h" #import "TSContactThread.h" #import "TSDatabaseSecondaryIndexes.h" #import "TSGroupThread.h" @@ -115,37 +117,29 @@ NSString *const TSIncomingMessageWasReadOnThisDeviceNotification = @"TSIncomingM return YES; } -- (void)markAsReadFromReadReceipt +- (void)markAsReadWithTransaction:(YapDatabaseReadWriteTransaction *)transaction sendReadReceipt:(BOOL)sendReadReceipt { - [self.dbConnection readWriteWithBlock:^(YapDatabaseReadWriteTransaction *transaction) { - [self markAsReadWithoutNotificationWithTransaction:transaction]; - }]; -} - -- (void)markAsReadLocallyWithTransaction:(YapDatabaseReadWriteTransaction *)transaction -{ - [self markAsReadWithoutNotificationWithTransaction:transaction]; - [[NSNotificationCenter defaultCenter] postNotificationName:TSIncomingMessageWasReadOnThisDeviceNotification - object:self]; -} + OWSAssert(transaction); -- (void)markAsReadLocally -{ - [self.dbConnection readWriteWithBlock:^(YapDatabaseReadWriteTransaction *transaction) { - [self markAsReadWithoutNotificationWithTransaction:transaction]; - }]; - // Notification must happen outside of the transaction, else we'll likely crash when the notification receiver - // tries to do anything with the DB. - [[NSNotificationCenter defaultCenter] postNotificationName:TSIncomingMessageWasReadOnThisDeviceNotification - object:self]; -} + if (_read) { + return; + } -- (void)markAsReadWithoutNotificationWithTransaction:(YapDatabaseReadWriteTransaction *)transaction -{ DDLogInfo(@"%@ marking as read uniqueId: %@ which has timestamp: %llu", self.tag, self.uniqueId, self.timestamp); _read = YES; [self saveWithTransaction:transaction]; [self touchThreadWithTransaction:transaction]; + + [OWSDisappearingMessagesJob setExpirationForMessage:self]; + + if (sendReadReceipt) { + // Notification must happen outside of the transaction, else we'll likely crash when the notification receiver + // tries to do anything with the DB. + dispatch_async(dispatch_get_main_queue(), ^{ + [[NSNotificationCenter defaultCenter] postNotificationName:TSIncomingMessageWasReadOnThisDeviceNotification + object:self]; + }); + } } #pragma mark - Logging diff --git a/src/Messages/Interactions/TSInfoMessage.h b/src/Messages/Interactions/TSInfoMessage.h index 4bd00c871..fa9278e2d 100644 --- a/src/Messages/Interactions/TSInfoMessage.h +++ b/src/Messages/Interactions/TSInfoMessage.h @@ -44,8 +44,6 @@ typedef NS_ENUM(NSInteger, TSInfoMessageType) { expiresInSeconds:(uint32_t)expiresInSeconds expireStartedAt:(uint64_t)expireStartedAt NS_UNAVAILABLE; -- (void)markAsReadLocallyWithTransaction:(YapDatabaseReadWriteTransaction *)transaction; - @end NS_ASSUME_NONNULL_END diff --git a/src/Messages/Interactions/TSInfoMessage.m b/src/Messages/Interactions/TSInfoMessage.m index ad6fa981f..ecf304978 100644 --- a/src/Messages/Interactions/TSInfoMessage.m +++ b/src/Messages/Interactions/TSInfoMessage.m @@ -109,19 +109,20 @@ NSUInteger TSInfoMessageSchemaVersion = 1; return NO; } -- (void)markAsReadLocally -{ - [self.dbConnection readWriteWithBlock:^(YapDatabaseReadWriteTransaction *transaction) { - [self markAsReadLocallyWithTransaction:transaction]; - }]; -} - -- (void)markAsReadLocallyWithTransaction:(YapDatabaseReadWriteTransaction *)transaction +- (void)markAsReadWithTransaction:(YapDatabaseReadWriteTransaction *)transaction sendReadReceipt:(BOOL)sendReadReceipt { OWSAssert(transaction); + + if (_read) { + return; + } + DDLogInfo(@"%@ marking as read uniqueId: %@ which has timestamp: %llu", self.tag, self.uniqueId, self.timestamp); _read = YES; [self saveWithTransaction:transaction]; + [self touchThreadWithTransaction:transaction]; + + // Ignore sendReadReceipt; it doesn't apply to info messages. } #pragma mark - Logging diff --git a/src/Messages/OWSIncomingMessageReadObserver.m b/src/Messages/OWSIncomingMessageReadObserver.m index 13c16ff6f..20c581e4d 100644 --- a/src/Messages/OWSIncomingMessageReadObserver.m +++ b/src/Messages/OWSIncomingMessageReadObserver.m @@ -4,8 +4,6 @@ #import "OWSIncomingMessageReadObserver.h" #import "NSDate+millisecondTimeStamp.h" -#import "OWSDisappearingMessagesConfiguration.h" -#import "OWSDisappearingMessagesJob.h" #import "OWSSendReadReceiptsJob.h" #import "TSIncomingMessage.h" @@ -13,7 +11,7 @@ NS_ASSUME_NONNULL_BEGIN @interface OWSIncomingMessageReadObserver () -@property BOOL isObserving; +@property (nonatomic) BOOL isObserving; @property (nonatomic, readonly) OWSSendReadReceiptsJob *sendReadReceiptsJob; @end @@ -41,6 +39,8 @@ NS_ASSUME_NONNULL_BEGIN - (void)startObserving { + OWSAssert([NSThread isMainThread]); + if (self.isObserving) { return; } @@ -60,7 +60,6 @@ NS_ASSUME_NONNULL_BEGIN } TSIncomingMessage *message = (TSIncomingMessage *)notification.object; - [OWSDisappearingMessagesJob setExpirationForMessage:message]; [self.sendReadReceiptsJob runWith:message]; } diff --git a/src/Messages/OWSReadTracking.h b/src/Messages/OWSReadTracking.h index fa981fc9a..6712f4f16 100644 --- a/src/Messages/OWSReadTracking.h +++ b/src/Messages/OWSReadTracking.h @@ -20,10 +20,8 @@ - (BOOL)shouldAffectUnreadCounts; /** - * Call when the user viewed the message/call on this device. "locally" as opposed to being notified via a read receipt - * sync message of a remote read. + * Used for *responding* to a remote read receipt or in response to user activity. */ -- (void)markAsReadLocally; -- (void)markAsReadLocallyWithTransaction:(YapDatabaseReadWriteTransaction *)transaction; +- (void)markAsReadWithTransaction:(YapDatabaseReadWriteTransaction *)transaction sendReadReceipt:(BOOL)sendReadReceipt; @end diff --git a/src/Messages/TSCall.m b/src/Messages/TSCall.m index 7e135e774..caa6338be 100644 --- a/src/Messages/TSCall.m +++ b/src/Messages/TSCall.m @@ -86,22 +86,22 @@ NSUInteger TSCallCurrentSchemaVersion = 1; return YES; } -- (void)markAsReadLocallyWithTransaction:(YapDatabaseReadWriteTransaction *)transaction +- (void)markAsReadWithTransaction:(YapDatabaseReadWriteTransaction *)transaction sendReadReceipt:(BOOL)sendReadReceipt { - DDLogInfo(@"%@ marking as read uniqueId: %@ which has timestamp: %llu", self.tag, self.uniqueId, self.timestamp); + OWSAssert(transaction); + + if (_read) { + return; + } + DDLogInfo(@"%@ marking as read uniqueId: %@ which has timestamp: %llu", self.tag, self.uniqueId, self.timestamp); _read = YES; [self saveWithTransaction:transaction]; - - // redraw any thread-related unread count UI. [self touchThreadWithTransaction:transaction]; -} -- (void)markAsReadLocally -{ - [self.dbConnection readWriteWithBlock:^(YapDatabaseReadWriteTransaction *_Nonnull transaction) { - [self markAsReadLocallyWithTransaction:transaction]; - }]; + // Ignore sendReadReceipt; it doesn't apply to calls. + // + // TODO: Should we update expiration of calls? } #pragma mark - Methods diff --git a/src/Messages/TSMessagesManager.m b/src/Messages/TSMessagesManager.m index bf2129a85..7bf459997 100644 --- a/src/Messages/TSMessagesManager.m +++ b/src/Messages/TSMessagesManager.m @@ -924,7 +924,8 @@ NS_ASSUME_NONNULL_BEGIN // automatically marked as read. BOOL shouldMarkMessageAsRead = [envelope.source isEqualToString:localNumber]; if (shouldMarkMessageAsRead) { - [incomingMessage markAsReadLocallyWithTransaction:transaction]; + // Don't send a read receipt for messages sent by ourselves. + [incomingMessage markAsReadWithTransaction:transaction sendReadReceipt:NO]; } // Other clients allow attachments to be sent along with body, we want the text displayed as a separate From dc9a2253d5bfaf387a21ed2d80d95f0d861a9f40 Mon Sep 17 00:00:00 2001 From: Matthew Chen Date: Mon, 12 Jun 2017 12:28:23 -0400 Subject: [PATCH 2/5] Rework how messages are marked read. // FREEBIE --- src/Devices/OWSReadReceiptsProcessor.m | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/src/Devices/OWSReadReceiptsProcessor.m b/src/Devices/OWSReadReceiptsProcessor.m index 8816a4cb4..c531ce8b0 100644 --- a/src/Devices/OWSReadReceiptsProcessor.m +++ b/src/Devices/OWSReadReceiptsProcessor.m @@ -87,7 +87,9 @@ NSString *const OWSReadReceiptsProcessorMarkedMessageAsReadNotification = if (message) { OWSAssert(message.thread); - NSMutableArray> *interactionToMarkAsRead = [NSMutableArray new]; + // Mark all unread messages in this thread that are as old or older than the read + // receipt. + NSMutableArray> *interactionsToMarkAsRead = [NSMutableArray new]; [self.storageManager.dbConnection readWriteWithBlock:^(YapDatabaseReadWriteTransaction *transaction) { [[transaction ext:TSUnseenDatabaseViewExtensionName] enumerateRowsInGroup:message.uniqueThreadId @@ -99,21 +101,22 @@ NSString *const OWSReadReceiptsProcessorMarkedMessageAsReadNotification = BOOL *stop) { TSInteraction *interaction = object; - if (interaction.timestampForSorting < message.timestampForSorting) { + if (interaction.timestampForSorting <= message.timestampForSorting) { *stop = YES; return; } id possiblyRead = (id)object; OWSAssert(!possiblyRead.read); - [interactionToMarkAsRead addObject:possiblyRead]; + [interactionsToMarkAsRead addObject:possiblyRead]; }]; - for (id possiblyRead in interactionToMarkAsRead) { + for (id possiblyRead in interactionsToMarkAsRead) { // Don't send a read receipt in response to a read receipt. [possiblyRead markAsReadWithTransaction:transaction sendReadReceipt:NO]; } }]; + OWSAssert(interactionsToMarkAsRead.count > 0); [OWSDisappearingMessagesJob setExpirationForMessage:message expirationStartedAt:readReceipt.timestamp]; // If it was previously saved, no need to keep it around any longer. From 5e5071141258157318090b3cb546323e78f84a21 Mon Sep 17 00:00:00 2001 From: Matthew Chen Date: Mon, 12 Jun 2017 13:29:17 -0400 Subject: [PATCH 3/5] =?UTF-8?q?Don=E2=80=99t=20update=20expiration=20for?= =?UTF-8?q?=20messages=20twice.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit // FREEBIE --- src/Devices/OWSReadReceiptsProcessor.m | 28 +++++++++++-------- src/Messages/Interactions/TSErrorMessage.m | 6 ++-- src/Messages/Interactions/TSIncomingMessage.m | 8 ++++-- src/Messages/Interactions/TSInfoMessage.m | 6 ++-- src/Messages/OWSReadTracking.h | 4 ++- src/Messages/TSCall.m | 6 ++-- src/Messages/TSMessagesManager.m | 2 +- 7 files changed, 39 insertions(+), 21 deletions(-) diff --git a/src/Devices/OWSReadReceiptsProcessor.m b/src/Devices/OWSReadReceiptsProcessor.m index c531ce8b0..fa753a086 100644 --- a/src/Devices/OWSReadReceiptsProcessor.m +++ b/src/Devices/OWSReadReceiptsProcessor.m @@ -87,7 +87,7 @@ NSString *const OWSReadReceiptsProcessorMarkedMessageAsReadNotification = if (message) { OWSAssert(message.thread); - // Mark all unread messages in this thread that are as old or older than the read + // Mark all unread messages in this thread that are older than message specified in the read // receipt. NSMutableArray> *interactionsToMarkAsRead = [NSMutableArray new]; [self.storageManager.dbConnection readWriteWithBlock:^(YapDatabaseReadWriteTransaction *transaction) { @@ -101,7 +101,7 @@ NSString *const OWSReadReceiptsProcessorMarkedMessageAsReadNotification = BOOL *stop) { TSInteraction *interaction = object; - if (interaction.timestampForSorting <= message.timestampForSorting) { + if (interaction.timestampForSorting > message.timestampForSorting) { *stop = YES; return; } @@ -111,20 +111,26 @@ NSString *const OWSReadReceiptsProcessorMarkedMessageAsReadNotification = [interactionsToMarkAsRead addObject:possiblyRead]; }]; - for (id possiblyRead in interactionsToMarkAsRead) { - // Don't send a read receipt in response to a read receipt. - [possiblyRead markAsReadWithTransaction:transaction sendReadReceipt:NO]; + for (id interaction in interactionsToMarkAsRead) { + // * Don't send a read receipt in response to a read receipt. + // * Don't update expiration; we'll do that in the next statement. + [interaction markAsReadWithTransaction:transaction sendReadReceipt:NO updateExpiration:NO]; + + // Update expiration using the timestamp from the readReceipt. + [OWSDisappearingMessagesJob setExpirationForMessage:(TSMessage *)interaction + expirationStartedAt:readReceipt.timestamp]; + + // Fire event that will cancel any pending notifications for this message. + dispatch_async(dispatch_get_main_queue(), ^{ + [[NSNotificationCenter defaultCenter] + postNotificationName:OWSReadReceiptsProcessorMarkedMessageAsReadNotification + object:(TSMessage *)interaction]; + }); } }]; - OWSAssert(interactionsToMarkAsRead.count > 0); - [OWSDisappearingMessagesJob setExpirationForMessage:message expirationStartedAt:readReceipt.timestamp]; // If it was previously saved, no need to keep it around any longer. [readReceipt remove]; - [[NSNotificationCenter defaultCenter] - postNotificationName:OWSReadReceiptsProcessorMarkedMessageAsReadNotification - object:message]; - } else { DDLogDebug(@"%@ Received read receipt for an unknown message. Saving it for later.", self.tag); [readReceipt save]; diff --git a/src/Messages/Interactions/TSErrorMessage.m b/src/Messages/Interactions/TSErrorMessage.m index aa269701a..9e0046943 100644 --- a/src/Messages/Interactions/TSErrorMessage.m +++ b/src/Messages/Interactions/TSErrorMessage.m @@ -167,7 +167,9 @@ NSUInteger TSErrorMessageSchemaVersion = 1; return NO; } -- (void)markAsReadWithTransaction:(YapDatabaseReadWriteTransaction *)transaction sendReadReceipt:(BOOL)sendReadReceipt +- (void)markAsReadWithTransaction:(YapDatabaseReadWriteTransaction *)transaction + sendReadReceipt:(BOOL)sendReadReceipt + updateExpiration:(BOOL)updateExpiration { OWSAssert(transaction); @@ -180,7 +182,7 @@ NSUInteger TSErrorMessageSchemaVersion = 1; [self saveWithTransaction:transaction]; [self touchThreadWithTransaction:transaction]; - // Ignore sendReadReceipt; it doesn't apply to error messages. + // Ignore sendReadReceipt and updateExpiration; they don't apply to error messages. } #pragma mark - Logging diff --git a/src/Messages/Interactions/TSIncomingMessage.m b/src/Messages/Interactions/TSIncomingMessage.m index 91cd7f6bb..dddbacef9 100644 --- a/src/Messages/Interactions/TSIncomingMessage.m +++ b/src/Messages/Interactions/TSIncomingMessage.m @@ -117,7 +117,9 @@ NSString *const TSIncomingMessageWasReadOnThisDeviceNotification = @"TSIncomingM return YES; } -- (void)markAsReadWithTransaction:(YapDatabaseReadWriteTransaction *)transaction sendReadReceipt:(BOOL)sendReadReceipt +- (void)markAsReadWithTransaction:(YapDatabaseReadWriteTransaction *)transaction + sendReadReceipt:(BOOL)sendReadReceipt + updateExpiration:(BOOL)updateExpiration { OWSAssert(transaction); @@ -130,7 +132,9 @@ NSString *const TSIncomingMessageWasReadOnThisDeviceNotification = @"TSIncomingM [self saveWithTransaction:transaction]; [self touchThreadWithTransaction:transaction]; - [OWSDisappearingMessagesJob setExpirationForMessage:self]; + if (updateExpiration) { + [OWSDisappearingMessagesJob setExpirationForMessage:self]; + } if (sendReadReceipt) { // Notification must happen outside of the transaction, else we'll likely crash when the notification receiver diff --git a/src/Messages/Interactions/TSInfoMessage.m b/src/Messages/Interactions/TSInfoMessage.m index ecf304978..7ae9afa04 100644 --- a/src/Messages/Interactions/TSInfoMessage.m +++ b/src/Messages/Interactions/TSInfoMessage.m @@ -109,7 +109,9 @@ NSUInteger TSInfoMessageSchemaVersion = 1; return NO; } -- (void)markAsReadWithTransaction:(YapDatabaseReadWriteTransaction *)transaction sendReadReceipt:(BOOL)sendReadReceipt +- (void)markAsReadWithTransaction:(YapDatabaseReadWriteTransaction *)transaction + sendReadReceipt:(BOOL)sendReadReceipt + updateExpiration:(BOOL)updateExpiration { OWSAssert(transaction); @@ -122,7 +124,7 @@ NSUInteger TSInfoMessageSchemaVersion = 1; [self saveWithTransaction:transaction]; [self touchThreadWithTransaction:transaction]; - // Ignore sendReadReceipt; it doesn't apply to info messages. + // Ignore sendReadReceipt and updateExpiration; they don't apply to info messages. } #pragma mark - Logging diff --git a/src/Messages/OWSReadTracking.h b/src/Messages/OWSReadTracking.h index 6712f4f16..5236753bb 100644 --- a/src/Messages/OWSReadTracking.h +++ b/src/Messages/OWSReadTracking.h @@ -22,6 +22,8 @@ /** * Used for *responding* to a remote read receipt or in response to user activity. */ -- (void)markAsReadWithTransaction:(YapDatabaseReadWriteTransaction *)transaction sendReadReceipt:(BOOL)sendReadReceipt; +- (void)markAsReadWithTransaction:(YapDatabaseReadWriteTransaction *)transaction + sendReadReceipt:(BOOL)sendReadReceipt + updateExpiration:(BOOL)updateExpiration; @end diff --git a/src/Messages/TSCall.m b/src/Messages/TSCall.m index caa6338be..f885bec78 100644 --- a/src/Messages/TSCall.m +++ b/src/Messages/TSCall.m @@ -86,7 +86,9 @@ NSUInteger TSCallCurrentSchemaVersion = 1; return YES; } -- (void)markAsReadWithTransaction:(YapDatabaseReadWriteTransaction *)transaction sendReadReceipt:(BOOL)sendReadReceipt +- (void)markAsReadWithTransaction:(YapDatabaseReadWriteTransaction *)transaction + sendReadReceipt:(BOOL)sendReadReceipt + updateExpiration:(BOOL)updateExpiration { OWSAssert(transaction); @@ -99,7 +101,7 @@ NSUInteger TSCallCurrentSchemaVersion = 1; [self saveWithTransaction:transaction]; [self touchThreadWithTransaction:transaction]; - // Ignore sendReadReceipt; it doesn't apply to calls. + // Ignore sendReadReceipt and updateExpiration; they don't apply to calls. // // TODO: Should we update expiration of calls? } diff --git a/src/Messages/TSMessagesManager.m b/src/Messages/TSMessagesManager.m index 7bf459997..d3df8d836 100644 --- a/src/Messages/TSMessagesManager.m +++ b/src/Messages/TSMessagesManager.m @@ -925,7 +925,7 @@ NS_ASSUME_NONNULL_BEGIN BOOL shouldMarkMessageAsRead = [envelope.source isEqualToString:localNumber]; if (shouldMarkMessageAsRead) { // Don't send a read receipt for messages sent by ourselves. - [incomingMessage markAsReadWithTransaction:transaction sendReadReceipt:NO]; + [incomingMessage markAsReadWithTransaction:transaction sendReadReceipt:NO updateExpiration:YES]; } // Other clients allow attachments to be sent along with body, we want the text displayed as a separate From dcbb72d8513f0acf512a4eb051b7ca96503345eb Mon Sep 17 00:00:00 2001 From: Matthew Chen Date: Mon, 12 Jun 2017 13:30:04 -0400 Subject: [PATCH 4/5] Filter messages shown in the home view. // FREEBIE --- src/Contacts/TSThread.h | 3 ++ src/Contacts/TSThread.m | 53 +++++++++++++++++++++++++++++----- src/Storage/TSDatabaseView.h | 2 ++ src/Storage/TSDatabaseView.m | 22 ++++++++++++++ src/Storage/TSStorageManager.m | 1 + 5 files changed, 73 insertions(+), 8 deletions(-) diff --git a/src/Contacts/TSThread.h b/src/Contacts/TSThread.h index 7a25f0882..4e4b7fbee 100644 --- a/src/Contacts/TSThread.h +++ b/src/Contacts/TSThread.h @@ -176,6 +176,9 @@ NS_ASSUME_NONNULL_BEGIN // data loss and will resolve all known issues. - (void)updateWithMutedUntilDate:(NSDate *)mutedUntilDate; +// Returns YES IFF the interaction should show up in the inbox as the last message. ++ (BOOL)shouldInteractionAppearInInbox:(TSInteraction *)interaction; + @end NS_ASSUME_NONNULL_END diff --git a/src/Contacts/TSThread.m b/src/Contacts/TSThread.m index 7ef6677cb..fa1aebcb3 100644 --- a/src/Contacts/TSThread.m +++ b/src/Contacts/TSThread.m @@ -228,7 +228,7 @@ NS_ASSUME_NONNULL_BEGIN - (void)markAllAsReadWithTransaction:(YapDatabaseReadWriteTransaction *)transaction { for (id message in [self unseenMessagesWithTransaction:transaction]) { - [message markAsReadWithTransaction:transaction sendReadReceipt:YES]; + [message markAsReadWithTransaction:transaction sendReadReceipt:YES updateExpiration:YES]; } // Just to be defensive, we'll also check for unread messages. @@ -243,6 +243,15 @@ NS_ASSUME_NONNULL_BEGIN return (TSInteraction *)last; } +- (TSInteraction *)lastInteractionForInbox +{ + __block TSInteraction *last; + [TSStorageManager.sharedManager.dbConnection readWithBlock:^(YapDatabaseReadTransaction *transaction) { + last = [[transaction ext:TSThreadInboxMessagesDatabaseViewExtensionName] lastObjectInGroup:self.uniqueId]; + }]; + return (TSInteraction *)last; +} + - (NSDate *)lastMessageDate { if (_lastMessageDate) { return _lastMessageDate; @@ -252,22 +261,50 @@ NS_ASSUME_NONNULL_BEGIN } - (NSString *)lastMessageLabel { - if (self.lastInteraction == nil) { + TSInteraction *interaction = self.lastInteractionForInbox; + if (interaction == nil) { return @""; } else { - return [self lastInteraction].description; + return interaction.description; + } +} + ++ (BOOL)shouldInteractionAppearInInbox:(TSInteraction *)interaction +{ + OWSAssert(interaction); + + if (interaction.isDynamicInteraction) { + DDLogDebug(@"%@ not showing dynamic interaction in inbox: %@", self.tag, interaction.debugDescription); + return NO; } + + + if ([interaction isKindOfClass:[TSErrorMessage class]]) { + TSErrorMessage *errorMessage = (TSErrorMessage *)interaction; + if (errorMessage.errorType == TSErrorMessageNonBlockingIdentityChange) { + // Otherwise all group threads with the recipient will percolate to the top of the inbox, even though + // there was no meaningful interaction. + DDLogDebug( + @"%@ not showing nonblocking identity change in inbox: %@", self.tag, errorMessage.debugDescription); + return NO; + } + } else if ([interaction isKindOfClass:[TSInfoMessage class]]) { + TSInfoMessage *infoMessage = (TSInfoMessage *)interaction; + if (infoMessage.messageType == TSInfoMessageVerificationStateChange) { + DDLogDebug( + @"%@ not showing verification state change in inbox: %@", self.tag, infoMessage.debugDescription); + return NO; + } + } + + return YES; } - (void)updateWithLastMessage:(TSInteraction *)lastMessage transaction:(YapDatabaseReadWriteTransaction *)transaction { OWSAssert(lastMessage); OWSAssert(transaction); - if (lastMessage.isDynamicInteraction) { - DDLogDebug(@"%@ not updating lastMessage for thread: %@ dynamic interaction: %@", - self.tag, - self, - lastMessage.debugDescription); + if (![self.class shouldInteractionAppearInInbox:lastMessage]) { return; } diff --git a/src/Storage/TSDatabaseView.h b/src/Storage/TSDatabaseView.h index e6645a460..c561e1c32 100644 --- a/src/Storage/TSDatabaseView.h +++ b/src/Storage/TSDatabaseView.h @@ -14,6 +14,7 @@ extern NSString *TSSecondaryDevicesGroup; extern NSString *TSThreadDatabaseViewExtensionName; extern NSString *TSMessageDatabaseViewExtensionName; +extern NSString *TSThreadInboxMessagesDatabaseViewExtensionName; extern NSString *TSThreadIncomingMessageDatabaseViewExtensionName; extern NSString *TSThreadOutgoingMessageDatabaseViewExtensionName; extern NSString *TSUnreadDatabaseViewExtensionName; @@ -24,6 +25,7 @@ extern NSString *TSSecondaryDevicesDatabaseViewExtensionName; + (BOOL)registerThreadDatabaseView; + (BOOL)registerThreadInteractionsDatabaseView; ++ (BOOL)registerThreadInboxMessageDatabaseView; + (BOOL)registerThreadIncomingMessagesDatabaseView; + (BOOL)registerThreadOutgoingMessagesDatabaseView; diff --git a/src/Storage/TSDatabaseView.m b/src/Storage/TSDatabaseView.m index 43e6fd065..c5b0141c1 100644 --- a/src/Storage/TSDatabaseView.m +++ b/src/Storage/TSDatabaseView.m @@ -20,6 +20,7 @@ NSString *TSSecondaryDevicesGroup = @"TSSecondaryDevicesGroup"; NSString *TSThreadDatabaseViewExtensionName = @"TSThreadDatabaseViewExtensionName"; NSString *TSMessageDatabaseViewExtensionName = @"TSMessageDatabaseViewExtensionName"; +NSString *TSThreadInboxMessagesDatabaseViewExtensionName = @"TSThreadInboxMessagesDatabaseViewExtensionName"; NSString *TSThreadIncomingMessageDatabaseViewExtensionName = @"TSThreadOutgoingMessageDatabaseViewExtensionName"; NSString *TSThreadOutgoingMessageDatabaseViewExtensionName = @"TSThreadOutgoingMessageDatabaseViewExtensionName"; NSString *TSUnreadDatabaseViewExtensionName = @"TSUnreadDatabaseViewExtensionName"; @@ -147,6 +148,27 @@ NSString *TSSecondaryDevicesDatabaseViewExtensionName = @"TSSecondaryDevicesData version:@"1"]; } ++ (BOOL)registerThreadInboxMessageDatabaseView +{ + YapDatabaseViewGrouping *viewGrouping = [YapDatabaseViewGrouping withObjectBlock:^NSString *( + YapDatabaseReadTransaction *transaction, NSString *collection, NSString *key, id object) { + if ([object isKindOfClass:[TSInteraction class]]) { + TSInteraction *interaction = (TSInteraction *)object; + if (![TSThread shouldInteractionAppearInInbox:interaction]) { + return nil; + } + return interaction.uniqueThreadId; + } else { + OWSAssert(0); + } + return nil; + }]; + + return [self registerMessageDatabaseViewWithName:TSThreadInboxMessagesDatabaseViewExtensionName + viewGrouping:viewGrouping + version:@"1"]; +} + + (BOOL)registerThreadIncomingMessagesDatabaseView { YapDatabaseViewGrouping *viewGrouping = [YapDatabaseViewGrouping withObjectBlock:^NSString *( diff --git a/src/Storage/TSStorageManager.m b/src/Storage/TSStorageManager.m index 2c5d4f79b..01c3e55b1 100644 --- a/src/Storage/TSStorageManager.m +++ b/src/Storage/TSStorageManager.m @@ -196,6 +196,7 @@ static NSString *keychainDBPassAccount = @"TSDatabasePass"; // Register extensions which are essential for rendering threads synchronously [TSDatabaseView registerThreadDatabaseView]; [TSDatabaseView registerThreadInteractionsDatabaseView]; + [TSDatabaseView registerThreadInboxMessageDatabaseView]; [TSDatabaseView registerThreadIncomingMessagesDatabaseView]; [TSDatabaseView registerThreadOutgoingMessagesDatabaseView]; [TSDatabaseView registerUnreadDatabaseView]; From 4a028d32b1419e7b5de9d7e5ed2d07c66a2bc4a2 Mon Sep 17 00:00:00 2001 From: Matthew Chen Date: Mon, 12 Jun 2017 13:33:12 -0400 Subject: [PATCH 5/5] Filter messages shown in the home view. // FREEBIE --- src/Contacts/TSThread.m | 28 ++-------------------------- 1 file changed, 2 insertions(+), 26 deletions(-) diff --git a/src/Contacts/TSThread.m b/src/Contacts/TSThread.m index fa1aebcb3..643dfd203 100644 --- a/src/Contacts/TSThread.m +++ b/src/Contacts/TSThread.m @@ -240,7 +240,7 @@ NS_ASSUME_NONNULL_BEGIN [TSStorageManager.sharedManager.dbConnection readWithBlock:^(YapDatabaseReadTransaction *transaction){ last = [[transaction ext:TSMessageDatabaseViewExtensionName] lastObjectInGroup:self.uniqueId]; }]; - return (TSInteraction *)last; + return last; } - (TSInteraction *)lastInteractionForInbox @@ -249,7 +249,7 @@ NS_ASSUME_NONNULL_BEGIN [TSStorageManager.sharedManager.dbConnection readWithBlock:^(YapDatabaseReadTransaction *transaction) { last = [[transaction ext:TSThreadInboxMessagesDatabaseViewExtensionName] lastObjectInGroup:self.uniqueId]; }]; - return (TSInteraction *)last; + return last; } - (NSDate *)lastMessageDate { @@ -278,7 +278,6 @@ NS_ASSUME_NONNULL_BEGIN return NO; } - if ([interaction isKindOfClass:[TSErrorMessage class]]) { TSErrorMessage *errorMessage = (TSErrorMessage *)interaction; if (errorMessage.errorType == TSErrorMessageNonBlockingIdentityChange) { @@ -309,29 +308,6 @@ NS_ASSUME_NONNULL_BEGIN } NSDate *lastMessageDate = [lastMessage dateForSorting]; - - if ([lastMessage isKindOfClass:[TSErrorMessage class]]) { - TSErrorMessage *errorMessage = (TSErrorMessage *)lastMessage; - if (errorMessage.errorType == TSErrorMessageNonBlockingIdentityChange) { - // Otherwise all group threads with the recipient will percolate to the top of the inbox, even though - // there was no meaningful interaction. - DDLogDebug(@"%@ not updating lastMessage for thread: %@ nonblocking identity change: %@", - self.tag, - self, - errorMessage.debugDescription); - return; - } - } else if ([lastMessage isKindOfClass:[TSInfoMessage class]]) { - TSInfoMessage *infoMessage = (TSInfoMessage *)lastMessage; - if (infoMessage.messageType == TSInfoMessageVerificationStateChange) { - DDLogDebug(@"%@ not updating lastMessage for thread: %@ verification state change: %@", - self.tag, - self, - infoMessage.debugDescription); - return; - } - } - if (!_lastMessageDate || [lastMessageDate timeIntervalSinceDate:self.lastMessageDate] > 0) { _lastMessageDate = lastMessageDate;