mirror of
https://github.com/oxen-io/session-ios.git
synced 2023-12-13 21:30:14 +01:00
Refactor linked device read receipts.
// FREEBIE
This commit is contained in:
parent
289291e03d
commit
ffe44e68be
14 changed files with 262 additions and 436 deletions
|
@ -11,7 +11,7 @@
|
|||
#import <SignalServiceKit/NSDate+OWS.h>
|
||||
#import <SignalServiceKit/OWSMessageReceiver.h>
|
||||
#import <SignalServiceKit/OWSMessageSender.h>
|
||||
#import <SignalServiceKit/OWSReadReceiptsProcessor.h>
|
||||
#import <SignalServiceKit/OWSReadReceiptManager.h>
|
||||
#import <SignalServiceKit/OWSSignalService.h>
|
||||
#import <SignalServiceKit/TSAccountManager.h>
|
||||
#import <SignalServiceKit/TSIncomingMessage.h>
|
||||
|
@ -85,7 +85,7 @@ NSString *const Signal_Message_MarkAsRead_Identifier = @"Signal_Message_MarkAsRe
|
|||
|
||||
[[NSNotificationCenter defaultCenter] addObserver:self
|
||||
selector:@selector(handleMessageRead:)
|
||||
name:OWSReadReceiptsProcessorMarkedMessageAsReadNotification
|
||||
name:kMessageMarkedAsReadNotification
|
||||
object:nil];
|
||||
|
||||
return self;
|
||||
|
@ -93,6 +93,8 @@ NSString *const Signal_Message_MarkAsRead_Identifier = @"Signal_Message_MarkAsRe
|
|||
|
||||
- (void)handleMessageRead:(NSNotification *)notification
|
||||
{
|
||||
OWSAssert([NSThread isMainThread]);
|
||||
|
||||
if ([notification.object isKindOfClass:[TSIncomingMessage class]]) {
|
||||
TSIncomingMessage *message = (TSIncomingMessage *)notification.object;
|
||||
|
||||
|
|
21
SignalServiceKit/src/Devices/OWSLinkedDeviceReadReceipt.h
Normal file
21
SignalServiceKit/src/Devices/OWSLinkedDeviceReadReceipt.h
Normal file
|
@ -0,0 +1,21 @@
|
|||
//
|
||||
// Copyright (c) 2017 Open Whisper Systems. All rights reserved.
|
||||
//
|
||||
|
||||
#import "TSYapDatabaseObject.h"
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
@interface OWSLinkedDeviceReadReceipt : TSYapDatabaseObject
|
||||
|
||||
@property (nonatomic, readonly) NSString *senderId;
|
||||
@property (nonatomic, readonly) uint64_t timestamp;
|
||||
|
||||
- (instancetype)initWithSenderId:(NSString *)senderId timestamp:(uint64_t)timestamp;
|
||||
|
||||
+ (nullable OWSLinkedDeviceReadReceipt *)linkedDeviceReadReceiptWithSenderId:(NSString *)senderId
|
||||
timestamp:(uint64_t)timestamp;
|
||||
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
53
SignalServiceKit/src/Devices/OWSLinkedDeviceReadReceipt.m
Normal file
53
SignalServiceKit/src/Devices/OWSLinkedDeviceReadReceipt.m
Normal file
|
@ -0,0 +1,53 @@
|
|||
//
|
||||
// Copyright (c) 2017 Open Whisper Systems. All rights reserved.
|
||||
//
|
||||
|
||||
#import "OWSLinkedDeviceReadReceipt.h"
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
@implementation OWSLinkedDeviceReadReceipt
|
||||
|
||||
- (instancetype)initWithSenderId:(NSString *)senderId timestamp:(uint64_t)timestamp;
|
||||
{
|
||||
OWSAssert(senderId.length > 0 && timestamp > 0);
|
||||
|
||||
self = [super initWithUniqueId:[OWSLinkedDeviceReadReceipt uniqueIdForSenderId:senderId timestamp:timestamp]];
|
||||
if (!self) {
|
||||
return self;
|
||||
}
|
||||
|
||||
_senderId = senderId;
|
||||
_timestamp = timestamp;
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
+ (NSString *)uniqueIdForSenderId:(NSString *)senderId timestamp:(uint64_t)timestamp
|
||||
{
|
||||
OWSAssert(senderId.length > 0 && timestamp > 0);
|
||||
|
||||
return [NSString stringWithFormat:@"%@ %llu", senderId, timestamp];
|
||||
}
|
||||
|
||||
+ (nullable OWSLinkedDeviceReadReceipt *)linkedDeviceReadReceiptWithSenderId:(NSString *)senderId
|
||||
timestamp:(uint64_t)timestamp
|
||||
{
|
||||
return [OWSLinkedDeviceReadReceipt fetchObjectWithUniqueID:[self uniqueIdForSenderId:senderId timestamp:timestamp]];
|
||||
}
|
||||
|
||||
#pragma mark - Logging
|
||||
|
||||
+ (NSString *)tag
|
||||
{
|
||||
return [NSString stringWithFormat:@"[%@]", self.class];
|
||||
}
|
||||
|
||||
- (NSString *)tag
|
||||
{
|
||||
return self.class.tag;
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
|
@ -1,23 +0,0 @@
|
|||
// Copyright © 2016 Open Whisper Systems. All rights reserved.
|
||||
|
||||
#import "TSYapDatabaseObject.h"
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
@class YapDatabase;
|
||||
|
||||
@interface OWSReadReceipt : TSYapDatabaseObject
|
||||
|
||||
@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;
|
||||
|
||||
+ (nullable instancetype)firstWithSenderId:(NSString *)senderId timestamp:(uint64_t)timestamp;
|
||||
+ (void)asyncRegisterIndexOnSenderIdAndTimestampWithDatabase:(YapDatabase *)database;
|
||||
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
|
@ -1,140 +0,0 @@
|
|||
//
|
||||
// Copyright (c) 2017 Open Whisper Systems. All rights reserved.
|
||||
//
|
||||
|
||||
#import "OWSReadReceipt.h"
|
||||
#import <YapDatabase/YapDatabase.h>
|
||||
#import <YapDatabase/YapDatabaseConnection.h>
|
||||
#import <YapDatabase/YapDatabaseSecondaryIndex.h>
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
NSString *const OWSReadReceiptIndexOnSenderIdAndTimestamp = @"OWSReadReceiptIndexOnSenderIdAndTimestamp";
|
||||
NSString *const OWSReadReceiptColumnTimestamp = @"timestamp";
|
||||
NSString *const OWSReadReceiptColumnSenderId = @"senderId";
|
||||
|
||||
@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;
|
||||
}
|
||||
|
||||
- (instancetype)initWithCoder:(NSCoder *)decoder
|
||||
{
|
||||
self = [super initWithCoder:decoder];
|
||||
if (!self) {
|
||||
return nil;
|
||||
}
|
||||
|
||||
_valid = YES;
|
||||
_validationErrorMessages = @[];
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
+ (MTLPropertyStorage)storageBehaviorForPropertyWithKey:(NSString *)propertyKey
|
||||
{
|
||||
// Don't store ephemeral properties.
|
||||
if ([propertyKey isEqualToString:@"valid"] || [propertyKey isEqualToString:@"validationErrorMessages"]) {
|
||||
return MTLPropertyStorageNone;
|
||||
} else {
|
||||
return [super storageBehaviorForPropertyWithKey:propertyKey];
|
||||
}
|
||||
}
|
||||
|
||||
+ (void)asyncRegisterIndexOnSenderIdAndTimestampWithDatabase:(YapDatabase *)database
|
||||
{
|
||||
YapDatabaseSecondaryIndexSetup *setup = [YapDatabaseSecondaryIndexSetup new];
|
||||
[setup addColumn:OWSReadReceiptColumnSenderId withType:YapDatabaseSecondaryIndexTypeText];
|
||||
[setup addColumn:OWSReadReceiptColumnTimestamp withType:YapDatabaseSecondaryIndexTypeInteger];
|
||||
|
||||
YapDatabaseSecondaryIndexHandler *handler =
|
||||
[YapDatabaseSecondaryIndexHandler withObjectBlock:^(YapDatabaseReadTransaction *transaction,
|
||||
NSMutableDictionary *dict,
|
||||
NSString *collection,
|
||||
NSString *key,
|
||||
id object) {
|
||||
if ([object isKindOfClass:[OWSReadReceipt class]]) {
|
||||
OWSReadReceipt *readReceipt = (OWSReadReceipt *)object;
|
||||
dict[OWSReadReceiptColumnSenderId] = readReceipt.senderId;
|
||||
dict[OWSReadReceiptColumnTimestamp] = @(readReceipt.timestamp);
|
||||
}
|
||||
}];
|
||||
|
||||
YapDatabaseSecondaryIndex *index = [[YapDatabaseSecondaryIndex alloc] initWithSetup:setup handler:handler];
|
||||
|
||||
[database
|
||||
asyncRegisterExtension:index
|
||||
withName:OWSReadReceiptIndexOnSenderIdAndTimestamp
|
||||
completionBlock:^(BOOL ready) {
|
||||
if (ready) {
|
||||
DDLogDebug(@"%@ Successfully set up extension: %@",
|
||||
self.tag,
|
||||
OWSReadReceiptIndexOnSenderIdAndTimestamp);
|
||||
} else {
|
||||
DDLogError(
|
||||
@"%@ Unable to setup extension: %@", self.tag, OWSReadReceiptIndexOnSenderIdAndTimestamp);
|
||||
}
|
||||
}];
|
||||
}
|
||||
|
||||
+ (nullable instancetype)firstWithSenderId:(NSString *)senderId timestamp:(uint64_t)timestamp
|
||||
{
|
||||
__block OWSReadReceipt *foundReadReceipt;
|
||||
|
||||
NSString *queryFormat = [NSString
|
||||
stringWithFormat:@"WHERE %@ = ? AND %@ = ?", OWSReadReceiptColumnSenderId, OWSReadReceiptColumnTimestamp];
|
||||
YapDatabaseQuery *query = [YapDatabaseQuery queryWithFormat:queryFormat, senderId, @(timestamp)];
|
||||
|
||||
[[self dbReadConnection] readWithBlock:^(YapDatabaseReadTransaction *_Nonnull transaction) {
|
||||
[[transaction ext:OWSReadReceiptIndexOnSenderIdAndTimestamp]
|
||||
enumerateKeysAndObjectsMatchingQuery:query
|
||||
usingBlock:^(NSString *collection, NSString *key, id object, BOOL *stop) {
|
||||
if (![object isKindOfClass:[OWSReadReceipt class]]) {
|
||||
DDLogError(@"%@ Unexpected object in index: %@", self.tag, object);
|
||||
return;
|
||||
}
|
||||
|
||||
foundReadReceipt = (OWSReadReceipt *)object;
|
||||
*stop = YES;
|
||||
}];
|
||||
}];
|
||||
|
||||
return foundReadReceipt;
|
||||
}
|
||||
|
||||
#pragma mark - Logging
|
||||
|
||||
+ (NSString *)tag
|
||||
{
|
||||
return [NSString stringWithFormat:@"[%@]", self.class];
|
||||
}
|
||||
|
||||
- (NSString *)tag
|
||||
{
|
||||
return self.class.tag;
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
|
@ -6,11 +6,11 @@
|
|||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
@class OWSReadReceipt;
|
||||
@class OWSLinkedDeviceReadReceipt;
|
||||
|
||||
@interface OWSReadReceiptsForLinkedDevicesMessage : OWSOutgoingSyncMessage
|
||||
|
||||
- (instancetype)initWithReadReceipts:(NSArray<OWSReadReceipt *> *)readReceipts;
|
||||
- (instancetype)initWithReadReceipts:(NSArray<OWSLinkedDeviceReadReceipt *> *)readReceipts;
|
||||
|
||||
@end
|
||||
|
||||
|
|
|
@ -3,20 +3,20 @@
|
|||
//
|
||||
|
||||
#import "OWSReadReceiptsForLinkedDevicesMessage.h"
|
||||
#import "OWSReadReceipt.h"
|
||||
#import "OWSLinkedDeviceReadReceipt.h"
|
||||
#import "OWSSignalServiceProtos.pb.h"
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
@interface OWSReadReceiptsForLinkedDevicesMessage ()
|
||||
|
||||
@property (nonatomic, readonly) NSArray<OWSReadReceipt *> *readReceipts;
|
||||
@property (nonatomic, readonly) NSArray<OWSLinkedDeviceReadReceipt *> *readReceipts;
|
||||
|
||||
@end
|
||||
|
||||
@implementation OWSReadReceiptsForLinkedDevicesMessage
|
||||
|
||||
- (instancetype)initWithReadReceipts:(NSArray<OWSReadReceipt *> *)readReceipts
|
||||
- (instancetype)initWithReadReceipts:(NSArray<OWSLinkedDeviceReadReceipt *> *)readReceipts
|
||||
{
|
||||
self = [super init];
|
||||
if (!self) {
|
||||
|
@ -31,7 +31,7 @@ NS_ASSUME_NONNULL_BEGIN
|
|||
- (OWSSignalServiceProtosSyncMessageBuilder *)syncMessageBuilder
|
||||
{
|
||||
OWSSignalServiceProtosSyncMessageBuilder *syncMessageBuilder = [OWSSignalServiceProtosSyncMessageBuilder new];
|
||||
for (OWSReadReceipt *readReceipt in self.readReceipts) {
|
||||
for (OWSLinkedDeviceReadReceipt *readReceipt in self.readReceipts) {
|
||||
OWSSignalServiceProtosSyncMessageReadBuilder *readProtoBuilder =
|
||||
[OWSSignalServiceProtosSyncMessageReadBuilder new];
|
||||
[readProtoBuilder setSender:readReceipt.senderId];
|
||||
|
|
|
@ -4,7 +4,6 @@
|
|||
|
||||
#import "OWSReadReceiptsForSenderMessage.h"
|
||||
#import "NSDate+OWS.h"
|
||||
#import "OWSReadReceipt.h"
|
||||
#import "OWSSignalServiceProtos.pb.h"
|
||||
#import "SignalRecipient.h"
|
||||
|
||||
|
|
|
@ -1,41 +0,0 @@
|
|||
//
|
||||
// Copyright (c) 2017 Open Whisper Systems. All rights reserved.
|
||||
//
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
@class OWSReadReceipt;
|
||||
@class OWSSignalServiceProtosSyncMessageRead;
|
||||
@class TSIncomingMessage;
|
||||
@class TSStorageManager;
|
||||
@class YapDatabaseReadWriteTransaction;
|
||||
|
||||
extern NSString *const OWSReadReceiptsProcessorMarkedMessageAsReadNotification;
|
||||
|
||||
// TODO:
|
||||
@interface OWSReadReceiptsProcessor : NSObject
|
||||
|
||||
/**
|
||||
* Mark existing messages as read from the given received read receipts.
|
||||
*/
|
||||
- (instancetype)initWithReadReceiptProtos:(NSArray<OWSSignalServiceProtosSyncMessageRead *> *)readReceiptProtos
|
||||
storageManager:(TSStorageManager *)storageManager;
|
||||
|
||||
/**
|
||||
* Mark a new message as read in the rare (but does happen!) case that we receive the read receipt before the message
|
||||
* the read receipt refers to.
|
||||
*/
|
||||
- (instancetype)initWithIncomingMessage:(TSIncomingMessage *)incomingMessage
|
||||
storageManager:(TSStorageManager *)storageManager;
|
||||
|
||||
- (instancetype)initWithReadReceipts:(NSArray<OWSReadReceipt *> *)readReceipts
|
||||
storageManager:(TSStorageManager *)storageManager NS_DESIGNATED_INITIALIZER;
|
||||
|
||||
- (instancetype)init NS_UNAVAILABLE;
|
||||
|
||||
- (void)process;
|
||||
- (void)processWithTransaction:(YapDatabaseReadWriteTransaction *)transaction;
|
||||
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
|
@ -1,162 +0,0 @@
|
|||
//
|
||||
// Copyright (c) 2017 Open Whisper Systems. All rights reserved.
|
||||
//
|
||||
|
||||
#import "OWSReadReceiptsProcessor.h"
|
||||
#import "NSNotificationCenter+OWS.h"
|
||||
#import "OWSDisappearingMessagesJob.h"
|
||||
#import "OWSReadReceipt.h"
|
||||
#import "OWSSignalServiceProtos.pb.h"
|
||||
#import "TSContactThread.h"
|
||||
#import "TSDatabaseView.h"
|
||||
#import "TSIncomingMessage.h"
|
||||
#import "TSStorageManager.h"
|
||||
#import <YapDatabase/YapDatabaseConnection.h>
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
NSString *const OWSReadReceiptsProcessorMarkedMessageAsReadNotification =
|
||||
@"OWSReadReceiptsProcessorMarkedMessageAsReadNotification";
|
||||
|
||||
@interface OWSReadReceiptsProcessor ()
|
||||
|
||||
@property (nonatomic, readonly) NSArray<OWSReadReceipt *> *readReceipts;
|
||||
@property (nonatomic, readonly) TSStorageManager *storageManager;
|
||||
|
||||
@end
|
||||
|
||||
@implementation OWSReadReceiptsProcessor
|
||||
|
||||
- (instancetype)initWithReadReceipts:(NSArray<OWSReadReceipt *> *)readReceipts
|
||||
storageManager:(TSStorageManager *)storageManager;
|
||||
{
|
||||
self = [super init];
|
||||
if (!self) {
|
||||
return self;
|
||||
}
|
||||
|
||||
_readReceipts = [readReceipts copy];
|
||||
_storageManager = storageManager;
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
- (instancetype)initWithReadReceiptProtos:(NSArray<OWSSignalServiceProtosSyncMessageRead *> *)readReceiptProtos
|
||||
storageManager:(TSStorageManager *)storageManager
|
||||
{
|
||||
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: %@", self.tag, readReceipt.validationErrorMessages);
|
||||
}
|
||||
}
|
||||
|
||||
return [self initWithReadReceipts:[readReceipts copy] storageManager:storageManager];
|
||||
}
|
||||
|
||||
- (instancetype)initWithIncomingMessage:(TSIncomingMessage *)message storageManager:(TSStorageManager *)storageManager
|
||||
{
|
||||
NSString *messageAuthorId = message.messageAuthorId;
|
||||
OWSAssert(messageAuthorId.length > 0);
|
||||
|
||||
OWSReadReceipt *readReceipt = [OWSReadReceipt firstWithSenderId:messageAuthorId timestamp:message.timestamp];
|
||||
if (readReceipt) {
|
||||
DDLogInfo(@"%@ Found prior read receipt for incoming message.", self.tag);
|
||||
return [self initWithReadReceipts:@[ readReceipt ] storageManager:storageManager];
|
||||
} else {
|
||||
// no-op
|
||||
return [self initWithReadReceipts:@[] storageManager:storageManager];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)process
|
||||
{
|
||||
[[self.storageManager newDatabaseConnection] readWriteWithBlock:^(YapDatabaseReadWriteTransaction *transaction) {
|
||||
[self processWithTransaction:transaction];
|
||||
}];
|
||||
}
|
||||
|
||||
- (void)processWithTransaction:(YapDatabaseReadWriteTransaction *)transaction
|
||||
{
|
||||
OWSAssert(transaction);
|
||||
|
||||
DDLogDebug(@"%@ Processing %ld read receipts.", self.tag, (unsigned long)self.readReceipts.count);
|
||||
for (OWSReadReceipt *readReceipt in self.readReceipts) {
|
||||
TSIncomingMessage *message = [TSIncomingMessage findMessageWithAuthorId:readReceipt.senderId
|
||||
timestamp:readReceipt.timestamp
|
||||
transaction:transaction];
|
||||
if (message) {
|
||||
OWSAssert(message.thread);
|
||||
|
||||
// Mark all unread messages in this thread that are older than message specified in the read
|
||||
// receipt.
|
||||
NSMutableArray<id<OWSReadTracking>> *interactionsToMarkAsRead = [NSMutableArray new];
|
||||
|
||||
// Always mark the message specified by the read receipt as read.
|
||||
[interactionsToMarkAsRead addObject:message];
|
||||
|
||||
[[TSDatabaseView unseenDatabaseViewExtension:transaction]
|
||||
enumerateRowsInGroup:message.uniqueThreadId
|
||||
usingBlock:^(NSString *collection,
|
||||
NSString *key,
|
||||
id object,
|
||||
id metadata,
|
||||
NSUInteger index,
|
||||
BOOL *stop) {
|
||||
|
||||
TSInteraction *interaction = object;
|
||||
if (interaction.timestampForSorting > message.timestampForSorting) {
|
||||
*stop = YES;
|
||||
return;
|
||||
}
|
||||
|
||||
id<OWSReadTracking> possiblyRead = (id<OWSReadTracking>)object;
|
||||
OWSAssert(!possiblyRead.read);
|
||||
[interactionsToMarkAsRead addObject:possiblyRead];
|
||||
}];
|
||||
|
||||
for (id<OWSReadTracking> interaction in interactionsToMarkAsRead) {
|
||||
// * Don't send a read receipt in response to a read receipt.
|
||||
// * Don't update expiration; we'll do that in the next statement.
|
||||
[interaction markAsReadWithTransaction:transaction sendReadReceipt:NO updateExpiration:NO];
|
||||
|
||||
if ([interaction isKindOfClass:[TSMessage class]]) {
|
||||
TSMessage *otherMessage = (TSMessage *)interaction;
|
||||
|
||||
// Update expiration using the timestamp from the readReceipt.
|
||||
[OWSDisappearingMessagesJob setExpirationForMessage:otherMessage
|
||||
expirationStartedAt:readReceipt.timestamp];
|
||||
|
||||
// Fire event that will cancel any pending notifications for this message.
|
||||
[[NSNotificationCenter defaultCenter]
|
||||
postNotificationNameAsync:OWSReadReceiptsProcessorMarkedMessageAsReadNotification
|
||||
object:otherMessage];
|
||||
}
|
||||
}
|
||||
|
||||
// If it was previously saved, no need to keep it around any longer.
|
||||
[readReceipt removeWithTransaction:transaction];
|
||||
} else {
|
||||
DDLogDebug(@"%@ Received read receipt for an unknown message. Saving it for later.", self.tag);
|
||||
[readReceipt saveWithTransaction:transaction];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+ (NSString *)tag
|
||||
{
|
||||
return [NSString stringWithFormat:@"[%@]", self.class];
|
||||
}
|
||||
|
||||
- (NSString *)tag
|
||||
{
|
||||
return self.class.tag;
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
|
@ -19,7 +19,6 @@
|
|||
#import "OWSIncomingSentMessageTranscript.h"
|
||||
#import "OWSMessageSender.h"
|
||||
#import "OWSReadReceiptManager.h"
|
||||
#import "OWSReadReceiptsProcessor.h"
|
||||
#import "OWSRecordTranscriptJob.h"
|
||||
#import "OWSSyncContactsMessage.h"
|
||||
#import "OWSSyncGroupsMessage.h"
|
||||
|
@ -570,10 +569,8 @@ NS_ASSUME_NONNULL_BEGIN
|
|||
} else if (syncMessage.read.count > 0) {
|
||||
DDLogInfo(@"%@ Received %ld read receipt(s)", self.tag, (u_long)syncMessage.read.count);
|
||||
|
||||
OWSReadReceiptsProcessor *readReceiptsProcessor =
|
||||
[[OWSReadReceiptsProcessor alloc] initWithReadReceiptProtos:syncMessage.read
|
||||
storageManager:self.storageManager];
|
||||
[readReceiptsProcessor processWithTransaction:transaction];
|
||||
[OWSReadReceiptManager.sharedManager processReadReceiptsFromLinkedDevice:syncMessage.read
|
||||
transaction:transaction];
|
||||
} else if (syncMessage.hasVerified) {
|
||||
DDLogInfo(@"%@ Received verification state for %@", self.tag, syncMessage.verified.destination);
|
||||
// TODO: Do this synchronously.
|
||||
|
@ -900,14 +897,12 @@ NS_ASSUME_NONNULL_BEGIN
|
|||
}
|
||||
|
||||
if (thread && incomingMessage) {
|
||||
// In case we already have a read receipt for this new message (this happens sometimes).
|
||||
[OWSReadReceiptManager.sharedManager updateIncomingMessage:incomingMessage
|
||||
transaction:transaction];
|
||||
|
||||
// TODO: Do this synchronously.
|
||||
dispatch_async(dispatch_get_main_queue(), ^{
|
||||
// In case we already have a read receipt for this new message (happens sometimes).
|
||||
OWSReadReceiptsProcessor *readReceiptsProcessor =
|
||||
[[OWSReadReceiptsProcessor alloc] initWithIncomingMessage:incomingMessage
|
||||
storageManager:self.storageManager];
|
||||
[readReceiptsProcessor process];
|
||||
|
||||
[OWSDisappearingMessagesJob becomeConsistentWithConfigurationForMessage:incomingMessage
|
||||
contactsManager:self.contactsManager];
|
||||
|
||||
|
|
|
@ -6,11 +6,14 @@ NS_ASSUME_NONNULL_BEGIN
|
|||
|
||||
@class OWSSignalServiceProtosEnvelope;
|
||||
@class OWSSignalServiceProtosReceiptMessage;
|
||||
@class OWSSignalServiceProtosSyncMessageRead;
|
||||
@class TSIncomingMessage;
|
||||
@class TSOutgoingMessage;
|
||||
@class TSThread;
|
||||
@class YapDatabaseReadWriteTransaction;
|
||||
|
||||
extern NSString *const kMessageMarkedAsReadNotification;
|
||||
|
||||
// There are four kinds of read receipts:
|
||||
//
|
||||
// * Read receipts that this client sends to linked
|
||||
|
@ -26,12 +29,14 @@ NS_ASSUME_NONNULL_BEGIN
|
|||
// * These read receipts are saved so that they can be applied
|
||||
// if they arrive before the corresponding message.
|
||||
//
|
||||
// TODO: Merge OWSReadReceiptsProcessor into this class.
|
||||
// This manager is responsible for handling and emitting all four kinds.
|
||||
@interface OWSReadReceiptManager : NSObject
|
||||
|
||||
- (instancetype)init NS_UNAVAILABLE;
|
||||
+ (instancetype)sharedManager;
|
||||
|
||||
#pragma mark - Sender/Recipient Read Receipts
|
||||
|
||||
// This method should be called when we receive a read receipt
|
||||
// from a user to whom we have sent a message.
|
||||
//
|
||||
|
@ -42,6 +47,16 @@ NS_ASSUME_NONNULL_BEGIN
|
|||
- (void)updateOutgoingMessageFromLinkedDevice:(TSOutgoingMessage *)message
|
||||
transaction:(YapDatabaseReadWriteTransaction *)transaction;
|
||||
|
||||
#pragma mark - Linked Device Read Receipts
|
||||
|
||||
- (void)processReadReceiptsFromLinkedDevice:(NSArray<OWSSignalServiceProtosSyncMessageRead *> *)readReceiptProtos
|
||||
transaction:(YapDatabaseReadWriteTransaction *)transaction;
|
||||
|
||||
- (void)updateIncomingMessage:(TSIncomingMessage *)message
|
||||
transaction:(YapDatabaseReadWriteTransaction *)transaction;
|
||||
|
||||
#pragma mark - Locally Read
|
||||
|
||||
// This method cues this manager:
|
||||
//
|
||||
// * ...to inform the sender that this message was read (if read receipts
|
||||
|
|
|
@ -3,8 +3,9 @@
|
|||
//
|
||||
|
||||
#import "OWSReadReceiptManager.h"
|
||||
#import "NSNotificationCenter+OWS.h"
|
||||
#import "OWSLinkedDeviceReadReceipt.h"
|
||||
#import "OWSMessageSender.h"
|
||||
#import "OWSReadReceipt.h"
|
||||
#import "OWSReadReceiptsForLinkedDevicesMessage.h"
|
||||
#import "OWSReadReceiptsForSenderMessage.h"
|
||||
#import "OWSSignalServiceProtos.pb.h"
|
||||
|
@ -17,6 +18,8 @@
|
|||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
NSString *const kMessageMarkedAsReadNotification = @"kMessageMarkedAsReadNotification";
|
||||
|
||||
@interface TSRecipientReadReceipt : TSYapDatabaseObject
|
||||
|
||||
@property (nonatomic, readonly) uint64_t sentTimestamp;
|
||||
|
@ -115,7 +118,7 @@ NSString *const OWSReadReceiptManagerAreReadReceiptsEnabled = @"areReadReceiptsE
|
|||
// we will send to our linked devices.
|
||||
//
|
||||
// Should only be accessed while synchronized on the OWSReadReceiptManager.
|
||||
@property (nonatomic, readonly) NSMutableDictionary<NSString *, OWSReadReceipt *> *toLinkedDevicesReadReceiptMap;
|
||||
@property (nonatomic, readonly) NSMutableDictionary<NSString *, OWSLinkedDeviceReadReceipt *> *toLinkedDevicesReadReceiptMap;
|
||||
|
||||
// A map of "recipient id"-to-"timestamp list" for read receipts that
|
||||
// we will send to senders.
|
||||
|
@ -211,7 +214,7 @@ NSString *const OWSReadReceiptManagerAreReadReceiptsEnabled = @"areReadReceiptsE
|
|||
|
||||
// Process read receipts every N seconds.
|
||||
//
|
||||
// We want a value high enough to allow us to effectively deduplicate,
|
||||
// We want a value high enough to allow us to effectively de-duplicate,
|
||||
// read receipts without being so high that we risk not sending read
|
||||
// receipts due to app exit.
|
||||
const CGFloat kProcessingFrequencySeconds = 3.f;
|
||||
|
@ -288,46 +291,10 @@ NSString *const OWSReadReceiptManagerAreReadReceiptsEnabled = @"areReadReceiptsE
|
|||
|
||||
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
|
||||
[self.dbConnection readWriteWithBlock:^(YapDatabaseReadWriteTransaction *transaction) {
|
||||
NSMutableArray<id<OWSReadTracking>> *interactions = [NSMutableArray new];
|
||||
|
||||
[[TSDatabaseView unseenDatabaseViewExtension:transaction]
|
||||
enumerateRowsInGroup:thread.uniqueId
|
||||
usingBlock:^(NSString *collection,
|
||||
NSString *key,
|
||||
id object,
|
||||
id metadata,
|
||||
NSUInteger index,
|
||||
BOOL *stop) {
|
||||
|
||||
if (![object conformsToProtocol:@protocol(OWSReadTracking)]) {
|
||||
OWSFail(
|
||||
@"Expected to conform to OWSReadTracking: object with class: %@ collection: %@ "
|
||||
@"key: %@",
|
||||
[object class],
|
||||
collection,
|
||||
key);
|
||||
return;
|
||||
}
|
||||
id<OWSReadTracking> possiblyRead = (id<OWSReadTracking>)object;
|
||||
|
||||
if (possiblyRead.timestampForSorting > timestamp) {
|
||||
*stop = YES;
|
||||
return;
|
||||
}
|
||||
|
||||
OWSAssert(!possiblyRead.read);
|
||||
if (!possiblyRead.read) {
|
||||
[interactions addObject:possiblyRead];
|
||||
}
|
||||
}];
|
||||
|
||||
if (interactions.count < 1) {
|
||||
return;
|
||||
}
|
||||
DDLogError(@"Marking %zd messages as read.", interactions.count);
|
||||
for (id<OWSReadTracking> possiblyRead in interactions) {
|
||||
[possiblyRead markAsReadWithTransaction:transaction sendReadReceipt:YES updateExpiration:YES];
|
||||
}
|
||||
[self markAsReadBeforeTimestamp:timestamp
|
||||
thread:thread
|
||||
wasLocal:YES
|
||||
transaction:transaction];
|
||||
}];
|
||||
});
|
||||
}
|
||||
|
@ -343,10 +310,10 @@ NSString *const OWSReadReceiptManagerAreReadReceiptsEnabled = @"areReadReceiptsE
|
|||
NSString *messageAuthorId = message.messageAuthorId;
|
||||
OWSAssert(messageAuthorId.length > 0);
|
||||
|
||||
OWSReadReceipt *newReadReceipt =
|
||||
[[OWSReadReceipt alloc] initWithSenderId:messageAuthorId timestamp:message.timestamp];
|
||||
OWSLinkedDeviceReadReceipt *newReadReceipt =
|
||||
[[OWSLinkedDeviceReadReceipt alloc] initWithSenderId:messageAuthorId timestamp:message.timestamp];
|
||||
|
||||
OWSReadReceipt *_Nullable oldReadReceipt = self.toLinkedDevicesReadReceiptMap[threadUniqueId];
|
||||
OWSLinkedDeviceReadReceipt *_Nullable oldReadReceipt = self.toLinkedDevicesReadReceiptMap[threadUniqueId];
|
||||
if (oldReadReceipt && oldReadReceipt.timestamp > newReadReceipt.timestamp) {
|
||||
// If there's an existing read receipt for the same thread with
|
||||
// a newer timestamp, discard the new read receipt.
|
||||
|
@ -445,6 +412,148 @@ NSString *const OWSReadReceiptManagerAreReadReceiptsEnabled = @"areReadReceiptsE
|
|||
[TSRecipientReadReceipt removeRecipientIdsForTimestamp:message.timestamp transaction:transaction];
|
||||
}
|
||||
|
||||
#pragma mark - Linked Device Read Receipts
|
||||
|
||||
- (void)updateIncomingMessage:(TSIncomingMessage *)message
|
||||
transaction:(YapDatabaseReadWriteTransaction *)transaction
|
||||
{
|
||||
OWSAssert(message);
|
||||
OWSAssert(transaction);
|
||||
|
||||
NSString *senderId = message.messageAuthorId;
|
||||
uint64_t timestamp = message.timestamp;
|
||||
if (senderId.length < 1 || timestamp < 1) {
|
||||
OWSFail(@"%@ Invalid incoming message: %@ %llu", self.tag, senderId, timestamp);
|
||||
return;
|
||||
}
|
||||
|
||||
OWSLinkedDeviceReadReceipt *_Nullable readReceipt = [OWSLinkedDeviceReadReceipt linkedDeviceReadReceiptWithSenderId:senderId
|
||||
timestamp:timestamp];
|
||||
if (!readReceipt) {
|
||||
return;
|
||||
}
|
||||
[message markAsReadWithTransaction:transaction sendReadReceipt:NO updateExpiration:YES];
|
||||
[readReceipt removeWithTransaction:transaction];
|
||||
|
||||
dispatch_async(dispatch_get_main_queue(), ^{
|
||||
[[NSNotificationCenter defaultCenter]
|
||||
postNotificationNameAsync:kMessageMarkedAsReadNotification
|
||||
object:message];
|
||||
});
|
||||
}
|
||||
|
||||
- (void)processReadReceiptsFromLinkedDevice:(NSArray<OWSSignalServiceProtosSyncMessageRead *> *)readReceiptProtos
|
||||
transaction:(YapDatabaseReadWriteTransaction *)transaction
|
||||
{
|
||||
OWSAssert(readReceiptProtos);
|
||||
OWSAssert(transaction);
|
||||
|
||||
for (OWSSignalServiceProtosSyncMessageRead *readReceiptProto in readReceiptProtos) {
|
||||
NSString *_Nullable senderId = readReceiptProto.sender;
|
||||
uint64_t timestamp = readReceiptProto.timestamp;
|
||||
BOOL isValid = senderId.length > 0 && timestamp > 0;
|
||||
if (!isValid) {
|
||||
continue;
|
||||
}
|
||||
|
||||
NSArray<TSIncomingMessage *> *messages = (NSArray<TSIncomingMessage *> *) [TSInteraction interactionsWithTimestamp:timestamp
|
||||
ofClass:[TSIncomingMessage class]
|
||||
withTransaction:transaction];
|
||||
if (messages.count > 0) {
|
||||
for (TSIncomingMessage *message in messages) {
|
||||
OWSAssert([message isKindOfClass:[TSIncomingMessage class]]);
|
||||
|
||||
[self markAsReadOnLinkedDevice:message
|
||||
transaction:transaction];
|
||||
}
|
||||
} else {
|
||||
// Received read receipt for unknown incoming message.
|
||||
// Persist in case we receive the incoming message later.
|
||||
OWSLinkedDeviceReadReceipt *readReceipt = [[OWSLinkedDeviceReadReceipt alloc] initWithSenderId:senderId timestamp:timestamp];
|
||||
[readReceipt saveWithTransaction:transaction];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
- (void)markAsReadOnLinkedDevice:(TSIncomingMessage *)message
|
||||
transaction:(YapDatabaseReadWriteTransaction *)transaction
|
||||
{
|
||||
OWSAssert(message);
|
||||
OWSAssert(transaction);
|
||||
|
||||
[self markAsReadBeforeTimestamp:message.timestamp
|
||||
thread:message.thread
|
||||
wasLocal:NO
|
||||
transaction:transaction];
|
||||
}
|
||||
|
||||
#pragma mark - Mark As Read
|
||||
|
||||
- (void)markAsReadBeforeTimestamp:(uint64_t)timestamp
|
||||
thread:(TSThread *)thread
|
||||
wasLocal:(BOOL)wasLocal
|
||||
transaction:(YapDatabaseReadWriteTransaction *)transaction
|
||||
{
|
||||
OWSAssert(timestamp > 0);
|
||||
OWSAssert(thread);
|
||||
OWSAssert(transaction);
|
||||
|
||||
NSMutableArray<id<OWSReadTracking>> *interactions = [NSMutableArray new];
|
||||
|
||||
[[TSDatabaseView unseenDatabaseViewExtension:transaction]
|
||||
enumerateRowsInGroup:thread.uniqueId
|
||||
usingBlock:^(NSString *collection,
|
||||
NSString *key,
|
||||
id object,
|
||||
id metadata,
|
||||
NSUInteger index,
|
||||
BOOL *stop) {
|
||||
|
||||
if (![object conformsToProtocol:@protocol(OWSReadTracking)]) {
|
||||
OWSFail(
|
||||
@"Expected to conform to OWSReadTracking: object with class: %@ collection: %@ "
|
||||
@"key: %@",
|
||||
[object class],
|
||||
collection,
|
||||
key);
|
||||
return;
|
||||
}
|
||||
id<OWSReadTracking> possiblyRead = (id<OWSReadTracking>)object;
|
||||
|
||||
if (possiblyRead.timestampForSorting > timestamp) {
|
||||
*stop = YES;
|
||||
return;
|
||||
}
|
||||
|
||||
OWSAssert(!possiblyRead.read);
|
||||
if (!possiblyRead.read) {
|
||||
[interactions addObject:possiblyRead];
|
||||
}
|
||||
}];
|
||||
|
||||
if (interactions.count < 1) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (wasLocal) {
|
||||
DDLogError(@"Marking %zd messages as read locally.", interactions.count);
|
||||
} else {
|
||||
DDLogError(@"Marking %zd messages as read by linked device.", interactions.count);
|
||||
}
|
||||
for (id<OWSReadTracking> possiblyRead in interactions) {
|
||||
[possiblyRead markAsReadWithTransaction:transaction sendReadReceipt:wasLocal updateExpiration:YES];
|
||||
|
||||
if ([possiblyRead isKindOfClass:[TSIncomingMessage class]]) {
|
||||
TSIncomingMessage *incomingMessage = (TSIncomingMessage *)possiblyRead;
|
||||
dispatch_async(dispatch_get_main_queue(), ^{
|
||||
[[NSNotificationCenter defaultCenter]
|
||||
postNotificationNameAsync:kMessageMarkedAsReadNotification
|
||||
object:incomingMessage];
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#pragma mark - Settings
|
||||
|
||||
- (void)prepareCachedValues
|
||||
|
|
|
@ -9,7 +9,6 @@
|
|||
#import "OWSFailedAttachmentDownloadsJob.h"
|
||||
#import "OWSFailedMessagesJob.h"
|
||||
#import "OWSIncomingMessageFinder.h"
|
||||
#import "OWSReadReceipt.h"
|
||||
#import "SignalRecipient.h"
|
||||
#import "TSAttachmentStream.h"
|
||||
#import "TSDatabaseSecondaryIndexes.h"
|
||||
|
@ -332,10 +331,9 @@ void setDatabaseInitialized()
|
|||
[TSDatabaseView asyncRegisterThreadOutgoingMessagesDatabaseView];
|
||||
[TSDatabaseView asyncRegisterThreadSpecialMessagesDatabaseView];
|
||||
|
||||
// Register extensions which aren't essential for rendering threads async
|
||||
// Register extensions which aren't essential for rendering threads async.
|
||||
[[OWSIncomingMessageFinder new] asyncRegisterExtension];
|
||||
[TSDatabaseView asyncRegisterSecondaryDevicesDatabaseView];
|
||||
[OWSReadReceipt asyncRegisterIndexOnSenderIdAndTimestampWithDatabase:self.database];
|
||||
[OWSDisappearingMessagesFinder asyncRegisterDatabaseExtensions:self];
|
||||
OWSFailedMessagesJob *failedMessagesJob = [[OWSFailedMessagesJob alloc] initWithStorageManager:self];
|
||||
[failedMessagesJob asyncRegisterDatabaseExtensions];
|
||||
|
|
Loading…
Reference in a new issue