Improving handling of edge cases in conversation view.

This commit is contained in:
Matthew Chen 2017-11-17 11:56:48 -05:00
parent 658746093d
commit 6d4a05bbea
8 changed files with 438 additions and 333 deletions

View File

@ -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

View File

@ -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<NSIndexPath *> *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];
[self updateLastVisibleTimestamp];
if (scrollToBottom) {
[self scrollToBottomAnimated:shouldAnimateScrollToBottom && shouldAnimateUpdates];
}
};
if (shouldAnimateUpdates) {
[self.collectionView performBatchUpdates:batchUpdates completion:batchUpdatesCompletion];
} else {
BOOL shouldReloadCollection = [self shouldReloadCollection:rowChanges];
if (shouldReloadCollection) {
[UIView performWithoutAnimation:^{
[self.collectionView performBatchUpdates:batchUpdates completion:batchUpdatesCompletion];
[self.collectionView reloadData];
}];
[self updateLastVisibleTimestamp];
} else {
BOOL shouldAnimateUpdates = [self shouldAnimateRowUpdates:rowChanges oldViewItemCount:oldViewItemCount];
void (^batchUpdatesCompletion)(BOOL) = ^(BOOL finished) {
OWSAssert([NSThread isMainThread]);
if (!finished) {
DDLogInfo(@"%@ performBatchUpdates did not finish", self.logTag);
}
[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<YapDatabaseViewRowChange *> *)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<YapDatabaseViewRowChange *> *)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;
}

View File

@ -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;

View File

@ -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;

View File

@ -40,6 +40,14 @@ NS_ASSUME_NONNULL_BEGIN
OWSAssert(thread);
NSMutableArray<OWSTableItem *> *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];
[TSStorageManager.sharedManager.dbReadWriteConnection
readWriteWithBlock:^(YapDatabaseReadWriteTransaction *transaction) {
[self sendFakeMessages:counter thread:thread transaction:transaction];
}];
}
NSString *filename = @"test.mp3";
UInt32 filesize = 16;
+ (void)sendFakeMessages:(NSUInteger)counter
thread:(TSThread *)thread
transaction:(YapDatabaseReadWriteTransaction *)transaction
{
DDLogInfo(@"%@ sendFakeMessages: %zd", self.logTag, counter);
TSAttachmentStream *attachmentStream = [[TSAttachmentStream alloc] initWithContentType:@"audio/mp3"
byteCount:filesize
sourceFilename:filename];
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);
NSError *error;
BOOL success = [attachmentStream writeData:[self createRandomNSDataOfSize:filesize] error:&error];
OWSAssert(success && !error);
NSString *filename = @"test.mp3";
UInt32 filesize = 16;
[attachmentStream saveWithTransaction:transaction];
[message.attachmentIds addObject:attachmentStream.uniqueId];
if (filename) {
message.attachmentFilenameMap[attachmentStream.uniqueId] = filename;
}
[message saveWithTransaction:transaction];
break;
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<ActionBlock> *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<NSNumber *> *messageIndices = [NSMutableArray new];
for (NSUInteger messageIdx =0; messageIdx < messageCount; messageIdx++) {
[messageIndices addObject:@(messageIdx)];
}
NSMutableArray<TSInteraction *> *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<NSNumber *> *messageIndices = [NSMutableArray new];
for (NSUInteger messageIdx = 0; messageIdx < messageCount; messageIdx++) {
[messageIndices addObject:@(messageIdx)];
}
NSMutableArray<TSInteraction *> *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<NSNumber *> *messageIndices = [NSMutableArray new];
for (NSUInteger i = 0; i < count && i < messageCount; i++) {
NSUInteger messageIdx = messageCount - (1 + i);
[messageIndices addObject:@(messageIdx)];
}
NSMutableArray<TSInteraction *> *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<NSNumber *> *messageIndices = [NSMutableArray new];
for (NSUInteger i = 0; i < count && i < messageCount; i++) {
NSUInteger messageIdx = messageCount - (1 + i);
[messageIndices addObject:@(messageIdx)];
}
NSMutableArray<TSInteraction *> *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<NSNumber *> *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<NSNumber *> *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<TSInteraction *> *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<TSInteraction *> *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

View File

@ -4,8 +4,10 @@
#import "DataSource.h"
#import "TSAttachment.h"
#if TARGET_OS_IPHONE
#import <UIKit/UIKit.h>
#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

View File

@ -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

View File

@ -363,7 +363,9 @@ NSString *const OWSReadReceiptManagerAreReadReceiptsEnabled = @"areReadReceiptsE
= (NSArray<TSOutgoingMessage *> *)[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?