Incoming Read Receipts

// FREEBIE
This commit is contained in:
Michael Kirk 2016-09-01 10:28:35 -04:00
parent a99fde4d3b
commit 580781e3e4
9 changed files with 208 additions and 5 deletions

View File

@ -178,8 +178,7 @@ NS_ASSUME_NONNULL_BEGIN
}];
for (TSIncomingMessage *message in array) {
message.read = YES;
[message saveWithTransaction:transaction];
[message markAsReadWithTransaction:transaction];
}
}

View File

@ -18,6 +18,8 @@ NS_ASSUME_NONNULL_BEGIN
- (NSString *)contactIdentifier;
+ (NSString *)contactIdFromThreadId:(NSString *)threadId;
@end
NS_ASSUME_NONNULL_END

View File

@ -0,0 +1,16 @@
// Copyright © 2016 Open Whisper Systems. All rights reserved.
NS_ASSUME_NONNULL_BEGIN
@interface OWSReadReceipt : NSObject
@property (nonatomic, readonly) NSString *senderId;
@property (nonatomic, readonly) uint64_t timestamp;
@property (nonatomic, readonly, getter=isValid) BOOL valid;
@property (nonatomic, readonly) NSArray<NSString *> *validationErrorMessages;
- (instancetype)initWithSenderId:(NSString *)senderId timestamp:(uint64_t)timestamp;
@end
NS_ASSUME_NONNULL_END

View File

@ -0,0 +1,35 @@
// Copyright © 2016 Open Whisper Systems. All rights reserved.
#import "OWSReadReceipt.h"
NS_ASSUME_NONNULL_BEGIN
@implementation OWSReadReceipt
- (instancetype)initWithSenderId:(NSString *)senderId timestamp:(uint64_t)timestamp;
{
self = [super init];
if (!self) {
return self;
}
NSMutableArray<NSString *> *validationErrorMessage = [NSMutableArray new];
if (!senderId) {
[validationErrorMessage addObject:@"Must specify sender id"];
}
_senderId = senderId;
if (!timestamp) {
[validationErrorMessage addObject:@"Must specify timestamp"];
}
_timestamp = timestamp;
_valid = validationErrorMessage.count == 0;
_validationErrorMessages = [validationErrorMessage copy];
return self;
}
@end
NS_ASSUME_NONNULL_END

View File

@ -0,0 +1,15 @@
// Copyright © 2016 Open Whisper Systems. All rights reserved.
NS_ASSUME_NONNULL_BEGIN
@class OWSSignalServiceProtosSyncMessageRead;
@interface OWSReadReceiptsProcessor : NSObject
- (instancetype)initWithReadReceiptProtos:(NSArray<OWSSignalServiceProtosSyncMessageRead *> *)readReceiptProtos
NS_DESIGNATED_INITIALIZER;
- (void)process;
@end
NS_ASSUME_NONNULL_END

View File

@ -0,0 +1,62 @@
// Copyright © 2016 Open Whisper Systems. All rights reserved.
#import "OWSReadReceiptsProcessor.h"
#import "OWSReadReceipt.h"
#import "OWSSignalServiceProtos.pb.h"
#import "TSIncomingMessage.h"
NS_ASSUME_NONNULL_BEGIN
@interface OWSReadReceiptsProcessor ()
@property (nonatomic, readonly) NSArray<OWSReadReceipt *> *readReceipts;
@end
@implementation OWSReadReceiptsProcessor
- (instancetype)init
{
return [self initWithReadReceiptProtos:@[]];
}
- (instancetype)initWithReadReceiptProtos:(NSArray<OWSSignalServiceProtosSyncMessageRead *> *)readReceiptProtos
{
self = [super init];
if (!self) {
return self;
}
NSMutableArray<OWSReadReceipt *> *readReceipts = [NSMutableArray new];
for (OWSSignalServiceProtosSyncMessageRead *readReceiptProto in readReceiptProtos) {
OWSReadReceipt *readReceipt =
[[OWSReadReceipt alloc] initWithSenderId:readReceiptProto.sender timestamp:readReceiptProto.timestamp];
if (readReceipt.isValid) {
[readReceipts addObject:readReceipt];
} else {
DDLogError(@"Received invalid read receipt: %@", readReceipt.validationErrorMessages);
}
}
_readReceipts = [readReceipts copy];
return self;
}
- (void)process
{
DDLogInfo(@"Processing %ld read receipts.", self.readReceipts.count);
for (OWSReadReceipt *readReceipt in self.readReceipts) {
TSIncomingMessage *message =
[TSIncomingMessage findMessageWithAuthorId:readReceipt.senderId timestamp:readReceipt.timestamp];
if (message) {
[message markAsRead];
} else {
DDLogWarn(@"Couldn't find message for read receipt. Message not synced?");
}
}
}
@end
NS_ASSUME_NONNULL_END

View File

@ -88,10 +88,27 @@ NS_ASSUME_NONNULL_BEGIN
messageBody:(nullable NSString *)body
attachmentIds:(NSArray<NSString *> *)attachmentIds;
/*
* Find a message matching the senderId and timestamp, if any.
*
* @param authorId
* Signal ID (i.e. e164) of the user who sent the message
* @params timestamp
* When the message was created in milliseconds since epoch
*
*/
+ (nullable instancetype)findMessageWithAuthorId:(NSString *)authorId timestamp:(uint64_t)timestamp;
@property (nonatomic, readonly) NSString *authorId;
@property (nonatomic, getter=wasRead) BOOL read;
@property (nonatomic, readonly, getter=wasRead) BOOL read;
@property (nonatomic, readonly) NSDate *receivedAt;
/*
* Marks a message as having been read and broadcasts a TSIncomingMessageWasReadNotification
*/
- (void)markAsRead;
- (void)markAsReadWithTransaction:(YapDatabaseReadWriteTransaction *)transaction;
@end
NS_ASSUME_NONNULL_END

View File

@ -3,7 +3,9 @@
#import "TSIncomingMessage.h"
#import "TSContactThread.h"
#import "TSDatabaseSecondaryIndexes.h"
#import "TSGroupThread.h"
#import <YapDatabase/YapDatabaseConnection.h>
NS_ASSUME_NONNULL_BEGIN
@ -61,6 +63,55 @@ NS_ASSUME_NONNULL_BEGIN
return self;
}
+ (nullable instancetype)findMessageWithAuthorId:(NSString *)authorId timestamp:(uint64_t)timestamp
{
__block TSIncomingMessage *foundMessage;
[self.dbConnection readWriteWithBlock:^(YapDatabaseReadWriteTransaction *transaction) {
// 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;
// Only groupthread sets authorId, thus this crappy code.
// TODO ALL incoming messages should have an authorId.
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;
}
- (void)markAsRead
{
[self.dbConnection readWriteWithBlock:^(YapDatabaseReadWriteTransaction *transaction) {
[self markAsReadWithTransaction:transaction];
}];
}
- (void)markAsReadWithTransaction:(YapDatabaseReadWriteTransaction *)transaction
{
_read = YES;
[self saveWithTransaction:transaction];
[transaction touchObjectForKey:self.uniqueThreadId inCollection:[TSThread collection]];
}
@end
NS_ASSUME_NONNULL_END

View File

@ -6,6 +6,7 @@
#import "MimeTypeUtil.h"
#import "NSData+messagePadding.h"
#import "OWSIncomingSentMessageTranscript.h"
#import "OWSReadReceiptsProcessor.h"
#import "OWSSyncContactsMessage.h"
#import "OWSSyncGroupsMessage.h"
#import "TSAccountManager.h"
@ -235,8 +236,7 @@
OWSIncomingSentMessageTranscript *transcript =
[[OWSIncomingSentMessageTranscript alloc] initWithProto:syncMessage.sent relay:messageEnvelope.relay];
[transcript record];
}
if (syncMessage.hasRequest) {
} else if (syncMessage.hasRequest) {
if (syncMessage.request.type == OWSSignalServiceProtosSyncMessageRequestTypeContacts) {
DDLogInfo(@"Received request `Contacts` syncMessage.");
@ -270,6 +270,12 @@
DDLogError(@"Failed to send Groups response syncMessage.");
}];
}
} else if (syncMessage.read.count > 0) {
DDLogInfo(@"Received %ld read receipt(s)", (u_long)syncMessage.read.count);
OWSReadReceiptsProcessor *readReceiptsProcessor =
[[OWSReadReceiptsProcessor alloc] initWithReadReceiptProtos:syncMessage.read];
[readReceiptsProcessor process];
} else {
DDLogWarn(@"Ignoring unsupported sync message.");
}