mirror of https://github.com/oxen-io/session-ios
parent
289291e03d
commit
ffe44e68be
@ -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
|
@ -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
|
|
@ -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
|
|
Loading…
Reference in New Issue