Outgoing Read Receipts

// FREEBIE
pull/1/head
Michael Kirk 9 years ago
parent 580781e3e4
commit acb89f0b0f

@ -0,0 +1,14 @@
// Copyright © 2016 Open Whisper Systems. All rights reserved.
NS_ASSUME_NONNULL_BEGIN
@class TSMessagesManager;
@interface OWSReadReceiptObserver : NSObject
- (instancetype)initWithMessagesManager:(TSMessagesManager *)messagesManager;
- (void)startObserving;
@end
NS_ASSUME_NONNULL_END

@ -0,0 +1,110 @@
// Copyright © 2016 Open Whisper Systems. All rights reserved.
#import "OWSReadReceiptObserver.h"
#import "OWSReadReceipt.h"
#import "OWSReadReceiptsMessage.h"
#import "TSContactThread.h"
#import "TSIncomingMessage.h"
#import "TSMessagesManager+sendMessages.h"
NS_ASSUME_NONNULL_BEGIN
@interface OWSReadReceiptObserver ()
@property (atomic) NSMutableArray<OWSReadReceipt *> *readReceiptsQueue;
@property (nonatomic, readonly) TSMessagesManager *messagesManager;
@property BOOL isObserving;
@end
@implementation OWSReadReceiptObserver
- (instancetype)initWithMessagesManager:(TSMessagesManager *)messagesManager
{
self = [super init];
if (!self) {
return self;
}
_readReceiptsQueue = [NSMutableArray new];
_messagesManager = messagesManager;
_isObserving = NO;
return self;
}
- (void)startObserving
{
if (self.isObserving) {
return;
}
self.isObserving = true;
[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(handleReadNotification:)
name:TSIncomingMessageWasReadOnThisDeviceNotification
object:nil];
}
- (void)handleReadNotification:(NSNotification *)notification
{
if (![notification.object isKindOfClass:[TSIncomingMessage class]]) {
DDLogError(@"Read receipt notifier got unexpected object: %@", notification.object);
return;
}
TSIncomingMessage *message = (TSIncomingMessage *)notification.object;
// 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];
}
OWSReadReceipt *readReceipt = [[OWSReadReceipt alloc] initWithSenderId:messageAuthorId timestamp:message.timestamp];
[self.readReceiptsQueue addObject:readReceipt];
// Wait a bit to bundle up read receipts into one request.
__weak typeof(self) weakSelf = self;
[weakSelf performSelector:@selector(sendAllReadReceiptsInQueue) withObject:nil afterDelay:2.0];
}
- (void)sendAllReadReceiptsInQueue
{
// Synchronized so we don't lose any read receipts while replacing the queue
__block NSArray<OWSReadReceipt *> *receiptsToSend;
@synchronized(self)
{
if (self.readReceiptsQueue.count > 0) {
receiptsToSend = [self.readReceiptsQueue copy];
self.readReceiptsQueue = [NSMutableArray new];
}
}
if (receiptsToSend) {
[self sendReadReceipts:receiptsToSend];
} else {
DDLogVerbose(@"Read receipts queue already drained.");
}
}
- (void)sendReadReceipts:(NSArray<OWSReadReceipt *> *)readReceipts
{
OWSReadReceiptsMessage *message = [[OWSReadReceiptsMessage alloc] initWithReadReceipts:readReceipts];
[self.messagesManager sendMessage:message
inThread:nil
success:^{
DDLogInfo(@"Successfully sent %ld read receipt", (unsigned long)readReceipts.count);
}
failure:^{
DDLogError(@"Failed to send read receipt");
}];
}
@end
NS_ASSUME_NONNULL_END

@ -0,0 +1,15 @@
// Copyright © 2016 Open Whisper Systems. All rights reserved.
#import "OWSOutgoingSyncMessage.h"
NS_ASSUME_NONNULL_BEGIN
@class OWSReadReceipt;
@interface OWSReadReceiptsMessage : OWSOutgoingSyncMessage
- (instancetype)initWithReadReceipts:(NSArray<OWSReadReceipt *> *)readReceipts;
@end
NS_ASSUME_NONNULL_END

@ -0,0 +1,45 @@
// Copyright © 2016 Open Whisper Systems. All rights reserved.
#import "OWSReadReceiptsMessage.h"
#import "OWSReadReceipt.h"
#import "OWSSignalServiceProtos.pb.h"
NS_ASSUME_NONNULL_BEGIN
@interface OWSReadReceiptsMessage ()
@property (nonatomic, readonly) NSArray<OWSReadReceipt *> *readReceipts;
@end
@implementation OWSReadReceiptsMessage
- (instancetype)initWithReadReceipts:(NSArray<OWSReadReceipt *> *)readReceipts
{
self = [super init];
if (!self) {
return self;
}
_readReceipts = [readReceipts copy];
return self;
}
- (OWSSignalServiceProtosSyncMessage *)buildSyncMessage
{
OWSSignalServiceProtosSyncMessageBuilder *syncMessageBuilder = [OWSSignalServiceProtosSyncMessageBuilder new];
for (OWSReadReceipt *readReceipt in self.readReceipts) {
OWSSignalServiceProtosSyncMessageReadBuilder *readProtoBuilder =
[OWSSignalServiceProtosSyncMessageReadBuilder new];
[readProtoBuilder setSender:readReceipt.senderId];
[readProtoBuilder setTimestamp:readReceipt.timestamp];
[syncMessageBuilder addRead:[readProtoBuilder build]];
}
return [syncMessageBuilder build];
}
@end
NS_ASSUME_NONNULL_END

@ -45,13 +45,15 @@ NS_ASSUME_NONNULL_BEGIN
- (void)process
{
DDLogInfo(@"Processing %ld read receipts.", self.readReceipts.count);
DDLogInfo(@"Processing %ld read receipts.", (unsigned long)self.readReceipts.count);
for (OWSReadReceipt *readReceipt in self.readReceipts) {
TSIncomingMessage *message =
[TSIncomingMessage findMessageWithAuthorId:readReceipt.senderId timestamp:readReceipt.timestamp];
if (message) {
[message markAsRead];
[message markAsReadFromReadReceipt];
} else {
// TODO keep read receipts around so that if we get the receipt before the message,
// we can immediately mark the message as read once we get it.
DDLogWarn(@"Couldn't find message for read receipt. Message not synced?");
}
}

@ -7,6 +7,13 @@ NS_ASSUME_NONNULL_BEGIN
@implementation OWSOutgoingSyncMessage
- (void)saveWithTransaction:(YapDatabaseReadWriteTransaction *)transaction
{
// override superclass with no-op.
//
// There's no need to save this message, since it's not displayed to the user.
}
- (BOOL)shouldSyncTranscript
{
return NO;

@ -10,7 +10,6 @@ NS_ASSUME_NONNULL_BEGIN
@interface OWSSyncContactsMessage : OWSOutgoingSyncMessage
- (instancetype)initWithContactsManager:(id<ContactsManagerProtocol>)contactsManager;
- (void)saveWithTransaction:(YapDatabaseReadWriteTransaction *)transaction;
- (NSData *)buildPlainTextAttachmentData;
@end

@ -6,7 +6,6 @@ NS_ASSUME_NONNULL_BEGIN
@interface OWSSyncGroupsMessage : OWSOutgoingSyncMessage
- (void)saveWithTransaction:(YapDatabaseReadWriteTransaction *)transaction;
- (NSData *)buildPlainTextAttachmentData;
@end

@ -17,14 +17,6 @@ NS_ASSUME_NONNULL_BEGIN
return [super initWithTimestamp:[NSDate ows_millisecondTimeStamp]];
}
- (void)saveWithTransaction:(YapDatabaseReadWriteTransaction *)transaction
{
// no-op
// There's no need to save this message, since it's not displayed to the user.
// Furthermore if we did save it, we probably don't want to save the conctactsManager property.
}
- (OWSSignalServiceProtosSyncMessage *)buildSyncMessage
{

@ -8,6 +8,8 @@ NS_ASSUME_NONNULL_BEGIN
@class TSContactThread;
@class TSGroupThread;
extern NSString *const TSIncomingMessageWasReadOnThisDeviceNotification;
@interface TSIncomingMessage : TSMessage
/**
@ -104,11 +106,17 @@ NS_ASSUME_NONNULL_BEGIN
@property (nonatomic, readonly) NSDate *receivedAt;
/*
* Marks a message as having been read and broadcasts a TSIncomingMessageWasReadNotification
* Marks a message as having been read on this device (as opposed to responding to a remote read receipt).
*
*/
- (void)markAsRead;
- (void)markAsReadWithTransaction:(YapDatabaseReadWriteTransaction *)transaction;
/**
* Similar to markAsReadWithTransaction, but doesn't send out read receipts.
* Used for *responding* to a remote read receipt.
*/
- (void)markAsReadFromReadReceipt;
@end
NS_ASSUME_NONNULL_END

@ -9,6 +9,8 @@
NS_ASSUME_NONNULL_BEGIN
NSString *const TSIncomingMessageWasReadOnThisDeviceNotification = @"TSIncomingMessageWasReadOnThisDeviceNotification";
@implementation TSIncomingMessage
- (instancetype)initWithTimestamp:(uint64_t)timestamp
@ -98,14 +100,21 @@ NS_ASSUME_NONNULL_BEGIN
return foundMessage;
}
- (void)markAsRead
- (void)markAsReadFromReadReceipt
{
[self.dbConnection readWriteWithBlock:^(YapDatabaseReadWriteTransaction *transaction) {
[self markAsReadWithTransaction:transaction];
[self markAsReadWithoutNotificationWithTransaction:transaction];
}];
}
- (void)markAsReadWithTransaction:(YapDatabaseReadWriteTransaction *)transaction
{
[self markAsReadWithoutNotificationWithTransaction:transaction];
[[NSNotificationCenter defaultCenter] postNotificationName:TSIncomingMessageWasReadOnThisDeviceNotification
object:self];
}
- (void)markAsReadWithoutNotificationWithTransaction:(YapDatabaseReadWriteTransaction *)transaction
{
_read = YES;
[self saveWithTransaction:transaction];

@ -69,7 +69,7 @@
DDLogWarn(@"Received an unknown message type");
break;
default:
DDLogWarn(@"Received unhandled envelope type: %d", envelope.type);
DDLogWarn(@"Received unhandled envelope type: %d", (int)envelope.type);
break;
}
} @catch (NSException *exception) {

Loading…
Cancel
Save