// // Copyright (c) 2017 Open Whisper Systems. All rights reserved. // #import "TSIncomingMessage.h" #import "OWSDisappearingMessagesConfiguration.h" #import "OWSDisappearingMessagesJob.h" #import "TSContactThread.h" #import "TSDatabaseSecondaryIndexes.h" #import "TSGroupThread.h" #import NS_ASSUME_NONNULL_BEGIN NSString *const TSIncomingMessageWasReadOnThisDeviceNotification = @"TSIncomingMessageWasReadOnThisDeviceNotification"; @interface TSIncomingMessage () @property (nonatomic, getter=wasRead) BOOL read; @end #pragma mark - @implementation TSIncomingMessage - (instancetype)initWithCoder:(NSCoder *)coder { self = [super initWithCoder:coder]; if (!self) { return self; } return self; } - (instancetype)initWithTimestamp:(uint64_t)timestamp inThread:(TSThread *)thread authorId:(NSString *)authorId sourceDeviceId:(uint32_t)sourceDeviceId messageBody:(nullable NSString *)body { return [self initWithTimestamp:timestamp inThread:thread authorId:authorId sourceDeviceId:sourceDeviceId messageBody:body attachmentIds:@[] expiresInSeconds:0]; } - (instancetype)initWithTimestamp:(uint64_t)timestamp inThread:(TSThread *)thread authorId:(NSString *)authorId sourceDeviceId:(uint32_t)sourceDeviceId messageBody:(nullable NSString *)body attachmentIds:(NSArray *)attachmentIds expiresInSeconds:(uint32_t)expiresInSeconds { self = [super initWithTimestamp:timestamp inThread:thread messageBody:body attachmentIds:attachmentIds expiresInSeconds:expiresInSeconds expireStartedAt:0]; if (!self) { return self; } _authorId = authorId; _sourceDeviceId = sourceDeviceId; _read = NO; return self; } + (nullable instancetype)findMessageWithAuthorId:(NSString *)authorId timestamp:(uint64_t)timestamp transaction:(YapDatabaseReadWriteTransaction *)transaction { OWSAssert(transaction); __block TSIncomingMessage *foundMessage; // In theory we could build a new secondaryIndex for (authorId,timestamp), but in practice there should // be *very* few (millisecond) timestamps with multiple authors. [TSDatabaseSecondaryIndexes enumerateMessagesWithTimestamp:timestamp withBlock:^(NSString *collection, NSString *key, BOOL *stop) { TSInteraction *interaction = [TSInteraction fetchObjectWithUniqueID:key transaction:transaction]; if ([interaction isKindOfClass:[TSIncomingMessage class]]) { TSIncomingMessage *message = (TSIncomingMessage *)interaction; // authorId isn't set on all legacy messages, so we take // extra measures to ensure we obtain a valid value. NSString *messageAuthorId; if (message.authorId) { // Group Thread messageAuthorId = message.authorId; } else { // Contact Thread messageAuthorId = [TSContactThread contactIdFromThreadId:message.uniqueThreadId]; } if ([messageAuthorId isEqualToString:authorId]) { foundMessage = message; } } } usingTransaction:transaction]; return foundMessage; } #pragma mark - OWSReadTracking - (BOOL)shouldAffectUnreadCounts { return YES; } - (void)markAsReadWithTransaction:(YapDatabaseReadWriteTransaction *)transaction sendReadReceipt:(BOOL)sendReadReceipt updateExpiration:(BOOL)updateExpiration { OWSAssert(transaction); if (_read) { return; } DDLogDebug(@"%@ marking as read uniqueId: %@ which has timestamp: %llu", self.tag, self.uniqueId, self.timestamp); _read = YES; [self saveWithTransaction:transaction]; [self touchThreadWithTransaction:transaction]; if (updateExpiration) { [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 + (NSString *)tag { return [NSString stringWithFormat:@"[%@]", self.class]; } - (NSString *)tag { return self.class.tag; } @end NS_ASSUME_NONNULL_END