diff --git a/Signal/src/ViewControllers/ConversationView/Cells/OWSAudioMessageView.m b/Signal/src/ViewControllers/ConversationView/Cells/OWSAudioMessageView.m index 270c7b781..076bfcda5 100644 --- a/Signal/src/ViewControllers/ConversationView/Cells/OWSAudioMessageView.m +++ b/Signal/src/ViewControllers/ConversationView/Cells/OWSAudioMessageView.m @@ -62,12 +62,9 @@ NS_ASSUME_NONNULL_BEGIN - (CGFloat)audioDurationSeconds { - NSNumber *_Nullable audioDurationSeconds = self.viewItem.audioDurationSeconds; - if (!audioDurationSeconds) { - audioDurationSeconds = @([self.attachmentStream audioDurationSecondsWithoutTransaction]); - self.viewItem.audioDurationSeconds = audioDurationSeconds; - } - return [audioDurationSeconds floatValue]; + OWSAssert(self.viewItem.audioDurationSeconds); + + return [self.viewItem.audioDurationSeconds floatValue]; } - (AudioPlaybackState)audioPlaybackState diff --git a/Signal/src/ViewControllers/ConversationView/ConversationViewController.m b/Signal/src/ViewControllers/ConversationView/ConversationViewController.m index 956efb530..0c7cf3758 100644 --- a/Signal/src/ViewControllers/ConversationView/ConversationViewController.m +++ b/Signal/src/ViewControllers/ConversationView/ConversationViewController.m @@ -97,7 +97,7 @@ static const int kYapDatabasePageSize = 50; static const int kYapDatabaseMaxPageCount = 500; // Never show more than 6*50 = 300 messages in conversation view when user // arrives. -static const int kYapDatabaseMaxInitialPageCount = 6; +static const int kYapDatabaseMaxInitialPageCount = 500; static const int kConversationInitialMaxRangeSize = kYapDatabasePageSize * kYapDatabaseMaxInitialPageCount; static const int kYapDatabaseRangeMaxLength = kYapDatabasePageSize * kYapDatabaseMaxPageCount; static const int kYapDatabaseRangeMinLength = 0; @@ -2883,8 +2883,6 @@ typedef NS_ENUM(NSInteger, MessagesRangeSizeMode) { // b) is inserting new interactions. __block BOOL scrollToBottom = wasAtBottom; - BOOL shouldAnimateUpdates = [self shouldAnimateRowUpdates:rowChanges oldViewItemCount:oldViewItemCount]; - void (^batchUpdates)(void) = ^{ for (YapDatabaseViewRowChange *rowChange in rowChanges) { switch (rowChange.type) { @@ -2893,6 +2891,7 @@ typedef NS_ENUM(NSInteger, MessagesRangeSizeMode) { rowChange.collectionKey, rowChange.indexPath, rowChange.finalIndex); + [DDLog flushLog]; [self.collectionView deleteItemsAtIndexPaths:@[ rowChange.indexPath ]]; YapCollectionKey *collectionKey = rowChange.collectionKey; OWSAssert(collectionKey.key.length > 0); @@ -2903,6 +2902,7 @@ typedef NS_ENUM(NSInteger, MessagesRangeSizeMode) { rowChange.collectionKey, rowChange.newIndexPath, rowChange.finalIndex); + [DDLog flushLog]; [self.collectionView insertItemsAtIndexPaths:@[ rowChange.newIndexPath ]]; // We don't want to reload a row that we just inserted. [rowsThatChangedSize removeObject:@(rowChange.finalIndex)]; @@ -2923,8 +2923,8 @@ typedef NS_ENUM(NSInteger, MessagesRangeSizeMode) { rowChange.indexPath, rowChange.newIndexPath, rowChange.finalIndex); - [self.collectionView deleteItemsAtIndexPaths:@[ rowChange.indexPath ]]; - [self.collectionView insertItemsAtIndexPaths:@[ rowChange.newIndexPath ]]; + [DDLog flushLog]; + [self.collectionView moveItemAtIndexPath:rowChange.indexPath toIndexPath:rowChange.newIndexPath]; // We don't want to reload a row that we just moved. [rowsThatChangedSize removeObject:@(rowChange.finalIndex)]; break; @@ -2934,6 +2934,7 @@ typedef NS_ENUM(NSInteger, MessagesRangeSizeMode) { rowChange.collectionKey, rowChange.indexPath, rowChange.finalIndex); + [DDLog flushLog]; [self.collectionView reloadItemsAtIndexPaths:@[ rowChange.indexPath ]]; // We don't want to reload a row that we've already reloaded. [rowsThatChangedSize removeObject:@(rowChange.finalIndex)]; @@ -2946,40 +2947,151 @@ typedef NS_ENUM(NSInteger, MessagesRangeSizeMode) { // as they may affect which cells show "date" headers or "status" footers. NSMutableArray *rowsToReload = [NSMutableArray new]; for (NSNumber *row in rowsThatChangedSize) { + DDLogVerbose(@"rowsToReload: %@", row); [rowsToReload addObject:[NSIndexPath indexPathForRow:row.integerValue inSection:0]]; } if (rowsToReload.count > 0) { + [DDLog flushLog]; [self.collectionView reloadItemsAtIndexPaths:rowsToReload]; } }; - void (^batchUpdatesCompletion)(BOOL) = ^(BOOL finished) { - OWSAssert([NSThread isMainThread]); - if (!finished) { - DDLogInfo(@"%@ performBatchUpdates did not finish", self.logTag); - } + DDLogVerbose(@"self.viewItems.count: %zd -> %zd", oldViewItemCount, self.viewItems.count); + [DDLog flushLog]; + BOOL shouldReloadCollection = [self shouldReloadCollection:rowChanges]; + if (shouldReloadCollection) { + [UIView performWithoutAnimation:^{ + [self.collectionView reloadData]; + }]; [self updateLastVisibleTimestamp]; + } else { + BOOL shouldAnimateUpdates = [self shouldAnimateRowUpdates:rowChanges oldViewItemCount:oldViewItemCount]; + void (^batchUpdatesCompletion)(BOOL) = ^(BOOL finished) { + OWSAssert([NSThread isMainThread]); - if (scrollToBottom) { - [self scrollToBottomAnimated:shouldAnimateScrollToBottom && shouldAnimateUpdates]; - } - }; + if (!finished) { + DDLogInfo(@"%@ performBatchUpdates did not finish", self.logTag); + } - if (shouldAnimateUpdates) { - [self.collectionView performBatchUpdates:batchUpdates completion:batchUpdatesCompletion]; - } else { - [UIView performWithoutAnimation:^{ + [self updateLastVisibleTimestamp]; + + if (scrollToBottom) { + [self scrollToBottomAnimated:shouldAnimateScrollToBottom && shouldAnimateUpdates]; + } + }; + if (shouldAnimateUpdates) { [self.collectionView performBatchUpdates:batchUpdates completion:batchUpdatesCompletion]; - }]; + } else { + [UIView performWithoutAnimation:^{ + [self.collectionView performBatchUpdates:batchUpdates completion:batchUpdatesCompletion]; + }]; + } } } +- (BOOL)shouldReloadCollection:(NSArray *)rowChanges +{ + OWSAssert(rowChanges); + + BOOL hasDeletes = NO; + BOOL hasInserts = NO; + BOOL hasMoves = NO; + BOOL hasUpdates = NO; + for (YapDatabaseViewRowChange *rowChange in rowChanges) { + switch (rowChange.type) { + case YapDatabaseViewChangeDelete: + DDLogVerbose(@"? YapDatabaseViewChangeDelete: %@, %@, %zd", + rowChange.collectionKey, + rowChange.indexPath, + rowChange.finalIndex); + [DDLog flushLog]; + hasDeletes = YES; + break; + case YapDatabaseViewChangeInsert: + DDLogVerbose(@"...YapDatabaseViewChangeInsert: %@, %@, %zd", + rowChange.collectionKey, + rowChange.newIndexPath, + rowChange.finalIndex); + [DDLog flushLog]; + hasInserts = YES; + break; + case YapDatabaseViewChangeMove: + DDLogVerbose(@"...YapDatabaseViewChangeMove: %@, %@, %@, %zd", + rowChange.collectionKey, + rowChange.indexPath, + rowChange.newIndexPath, + rowChange.finalIndex); + [DDLog flushLog]; + hasMoves = YES; + break; + case YapDatabaseViewChangeUpdate: + DDLogVerbose(@"...YapDatabaseViewChangeUpdate: %@, %@, %zd", + rowChange.collectionKey, + rowChange.indexPath, + rowChange.finalIndex); + [DDLog flushLog]; + hasUpdates = YES; + break; + } + } + if (hasMoves) { + // "Move" changes cannot be safely performed using + // [UICollectionView performBatchUpdates:]. This appears to be a + // bug in YapDatabase. + return YES; + } + // if (hasDeletes && hasInserts) { + // return YES; + // } + // if (hasDeletes && hasUpdates) { + // return YES; + // } + // if (hasInserts && hasUpdates) { + // return YES; + // } + return NO; +} + - (BOOL)shouldAnimateRowUpdates:(NSArray *)rowChanges oldViewItemCount:(NSUInteger)oldViewItemCount { OWSAssert(rowChanges); + // for (YapDatabaseViewRowChange *rowChange in rowChanges) { + // switch (rowChange.type) { + // case YapDatabaseViewChangeDelete: + // DDLogVerbose(@"...YapDatabaseViewChangeDelete: %@, %@, %zd", + // rowChange.collectionKey, + // rowChange.indexPath, + // rowChange.finalIndex); + // [DDLog flushLog]; + // break; + // case YapDatabaseViewChangeInsert: + // DDLogVerbose(@"...YapDatabaseViewChangeInsert: %@, %@, %zd", + // rowChange.collectionKey, + // rowChange.newIndexPath, + // rowChange.finalIndex); + // [DDLog flushLog]; + // break; + // case YapDatabaseViewChangeMove: + // DDLogVerbose(@"...YapDatabaseViewChangeMove: %@, %@, %@, %zd", + // rowChange.collectionKey, + // rowChange.indexPath, + // rowChange.newIndexPath, + // rowChange.finalIndex); + // [DDLog flushLog]; + // break; + // case YapDatabaseViewChangeUpdate: + // DDLogVerbose(@"...YapDatabaseViewChangeUpdate: %@, %@, %zd", + // rowChange.collectionKey, + // rowChange.indexPath, + // rowChange.finalIndex); + // [DDLog flushLog]; + // break; + // } + // } + // If user sends a new outgoing message, don't animate the change. BOOL isOnlyInsertingNewOutgoingMessages = YES; BOOL isOnlyUpdatingLastOutgoingMessage = YES; @@ -3325,20 +3437,23 @@ typedef NS_ENUM(NSInteger, MessagesRangeSizeMode) { [self presentViewController:actionSheetController animated:true completion:nil]; } -- (NSIndexPath *)lastVisibleIndexPath +- (nullable NSIndexPath *)lastVisibleIndexPath { - NSIndexPath *lastVisibleIndexPath = nil; + NSIndexPath *_Nullable lastVisibleIndexPath = nil; for (NSIndexPath *indexPath in [self.collectionView indexPathsForVisibleItems]) { if (!lastVisibleIndexPath || indexPath.row > lastVisibleIndexPath.row) { lastVisibleIndexPath = indexPath; } } + if (lastVisibleIndexPath && lastVisibleIndexPath.row >= self.viewItems.count) { + return (self.viewItems.count > 0 ? [NSIndexPath indexPathForRow:self.viewItems.count - 1 inSection:0] : nil); + } return lastVisibleIndexPath; } - (nullable ConversationViewItem *)lastVisibleViewItem { - NSIndexPath *lastVisibleIndexPath = [self lastVisibleIndexPath]; + NSIndexPath *_Nullable lastVisibleIndexPath = [self lastVisibleIndexPath]; if (!lastVisibleIndexPath) { return nil; } diff --git a/Signal/src/ViewControllers/ConversationView/ConversationViewItem.h b/Signal/src/ViewControllers/ConversationView/ConversationViewItem.h index ae315ef61..8d0c19d6d 100644 --- a/Signal/src/ViewControllers/ConversationView/ConversationViewItem.h +++ b/Signal/src/ViewControllers/ConversationView/ConversationViewItem.h @@ -72,7 +72,7 @@ NSString *NSStringForOWSMessageCellType(OWSMessageCellType cellType); @property (nonatomic, weak) OWSAudioMessageView *lastAudioMessageView; -@property (nonatomic, nullable) NSNumber *audioDurationSeconds; +@property (nonatomic, readonly, nullable) NSNumber *audioDurationSeconds; - (CGFloat)audioProgressSeconds; diff --git a/Signal/src/ViewControllers/ConversationView/ConversationViewItem.m b/Signal/src/ViewControllers/ConversationView/ConversationViewItem.m index a3492ab01..99be46755 100644 --- a/Signal/src/ViewControllers/ConversationView/ConversationViewItem.m +++ b/Signal/src/ViewControllers/ConversationView/ConversationViewItem.m @@ -47,6 +47,7 @@ NSString *NSStringForOWSMessageCellType(OWSMessageCellType cellType) @property (nonatomic) AudioPlaybackState audioPlaybackState; @property (nonatomic) CGFloat audioProgressSeconds; +@property (nonatomic, nullable) NSNumber *audioDurationSeconds; #pragma mark - View State @@ -251,9 +252,6 @@ NSString *NSStringForOWSMessageCellType(OWSMessageCellType cellType) OWSAssert([NSThread isMainThread]); self.audioProgressSeconds = progress; - if (duration > 0) { - self.audioDurationSeconds = @(duration); - } [self.lastAudioMessageView updateContents]; } @@ -390,13 +388,19 @@ NSString *NSStringForOWSMessageCellType(OWSMessageCellType cellType) self.messageCellType = OWSMessageCellType_GenericAttachment; return; } - self.contentSize = [self.attachmentStream imageSizeWithoutTransaction]; + self.contentSize = [self.attachmentStream imageSize]; if (self.contentSize.width <= 0 || self.contentSize.height <= 0) { self.messageCellType = OWSMessageCellType_GenericAttachment; } return; } else if ([self.attachmentStream isAudio]) { - self.messageCellType = OWSMessageCellType_Audio; + CGFloat audioDurationSeconds = [self.attachmentStream audioDurationSeconds]; + if (audioDurationSeconds > 0) { + self.audioDurationSeconds = @(audioDurationSeconds); + self.messageCellType = OWSMessageCellType_Audio; + } else { + self.messageCellType = OWSMessageCellType_GenericAttachment; + } return; } else { self.messageCellType = OWSMessageCellType_GenericAttachment; diff --git a/Signal/src/ViewControllers/DebugUI/DebugUIMessages.m b/Signal/src/ViewControllers/DebugUI/DebugUIMessages.m index 177b03067..499878b50 100644 --- a/Signal/src/ViewControllers/DebugUI/DebugUIMessages.m +++ b/Signal/src/ViewControllers/DebugUI/DebugUIMessages.m @@ -40,6 +40,14 @@ NS_ASSUME_NONNULL_BEGIN OWSAssert(thread); NSMutableArray *items = [@[ + [OWSTableItem itemWithTitle:@"Perform 100 random actions" + actionBlock:^{ + [DebugUIMessages performRandomActions:100 thread:thread]; + }], + [OWSTableItem itemWithTitle:@"Perform 1,000 random actions" + actionBlock:^{ + [DebugUIMessages performRandomActions:1000 thread:thread]; + }], [OWSTableItem itemWithTitle:@"Send 10 messages (1/sec.)" actionBlock:^{ [DebugUIMessages sendTextMessages:10 thread:thread]; @@ -241,14 +249,6 @@ NS_ASSUME_NONNULL_BEGIN actionBlock:^{ [DebugUIMessages injectFakeIncomingMessages:1000 thread:thread]; }], - [OWSTableItem itemWithTitle:@"Perform 100 random actions" - actionBlock:^{ - [DebugUIMessages performRandomActions:100 thread:thread]; - }], - [OWSTableItem itemWithTitle:@"Perform 1,000 random actions" - actionBlock:^{ - [DebugUIMessages performRandomActions:1000 thread:thread]; - }], ] mutableCopy]; if ([thread isKindOfClass:[TSContactThread class]]) { TSContactThread *contactThread = (TSContactThread *)thread; @@ -271,10 +271,14 @@ NS_ASSUME_NONNULL_BEGIN + (void)sendTextMessageInThread:(TSThread *)thread counter:(int)counter { + DDLogInfo(@"%@ sendTextMessageInThread: %d", self.logTag, counter); + [DDLog flushLog]; + NSString *randomText = [self randomText]; NSString *text = [[[@(counter) description] stringByAppendingString:@" "] stringByAppendingString:randomText]; OWSMessageSender *messageSender = [Environment getCurrent].messageSender; - [ThreadUtil sendMessageWithText:text inThread:thread messageSender:messageSender]; + TSOutgoingMessage *message = [ThreadUtil sendMessageWithText:text inThread:thread messageSender:messageSender]; + DDLogError(@"%@ sendTextMessageInThread timestamp: %llu.", self.logTag, message.timestamp); } + (void)sendTextMessages:(int)counter thread:(TSThread *)thread @@ -939,83 +943,96 @@ NS_ASSUME_NONNULL_BEGIN + (void)sendFakeMessages:(NSUInteger)counter thread:(TSThread *)thread { - [TSStorageManager.sharedManager.dbReadWriteConnection readWriteWithBlock:^( - YapDatabaseReadWriteTransaction *transaction) { - for (NSUInteger i = 0; i < counter; i++) { - NSString *randomText = [self randomText]; - switch (arc4random_uniform(4)) { - case 0: { - TSIncomingMessage *message = - [[TSIncomingMessage alloc] initWithTimestamp:[NSDate ows_millisecondTimeStamp] - inThread:thread - authorId:@"+19174054215" - sourceDeviceId:0 - messageBody:randomText]; - [message markAsReadWithTransaction:transaction sendReadReceipt:NO updateExpiration:NO]; - break; - } - case 1: { - TSOutgoingMessage *message = - [[TSOutgoingMessage alloc] initWithTimestamp:[NSDate ows_millisecondTimeStamp] - inThread:thread - messageBody:randomText]; - [message saveWithTransaction:transaction]; - break; - } - case 2: { - UInt32 filesize = 64; - TSAttachmentPointer *pointer = - [[TSAttachmentPointer alloc] initWithServerId:237391539706350548 - key:[self createRandomNSDataOfSize:filesize] - digest:nil - byteCount:filesize - contentType:@"audio/mp3" - relay:@"" - sourceFilename:@"test.mp3" - attachmentType:TSAttachmentTypeDefault]; - [pointer saveWithTransaction:transaction]; - TSIncomingMessage *message = - [[TSIncomingMessage alloc] initWithTimestamp:[NSDate ows_millisecondTimeStamp] - inThread:thread - authorId:@"+19174054215" - sourceDeviceId:0 - messageBody:nil - attachmentIds:@[ - pointer.uniqueId, - ] - expiresInSeconds:0]; - [message markAsReadWithTransaction:transaction sendReadReceipt:NO updateExpiration:NO]; - break; - } - case 3: { - TSOutgoingMessage *message = - [[TSOutgoingMessage alloc] initWithTimestamp:[NSDate ows_millisecondTimeStamp] - inThread:thread - isVoiceMessage:NO - expiresInSeconds:0]; - - NSString *filename = @"test.mp3"; - UInt32 filesize = 16; - - TSAttachmentStream *attachmentStream = [[TSAttachmentStream alloc] initWithContentType:@"audio/mp3" - byteCount:filesize - sourceFilename:filename]; - - NSError *error; - BOOL success = [attachmentStream writeData:[self createRandomNSDataOfSize:filesize] error:&error]; - OWSAssert(success && !error); - - [attachmentStream saveWithTransaction:transaction]; - [message.attachmentIds addObject:attachmentStream.uniqueId]; - if (filename) { - message.attachmentFilenameMap[attachmentStream.uniqueId] = filename; - } - [message saveWithTransaction:transaction]; - break; + [TSStorageManager.sharedManager.dbReadWriteConnection + readWriteWithBlock:^(YapDatabaseReadWriteTransaction *transaction) { + [self sendFakeMessages:counter thread:thread transaction:transaction]; + }]; +} + ++ (void)sendFakeMessages:(NSUInteger)counter + thread:(TSThread *)thread + transaction:(YapDatabaseReadWriteTransaction *)transaction +{ + DDLogInfo(@"%@ sendFakeMessages: %zd", self.logTag, counter); + + for (NSUInteger i = 0; i < counter; i++) { + NSString *randomText = [self randomText]; + switch (arc4random_uniform(4)) { + case 0: { + TSIncomingMessage *message = + [[TSIncomingMessage alloc] initWithTimestamp:[NSDate ows_millisecondTimeStamp] + inThread:thread + authorId:@"+19174054215" + sourceDeviceId:0 + messageBody:randomText]; + DDLogError(@"%@ sendFakeMessages incoming timestamp: %llu.", self.logTag, message.timestamp); + [message markAsReadWithTransaction:transaction sendReadReceipt:NO updateExpiration:NO]; + break; + } + case 1: { + TSOutgoingMessage *message = + [[TSOutgoingMessage alloc] initWithTimestamp:[NSDate ows_millisecondTimeStamp] + inThread:thread + messageBody:randomText]; + DDLogError(@"%@ sendFakeMessages outgoing timestamp: %llu.", self.logTag, message.timestamp); + [message saveWithTransaction:transaction]; + break; + } + case 2: { + UInt32 filesize = 64; + TSAttachmentPointer *pointer = + [[TSAttachmentPointer alloc] initWithServerId:237391539706350548 + key:[self createRandomNSDataOfSize:filesize] + digest:nil + byteCount:filesize + contentType:@"audio/mp3" + relay:@"" + sourceFilename:@"test.mp3" + attachmentType:TSAttachmentTypeDefault]; + [pointer saveWithTransaction:transaction]; + TSIncomingMessage *message = + [[TSIncomingMessage alloc] initWithTimestamp:[NSDate ows_millisecondTimeStamp] + inThread:thread + authorId:@"+19174054215" + sourceDeviceId:0 + messageBody:nil + attachmentIds:@[ + pointer.uniqueId, + ] + expiresInSeconds:0]; + DDLogError(@"%@ sendFakeMessages incoming attachment timestamp: %llu.", self.logTag, message.timestamp); + [message markAsReadWithTransaction:transaction sendReadReceipt:NO updateExpiration:NO]; + break; + } + case 3: { + TSOutgoingMessage *message = + [[TSOutgoingMessage alloc] initWithTimestamp:[NSDate ows_millisecondTimeStamp] + inThread:thread + isVoiceMessage:NO + expiresInSeconds:0]; + DDLogError(@"%@ sendFakeMessages outgoing attachment timestamp: %llu.", self.logTag, message.timestamp); + + NSString *filename = @"test.mp3"; + UInt32 filesize = 16; + + TSAttachmentStream *attachmentStream = [[TSAttachmentStream alloc] initWithContentType:@"audio/mp3" + byteCount:filesize + sourceFilename:filename]; + + NSError *error; + BOOL success = [attachmentStream writeData:[self createRandomNSDataOfSize:filesize] error:&error]; + OWSAssert(success && !error); + + [attachmentStream saveWithTransaction:transaction]; + [message.attachmentIds addObject:attachmentStream.uniqueId]; + if (filename) { + message.attachmentFilenameMap[attachmentStream.uniqueId] = filename; } + [message saveWithTransaction:transaction]; + break; } } - }]; + } } + (void)sendTinyAttachments:(int)counter thread:(TSThread *)thread @@ -1099,6 +1116,8 @@ NS_ASSUME_NONNULL_BEGIN { OWSAssert(thread); + DDLogInfo(@"%@ injectIncomingMessageInThread: %d", self.logTag, counter); + NSString *randomText = [self randomText]; NSString *text = [[[@(counter) description] stringByAppendingString:@" "] stringByAppendingString:randomText]; @@ -1154,144 +1173,151 @@ NS_ASSUME_NONNULL_BEGIN + (void)performRandomActionInThread:(TSThread *)thread counter:(int)counter { - typedef void (^ActionBlock)(void); + typedef void (^ActionBlock)(YapDatabaseReadWriteTransaction *transaction); NSArray *actionBlocks = @[ - ^{ - [self injectIncomingMessageInThread:thread counter:counter]; + ^(YapDatabaseReadWriteTransaction *transaction) { + // injectIncomingMessageInThread doesn't take a transaction. + dispatch_async(dispatch_get_main_queue(), ^{ + [self injectIncomingMessageInThread:thread counter:counter]; + }); }, - ^{ - [self sendTextMessageInThread:thread counter:counter]; + ^(YapDatabaseReadWriteTransaction *transaction) { + // sendTextMessageInThread doesn't take a transaction. + dispatch_async(dispatch_get_main_queue(), ^{ + [self sendTextMessageInThread:thread counter:counter]; + }); }, - ^{ + ^(YapDatabaseReadWriteTransaction *transaction) { NSUInteger messageCount = (NSUInteger)(1 + arc4random_uniform(4)); - [self sendFakeMessages:messageCount thread:thread]; + [self sendFakeMessages:messageCount thread:thread transaction:transaction]; }, - ^{ + ^(YapDatabaseReadWriteTransaction *transaction) { NSUInteger messageCount = (NSUInteger)(1 + arc4random_uniform(4)); - [self deleteRandomMessages:messageCount thread:thread]; + [self deleteRandomMessages:messageCount thread:thread transaction:transaction]; }, - ^{ + ^(YapDatabaseReadWriteTransaction *transaction) { NSUInteger messageCount = (NSUInteger)(1 + arc4random_uniform(4)); - [self deleteLastMessages:messageCount thread:thread]; + [self deleteLastMessages:messageCount thread:thread transaction:transaction]; }, - ^{ + ^(YapDatabaseReadWriteTransaction *transaction) { NSUInteger messageCount = (NSUInteger)(1 + arc4random_uniform(4)); - [self deleteRandomRecentMessages:messageCount thread:thread]; + [self deleteRandomRecentMessages:messageCount thread:thread transaction:transaction]; }, - ^{ + ^(YapDatabaseReadWriteTransaction *transaction) { NSUInteger messageCount = (NSUInteger)(1 + arc4random_uniform(4)); - [self insertAndDeleteNewOutgoingMessages:messageCount thread:thread]; + [self insertAndDeleteNewOutgoingMessages:messageCount thread:thread transaction:transaction]; }, - ^{ + ^(YapDatabaseReadWriteTransaction *transaction) { NSUInteger messageCount = (NSUInteger)(1 + arc4random_uniform(4)); - [self resurrectNewOutgoingMessages1:messageCount thread:thread]; + [self resurrectNewOutgoingMessages1:messageCount thread:thread transaction:transaction]; }, - ^{ + ^(YapDatabaseReadWriteTransaction *transaction) { NSUInteger messageCount = (NSUInteger)(1 + arc4random_uniform(4)); - [self resurrectNewOutgoingMessages2:messageCount thread:thread]; + [self resurrectNewOutgoingMessages2:messageCount thread:thread transaction:transaction]; }, ]; - ActionBlock actionBlock = actionBlocks[(NSUInteger) arc4random_uniform((uint32_t) actionBlocks.count)]; - actionBlock(); + [TSStorageManager.sharedManager.dbReadWriteConnection + readWriteWithBlock:^(YapDatabaseReadWriteTransaction *transaction) { + int actionCount = 1 + (int)arc4random_uniform(3); + for (int actionIdx = 0; actionIdx < actionCount; actionIdx++) { + ActionBlock actionBlock = actionBlocks[(NSUInteger)arc4random_uniform((uint32_t)actionBlocks.count)]; + actionBlock(transaction); + } + }]; } -+ (void)deleteRandomMessages:(NSUInteger)count thread:(TSThread *)thread ++ (void)deleteRandomMessages:(NSUInteger)count + thread:(TSThread *)thread + transaction:(YapDatabaseReadWriteTransaction *)transaction { DDLogInfo(@"%@ deleteRandomMessages: %zd", self.logTag, count); - [TSStorageManager.sharedManager.dbReadWriteConnection readWriteWithBlock:^( - YapDatabaseReadWriteTransaction *transaction) { - - YapDatabaseViewTransaction *interactionsByThread = [transaction ext:TSMessageDatabaseViewExtensionName]; - NSUInteger messageCount = [interactionsByThread numberOfItemsInGroup:thread.uniqueId]; - - NSMutableArray *messageIndices = [NSMutableArray new]; - for (NSUInteger messageIdx =0; messageIdx < messageCount; messageIdx++) { - [messageIndices addObject:@(messageIdx)]; - } - NSMutableArray *interactions = [NSMutableArray new]; - for (NSUInteger i =0; i < count && messageIndices.count > 0; i++) { - NSUInteger idx = (NSUInteger) arc4random_uniform((uint32_t) messageIndices.count); - NSNumber *messageIdx = messageIndices[idx]; - [messageIndices removeObjectAtIndex:idx]; - - TSInteraction *_Nullable interaction = + YapDatabaseViewTransaction *interactionsByThread = [transaction ext:TSMessageDatabaseViewExtensionName]; + NSUInteger messageCount = [interactionsByThread numberOfItemsInGroup:thread.uniqueId]; + + NSMutableArray *messageIndices = [NSMutableArray new]; + for (NSUInteger messageIdx = 0; messageIdx < messageCount; messageIdx++) { + [messageIndices addObject:@(messageIdx)]; + } + NSMutableArray *interactions = [NSMutableArray new]; + for (NSUInteger i = 0; i < count && messageIndices.count > 0; i++) { + NSUInteger idx = (NSUInteger)arc4random_uniform((uint32_t)messageIndices.count); + NSNumber *messageIdx = messageIndices[idx]; + [messageIndices removeObjectAtIndex:idx]; + + TSInteraction *_Nullable interaction = [interactionsByThread objectAtIndex:messageIdx.unsignedIntegerValue inGroup:thread.uniqueId]; - OWSAssert(interaction); - [interactions addObject:interaction]; - } - - for (TSInteraction *interaction in interactions) { - [interaction removeWithTransaction:transaction]; - } - }]; + OWSAssert(interaction); + [interactions addObject:interaction]; + } + + for (TSInteraction *interaction in interactions) { + [interaction removeWithTransaction:transaction]; + } } -+ (void)deleteLastMessages:(NSUInteger)count thread:(TSThread *)thread ++ (void)deleteLastMessages:(NSUInteger)count + thread:(TSThread *)thread + transaction:(YapDatabaseReadWriteTransaction *)transaction { DDLogInfo(@"%@ deleteLastMessages", self.logTag); + YapDatabaseViewTransaction *interactionsByThread = [transaction ext:TSMessageDatabaseViewExtensionName]; + NSUInteger messageCount = (NSUInteger)[interactionsByThread numberOfItemsInGroup:thread.uniqueId]; - [TSStorageManager.sharedManager.dbReadWriteConnection readWriteWithBlock:^( - YapDatabaseReadWriteTransaction *transaction) { - - YapDatabaseViewTransaction *interactionsByThread = [transaction ext:TSMessageDatabaseViewExtensionName]; - NSUInteger messageCount = (NSInteger)[interactionsByThread numberOfItemsInGroup:thread.uniqueId]; - - NSMutableArray *messageIndices = [NSMutableArray new]; - for (NSUInteger i = 0; i < count && i < messageCount; i++) { - NSUInteger messageIdx = messageCount - (1 + i); - [messageIndices addObject:@(messageIdx)]; - } - NSMutableArray *interactions = [NSMutableArray new]; - for (NSNumber *messageIdx in messageIndices) { - TSInteraction *_Nullable interaction = - [interactionsByThread objectAtIndex:messageIdx.unsignedIntegerValue inGroup:thread.uniqueId]; - OWSAssert(interaction); - [interactions addObject:interaction]; - } - for (TSInteraction *interaction in interactions) { - [interaction removeWithTransaction:transaction]; - } - }]; + NSMutableArray *messageIndices = [NSMutableArray new]; + for (NSUInteger i = 0; i < count && i < messageCount; i++) { + NSUInteger messageIdx = messageCount - (1 + i); + [messageIndices addObject:@(messageIdx)]; + } + NSMutableArray *interactions = [NSMutableArray new]; + for (NSNumber *messageIdx in messageIndices) { + TSInteraction *_Nullable interaction = + [interactionsByThread objectAtIndex:messageIdx.unsignedIntegerValue inGroup:thread.uniqueId]; + OWSAssert(interaction); + [interactions addObject:interaction]; + } + for (TSInteraction *interaction in interactions) { + [interaction removeWithTransaction:transaction]; + } } -+ (void)deleteRandomRecentMessages:(NSUInteger)count thread:(TSThread *)thread ++ (void)deleteRandomRecentMessages:(NSUInteger)count + thread:(TSThread *)thread + transaction:(YapDatabaseReadWriteTransaction *)transaction { DDLogInfo(@"%@ deleteRandomRecentMessages: %zd", self.logTag, count); - [TSStorageManager.sharedManager.dbReadWriteConnection readWriteWithBlock:^( - YapDatabaseReadWriteTransaction *transaction) { - - YapDatabaseViewTransaction *interactionsByThread = [transaction ext:TSMessageDatabaseViewExtensionName]; - NSInteger messageCount = (NSInteger) [interactionsByThread numberOfItemsInGroup:thread.uniqueId]; - - NSMutableArray *messageIndices = [NSMutableArray new]; - const NSInteger kRecentMessageCount = 10; - for (NSInteger i =0; i < kRecentMessageCount; i++) { - NSInteger messageIdx = messageCount - (1 + i); - if (messageIdx >= 0) { - [messageIndices addObject:@(messageIdx)]; - } + YapDatabaseViewTransaction *interactionsByThread = [transaction ext:TSMessageDatabaseViewExtensionName]; + NSInteger messageCount = (NSInteger)[interactionsByThread numberOfItemsInGroup:thread.uniqueId]; + + NSMutableArray *messageIndices = [NSMutableArray new]; + const NSInteger kRecentMessageCount = 10; + for (NSInteger i = 0; i < kRecentMessageCount; i++) { + NSInteger messageIdx = messageCount - (1 + i); + if (messageIdx >= 0) { + [messageIndices addObject:@(messageIdx)]; } - NSMutableArray *interactions = [NSMutableArray new]; - for (NSUInteger i =0; i < count && messageIndices.count > 0; i++) { - NSUInteger idx = (NSUInteger) arc4random_uniform((uint32_t) messageIndices.count); - NSNumber *messageIdx = messageIndices[idx]; - [messageIndices removeObjectAtIndex:idx]; - - TSInteraction *_Nullable interaction = + } + NSMutableArray *interactions = [NSMutableArray new]; + for (NSUInteger i = 0; i < count && messageIndices.count > 0; i++) { + NSUInteger idx = (NSUInteger)arc4random_uniform((uint32_t)messageIndices.count); + NSNumber *messageIdx = messageIndices[idx]; + [messageIndices removeObjectAtIndex:idx]; + + TSInteraction *_Nullable interaction = [interactionsByThread objectAtIndex:messageIdx.unsignedIntegerValue inGroup:thread.uniqueId]; - OWSAssert(interaction); - [interactions addObject:interaction]; - } - for (TSInteraction *interaction in interactions) { - [interaction removeWithTransaction:transaction]; - } - }]; + OWSAssert(interaction); + [interactions addObject:interaction]; + } + for (TSInteraction *interaction in interactions) { + [interaction removeWithTransaction:transaction]; + } } -+ (void)insertAndDeleteNewOutgoingMessages:(NSUInteger)count thread:(TSThread *)thread ++ (void)insertAndDeleteNewOutgoingMessages:(NSUInteger)count + thread:(TSThread *)thread + transaction:(YapDatabaseReadWriteTransaction *)transaction { DDLogInfo(@"%@ insertAndDeleteNewOutgoingMessages: %zd", self.logTag, count); @@ -1299,28 +1325,28 @@ NS_ASSUME_NONNULL_BEGIN for (NSUInteger i =0; i < count; i++) { NSString *text = [self randomText]; OWSDisappearingMessagesConfiguration *configuration = - [OWSDisappearingMessagesConfiguration fetchObjectWithUniqueID:thread.uniqueId]; + [OWSDisappearingMessagesConfiguration fetchObjectWithUniqueID:thread.uniqueId transaction:transaction]; TSOutgoingMessage *message = [[TSOutgoingMessage alloc] initWithTimestamp:[NSDate ows_millisecondTimeStamp] inThread:thread messageBody:text attachmentIds:[NSMutableArray new] expiresInSeconds:(configuration.isEnabled ? configuration.durationSeconds : 0)]; + DDLogError(@"%@ insertAndDeleteNewOutgoingMessages timestamp: %llu.", self.logTag, message.timestamp); [messages addObject:message]; } - - [TSStorageManager.sharedManager.dbReadWriteConnection readWriteWithBlock:^( - YapDatabaseReadWriteTransaction *transaction) { - for (TSOutgoingMessage *message in messages) { - [message saveWithTransaction:transaction]; - } - for (TSOutgoingMessage *message in messages) { - [message removeWithTransaction:transaction]; - } - }]; + + for (TSOutgoingMessage *message in messages) { + [message saveWithTransaction:transaction]; + } + for (TSOutgoingMessage *message in messages) { + [message removeWithTransaction:transaction]; + } } -+ (void)resurrectNewOutgoingMessages1:(NSUInteger)count thread:(TSThread *)thread ++ (void)resurrectNewOutgoingMessages1:(NSUInteger)count + thread:(TSThread *)thread + transaction:(YapDatabaseReadWriteTransaction *)initialTransaction { DDLogInfo(@"%@ resurrectNewOutgoingMessages1.1: %zd", self.logTag, count); @@ -1328,22 +1354,22 @@ NS_ASSUME_NONNULL_BEGIN for (NSUInteger i =0; i < count; i++) { NSString *text = [self randomText]; OWSDisappearingMessagesConfiguration *configuration = - [OWSDisappearingMessagesConfiguration fetchObjectWithUniqueID:thread.uniqueId]; + [OWSDisappearingMessagesConfiguration fetchObjectWithUniqueID:thread.uniqueId + transaction:initialTransaction]; TSOutgoingMessage *message = [[TSOutgoingMessage alloc] initWithTimestamp:[NSDate ows_millisecondTimeStamp] inThread:thread messageBody:text attachmentIds:[NSMutableArray new] expiresInSeconds:(configuration.isEnabled ? configuration.durationSeconds : 0)]; + DDLogError(@"%@ resurrectNewOutgoingMessages1 timestamp: %llu.", self.logTag, message.timestamp); [messages addObject:message]; } - - [TSStorageManager.sharedManager.dbReadWriteConnection readWriteWithBlock:^( - YapDatabaseReadWriteTransaction *transaction) { - for (TSOutgoingMessage *message in messages) { - [message saveWithTransaction:transaction]; - } - }]; + + for (TSOutgoingMessage *message in messages) { + [message saveWithTransaction:initialTransaction]; + } + dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(1.f * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{ DDLogInfo(@"%@ resurrectNewOutgoingMessages1.2: %zd", self.logTag, count); [TSStorageManager.sharedManager.dbReadWriteConnection @@ -1358,7 +1384,9 @@ NS_ASSUME_NONNULL_BEGIN }); } -+ (void)resurrectNewOutgoingMessages2:(NSUInteger)count thread:(TSThread *)thread ++ (void)resurrectNewOutgoingMessages2:(NSUInteger)count + thread:(TSThread *)thread + transaction:(YapDatabaseReadWriteTransaction *)initialTransaction { DDLogInfo(@"%@ resurrectNewOutgoingMessages2.1: %zd", self.logTag, count); @@ -1366,23 +1394,23 @@ NS_ASSUME_NONNULL_BEGIN for (NSUInteger i =0; i < count; i++) { NSString *text = [self randomText]; OWSDisappearingMessagesConfiguration *configuration = - [OWSDisappearingMessagesConfiguration fetchObjectWithUniqueID:thread.uniqueId]; + [OWSDisappearingMessagesConfiguration fetchObjectWithUniqueID:thread.uniqueId + transaction:initialTransaction]; TSOutgoingMessage *message = [[TSOutgoingMessage alloc] initWithTimestamp:[NSDate ows_millisecondTimeStamp] inThread:thread messageBody:text attachmentIds:[NSMutableArray new] expiresInSeconds:(configuration.isEnabled ? configuration.durationSeconds : 0)]; + DDLogError(@"%@ resurrectNewOutgoingMessages2 timestamp: %llu.", self.logTag, message.timestamp); [messages addObject:message]; } - - [TSStorageManager.sharedManager.dbReadWriteConnection readWriteWithBlock:^( - YapDatabaseReadWriteTransaction *transaction) { - for (TSOutgoingMessage *message in messages) { - [message updateWithMessageState:TSOutgoingMessageStateAttemptingOut transaction:transaction]; - [message saveWithTransaction:transaction]; - } - }]; + + for (TSOutgoingMessage *message in messages) { + [message updateWithMessageState:TSOutgoingMessageStateAttemptingOut transaction:initialTransaction]; + [message saveWithTransaction:initialTransaction]; + } + dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(1.f * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{ DDLogInfo(@"%@ resurrectNewOutgoingMessages2.2: %zd", self.logTag, count); [TSStorageManager.sharedManager.dbReadWriteConnection diff --git a/SignalServiceKit/src/Messages/Attachments/TSAttachmentStream.h b/SignalServiceKit/src/Messages/Attachments/TSAttachmentStream.h index 521374ccd..ac8f9c490 100644 --- a/SignalServiceKit/src/Messages/Attachments/TSAttachmentStream.h +++ b/SignalServiceKit/src/Messages/Attachments/TSAttachmentStream.h @@ -4,8 +4,10 @@ #import "DataSource.h" #import "TSAttachment.h" + #if TARGET_OS_IPHONE #import + #endif NS_ASSUME_NONNULL_BEGIN @@ -51,11 +53,9 @@ NS_ASSUME_NONNULL_BEGIN + (void)deleteAttachments; + (NSString *)attachmentsFolder; -- (CGSize)imageSizeWithTransaction:(YapDatabaseReadWriteTransaction *)transaction; -- (CGSize)imageSizeWithoutTransaction; +- (CGSize)imageSize; -- (CGFloat)audioDurationSecondsWithTransaction:(YapDatabaseReadWriteTransaction *)transaction; -- (CGFloat)audioDurationSecondsWithoutTransaction; +- (CGFloat)audioDurationSeconds; @end diff --git a/SignalServiceKit/src/Messages/Attachments/TSAttachmentStream.m b/SignalServiceKit/src/Messages/Attachments/TSAttachmentStream.m index 96d04d21b..ed6c5230e 100644 --- a/SignalServiceKit/src/Messages/Attachments/TSAttachmentStream.m +++ b/SignalServiceKit/src/Messages/Attachments/TSAttachmentStream.m @@ -396,7 +396,7 @@ NS_ASSUME_NONNULL_BEGIN } } -- (CGSize)ensureCachedImageSizeWithTransaction:(YapDatabaseReadWriteTransaction *_Nullable)transaction +- (CGSize)imageSize { OWSAssert([NSThread isMainThread]); @@ -408,68 +408,48 @@ NS_ASSUME_NONNULL_BEGIN self.cachedImageWidth = @(imageSize.width); self.cachedImageHeight = @(imageSize.height); - void (^updateDataStore)() = ^(YapDatabaseReadWriteTransaction *transaction) { - OWSAssert(transaction); - - NSString *collection = [[self class] collection]; - TSAttachmentStream *latestInstance = [transaction objectForKey:self.uniqueId inCollection:collection]; - if (latestInstance) { - latestInstance.cachedImageWidth = @(imageSize.width); - latestInstance.cachedImageHeight = @(imageSize.height); - [latestInstance saveWithTransaction:transaction]; - } else { - // This message has not yet been saved; do nothing. - OWSFail(@"%@ Attachment not yet saved.", self.logTag); - } - }; - - if (transaction) { - updateDataStore(transaction); - } else { + dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ [self.dbReadWriteConnection readWriteWithBlock:^(YapDatabaseReadWriteTransaction *transaction) { - updateDataStore(transaction); + + NSString *collection = [[self class] collection]; + TSAttachmentStream *latestInstance = [transaction objectForKey:self.uniqueId inCollection:collection]; + if (latestInstance) { + latestInstance.cachedImageWidth = @(imageSize.width); + latestInstance.cachedImageHeight = @(imageSize.height); + [latestInstance saveWithTransaction:transaction]; + } else { + // This message has not yet been saved; do nothing. + OWSFail(@"%@ Attachment not yet saved.", self.logTag); + } }]; - } + }); return imageSize; } -- (CGSize)imageSizeWithTransaction:(YapDatabaseReadWriteTransaction *)transaction -{ - OWSAssert([NSThread isMainThread]); - OWSAssert(transaction); - - return [self ensureCachedImageSizeWithTransaction:transaction]; -} - -- (CGSize)imageSizeWithoutTransaction -{ - OWSAssert([NSThread isMainThread]); - - return [self ensureCachedImageSizeWithTransaction:nil]; -} - - (CGFloat)calculateAudioDurationSeconds { OWSAssert([NSThread isMainThread]); OWSAssert([self isAudio]); - NSError *error; - AVAudioPlayer *audioPlayer = [[AVAudioPlayer alloc] initWithContentsOfURL:self.mediaURL error:&error]; - if (error && [error.domain isEqualToString:NSOSStatusErrorDomain] - && (error.code == kAudioFileInvalidFileError || error.code == kAudioFileStreamError_InvalidFile)) { - // Ignore "invalid audio file" errors. - return 0.f; - } - if (!error) { - return (CGFloat)[audioPlayer duration]; - } else { - OWSFail(@"Could not find audio duration: %@", self.mediaURL); - return 0; - } + return 0; + + // NSError *error; + // AVAudioPlayer *audioPlayer = [[AVAudioPlayer alloc] initWithContentsOfURL:self.mediaURL error:&error]; + // if (error && [error.domain isEqualToString:NSOSStatusErrorDomain] + // && (error.code == kAudioFileInvalidFileError || error.code == kAudioFileStreamError_InvalidFile)) { + // // Ignore "invalid audio file" errors. + // return 0.f; + // } + // if (!error) { + // return (CGFloat)[audioPlayer duration]; + // } else { + // OWSFail(@"Could not find audio duration: %@", self.mediaURL); + // return 0; + // } } -- (CGFloat)ensureCachedAudioDurationSecondsWithTransaction:(YapDatabaseReadWriteTransaction *_Nullable)transaction +- (CGFloat)audioDurationSeconds { OWSAssert([NSThread isMainThread]); @@ -480,46 +460,25 @@ NS_ASSUME_NONNULL_BEGIN CGFloat audioDurationSeconds = [self calculateAudioDurationSeconds]; self.cachedAudioDurationSeconds = @(audioDurationSeconds); - void (^updateDataStore)() = ^(YapDatabaseReadWriteTransaction *transaction) { - OWSAssert(transaction); - - NSString *collection = [[self class] collection]; - TSAttachmentStream *latestInstance = [transaction objectForKey:self.uniqueId inCollection:collection]; - if (latestInstance) { - latestInstance.cachedAudioDurationSeconds = @(audioDurationSeconds); - [latestInstance saveWithTransaction:transaction]; - } else { - // This message has not yet been saved; do nothing. - OWSFail(@"%@ Attachment not yet saved.", self.logTag); - } - }; - - if (transaction) { - updateDataStore(transaction); - } else { + dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ [self.dbReadWriteConnection readWriteWithBlock:^(YapDatabaseReadWriteTransaction *transaction) { - updateDataStore(transaction); + NSString *collection = [[self class] collection]; + TSAttachmentStream *latestInstance = [transaction objectForKey:self.uniqueId inCollection:collection]; + if (latestInstance) { + latestInstance.cachedAudioDurationSeconds = @(audioDurationSeconds); + [latestInstance saveWithTransaction:transaction]; + } else { + // This message has not yet been saved or has been deleted; do nothing. + // This isn't an error per se, but these race conditions should be + // _very_ rare. + OWSFail(@"%@ Attachment not yet saved.", self.logTag); + } }]; - } + }); return audioDurationSeconds; } -- (CGFloat)audioDurationSecondsWithTransaction:(YapDatabaseReadWriteTransaction *)transaction -{ - OWSAssert([NSThread isMainThread]); - OWSAssert(transaction); - - return [self ensureCachedAudioDurationSecondsWithTransaction:transaction]; -} - -- (CGFloat)audioDurationSecondsWithoutTransaction -{ - OWSAssert([NSThread isMainThread]); - - return [self ensureCachedAudioDurationSecondsWithTransaction:nil]; -} - @end NS_ASSUME_NONNULL_END diff --git a/SignalServiceKit/src/Messages/OWSReadReceiptManager.m b/SignalServiceKit/src/Messages/OWSReadReceiptManager.m index 3b94df19e..091a61197 100644 --- a/SignalServiceKit/src/Messages/OWSReadReceiptManager.m +++ b/SignalServiceKit/src/Messages/OWSReadReceiptManager.m @@ -363,7 +363,9 @@ NSString *const OWSReadReceiptManagerAreReadReceiptsEnabled = @"areReadReceiptsE = (NSArray *)[TSInteraction interactionsWithTimestamp:sentTimestamp ofClass:[TSOutgoingMessage class] withTransaction:transaction]; - OWSAssert(messages.count <= 1); + if (messages.count > 1) { + OWSFail(@"%@ More than one matching message with timestamp: %llu.", self.logTag, sentTimestamp); + } if (messages.count > 0) { // TODO: We might also need to "mark as read by recipient" any older messages // from us in that thread. Or maybe this state should hang on the thread?