Merge branch 'charlesmchen/showRecipientReadReceipts2'

pull/1/head
Matthew Chen 8 years ago
commit 442ab102ba

@ -80,6 +80,7 @@
34C42D611F4734CA0072EC04 /* OWSContactOffersCell.m in Sources */ = {isa = PBXBuildFile; fileRef = 34C42D601F4734CA0072EC04 /* OWSContactOffersCell.m */; };
34C42D661F4734ED0072EC04 /* OWSContactOffersInteraction.m in Sources */ = {isa = PBXBuildFile; fileRef = 34C42D631F4734ED0072EC04 /* OWSContactOffersInteraction.m */; };
34C42D671F4734ED0072EC04 /* TSUnreadIndicatorInteraction.m in Sources */ = {isa = PBXBuildFile; fileRef = 34C42D651F4734ED0072EC04 /* TSUnreadIndicatorInteraction.m */; };
34CA1C251F706B5400E51C51 /* NSAttributedString+OWS.m in Sources */ = {isa = PBXBuildFile; fileRef = 34CA1C241F706B5400E51C51 /* NSAttributedString+OWS.m */; };
34CCAF381F0C0599004084F4 /* AppUpdateNag.m in Sources */ = {isa = PBXBuildFile; fileRef = 34CCAF371F0C0599004084F4 /* AppUpdateNag.m */; };
34CCAF3B1F0C2748004084F4 /* OWSAddToContactViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 34CCAF3A1F0C2748004084F4 /* OWSAddToContactViewController.m */; };
34CE88E71F2FB9A10098030F /* ProfileViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 34CE88E61F2FB9A10098030F /* ProfileViewController.m */; };
@ -526,6 +527,8 @@
34C42D631F4734ED0072EC04 /* OWSContactOffersInteraction.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = OWSContactOffersInteraction.m; sourceTree = "<group>"; };
34C42D641F4734ED0072EC04 /* TSUnreadIndicatorInteraction.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = TSUnreadIndicatorInteraction.h; sourceTree = "<group>"; };
34C42D651F4734ED0072EC04 /* TSUnreadIndicatorInteraction.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = TSUnreadIndicatorInteraction.m; sourceTree = "<group>"; };
34CA1C231F706B5400E51C51 /* NSAttributedString+OWS.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "NSAttributedString+OWS.h"; sourceTree = "<group>"; };
34CA1C241F706B5400E51C51 /* NSAttributedString+OWS.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "NSAttributedString+OWS.m"; sourceTree = "<group>"; };
34CCAF361F0C0599004084F4 /* AppUpdateNag.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = AppUpdateNag.h; sourceTree = "<group>"; };
34CCAF371F0C0599004084F4 /* AppUpdateNag.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = AppUpdateNag.m; sourceTree = "<group>"; };
34CCAF391F0C2748004084F4 /* OWSAddToContactViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = OWSAddToContactViewController.h; sourceTree = "<group>"; };
@ -1415,6 +1418,8 @@
76EB04EA18170B33006006FC /* FunctionalUtil.h */,
76EB04EB18170B33006006FC /* FunctionalUtil.m */,
455AC69A1F4F79E500134004 /* ImageCache.swift */,
34CA1C231F706B5400E51C51 /* NSAttributedString+OWS.h */,
34CA1C241F706B5400E51C51 /* NSAttributedString+OWS.m */,
B62F5E0E1C2980B4000D370C /* NSData+ows_StripToken.h */,
B62F5E0F1C2980B4000D370C /* NSData+ows_StripToken.m */,
76EB04EC18170B33006006FC /* NumberUtil.h */,
@ -1436,6 +1441,7 @@
4542F0951EBB9E9A00C7EE92 /* Promise+retainUntilComplete.swift */,
76EB04F518170B33006006FC /* StringUtil.h */,
76EB04F618170B33006006FC /* StringUtil.m */,
4521C3BF1F59F3BA00B4C582 /* TextFieldHelper.swift */,
345670FF1E89A5F1006EE662 /* ThreadUtil.h */,
345671001E89A5F1006EE662 /* ThreadUtil.m */,
FCFA64B11A24F29E0007FB87 /* UI Categories */,
@ -1446,7 +1452,6 @@
76EB04FB18170B33006006FC /* Util.h */,
45F170D51E315310003FC1F2 /* Weak.swift */,
45F170CB1E310E22003FC1F2 /* WeakTimer.swift */,
4521C3BF1F59F3BA00B4C582 /* TextFieldHelper.swift */,
);
path = util;
sourceTree = "<group>";
@ -2210,6 +2215,7 @@
34CE88ED1F3237260098030F /* ProfileFetcherJob.swift in Sources */,
34B3F8791E8DF1700035BE1A /* CountryCodeViewController.m in Sources */,
4CE0E3771B954546007210CF /* TSAnimatedAdapter.m in Sources */,
34CA1C251F706B5400E51C51 /* NSAttributedString+OWS.m in Sources */,
4531C9C41DD8E6D800F08304 /* JSQMessagesCollectionViewCell+OWS.m in Sources */,
4542F0961EBB9E9A00C7EE92 /* Promise+retainUntilComplete.swift in Sources */,
4516E3FF1DD2193B00DC4206 /* OWS101ExistingUsersBlockOnIdentityChange.m in Sources */,

@ -196,8 +196,8 @@ static NSString *const kURLHostVerifyPrefix = @"verify";
NSString *lastLaunchedAppVersion = AppVersion.instance.lastAppVersion;
NSString *lastCompletedLaunchAppVersion = AppVersion.instance.lastCompletedLaunchAppVersion;
// Every time we change a database view in such a way that might cause a delay on launch,
// we need to bump this constant.
// Every time we change or add a database view in such a way that
// might cause a delay on launch, we need to bump this constant.
//
// We added a number of database views in v2.13.0.
NSString *kLastVersionWithDatabaseViewChange = @"2.13.0";
@ -833,7 +833,7 @@ static NSString *const kURLHostVerifyPrefix = @"verify";
[[OWSProfileManager sharedManager] ensureLocalProfileCached];
// For non-legacy users, read receipts are on by default.
[[Environment preferences] setAreReadReceiptsEnabled:YES];
[OWSReadReceiptManager.sharedManager setAreReadReceiptsEnabled:YES];
}
}

@ -12,6 +12,7 @@
#import "Environment.h"
#import "FingerprintViewController.h"
#import "FullImageViewController.h"
#import "NSAttributedString+OWS.h"
#import "NewGroupViewController.h"
#import "OWSAudioAttachmentPlayer.h"
#import "OWSCall.h"
@ -77,6 +78,7 @@
#import <SignalServiceKit/OWSIdentityManager.h>
#import <SignalServiceKit/OWSMessageManager.h>
#import <SignalServiceKit/OWSMessageSender.h>
#import <SignalServiceKit/OWSReadReceiptManager.h>
#import <SignalServiceKit/OWSVerificationStateChangeMessage.h>
#import <SignalServiceKit/SignalRecipient.h>
#import <SignalServiceKit/TSAccountManager.h>
@ -2132,6 +2134,17 @@ typedef NS_ENUM(NSInteger, MessagesRangeSizeMode) {
? NSLocalizedString(@"MESSAGE_STATUS_DELIVERED", @"message footer for delivered messages")
: NSLocalizedString(@"MESSAGE_STATUS_SENT", @"message footer for sent messages"));
NSAttributedString *result = [[NSAttributedString alloc] initWithString:text];
if (outgoingMessage.wasDelivered && outgoingMessage.readRecipientIds.count > 0) {
NSAttributedString *checkmark = [[NSAttributedString alloc]
initWithString:@"\uf00c "
attributes:@{
NSFontAttributeName : [UIFont ows_fontAwesomeFont:10.f],
NSForegroundColorAttributeName : [UIColor ows_materialBlueColor],
}];
NSAttributedString *spacing = [[NSAttributedString alloc] initWithString:@" "];
result = [[checkmark rtlSafeAppend:spacing referenceView:self.view] rtlSafeAppend:result
referenceView:self.view];
}
// Show when it's the last message in the thread
if (indexPath.item == [self.collectionView numberOfItemsInSection:indexPath.section] - 1) {
@ -4037,46 +4050,9 @@ typedef NS_ENUM(NSInteger, MessagesRangeSizeMode) {
{
[self updateLastVisibleTimestamp];
TSThread *thread = self.thread;
uint64_t lastVisibleTimestamp = self.lastVisibleTimestamp;
[self.editingDatabaseConnection asyncReadWriteWithBlock:^(YapDatabaseReadWriteTransaction *_Nonnull 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 > lastVisibleTimestamp) {
*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];
}
}];
[OWSReadReceiptManager.sharedManager markAsReadLocallyBeforeTimestamp:lastVisibleTimestamp thread:self.thread];
}
- (void)updateGroupModelTo:(TSGroupModel *)newGroupModel successCompletion:(void (^_Nullable)())successCompletion

@ -3,10 +3,11 @@
//
#import "PrivacySettingsTableViewController.h"
#import "OWSPreferences.h"
#import "BlockListViewController.h"
#import "Environment.h"
#import "OWSPreferences.h"
#import "Signal-Swift.h"
#import <SignalServiceKit/OWSReadReceiptManager.h>
NS_ASSUME_NONNULL_BEGIN
@ -59,11 +60,12 @@ NS_ASSUME_NONNULL_BEGIN
= NSLocalizedString(@"SETTINGS_READ_RECEIPTS_SECTION_TITLE", @"Title of the 'read receipts' settings section.");
readReceiptsSection.footerTitle = NSLocalizedString(
@"SETTINGS_READ_RECEIPTS_SECTION_FOOTER", @"An explanation of the 'read receipts' setting.");
[readReceiptsSection addItem:[OWSTableItem switchItemWithText:NSLocalizedString(@"SETTINGS_READ_RECEIPT",
@"Label for the 'read receipts' setting.")
isOn:[Environment.preferences areReadReceiptsEnabled]
target:weakSelf
selector:@selector(didToggleReadReceiptsSwitch:)]];
[readReceiptsSection
addItem:[OWSTableItem switchItemWithText:NSLocalizedString(@"SETTINGS_READ_RECEIPT",
@"Label for the 'read receipts' setting.")
isOn:[OWSReadReceiptManager.sharedManager areReadReceiptsEnabled]
target:weakSelf
selector:@selector(didToggleReadReceiptsSwitch:)]];
[contents addSection:readReceiptsSection];
// Allow calls to connect directly vs. using TURN exclusively
@ -152,7 +154,7 @@ NS_ASSUME_NONNULL_BEGIN
{
BOOL enabled = sender.isOn;
DDLogInfo(@"%@ toggled areReadReceiptsEnabled: %@", self.tag, enabled ? @"ON" : @"OFF");
[Environment.preferences setAreReadReceiptsEnabled:enabled];
[OWSReadReceiptManager.sharedManager setAreReadReceiptsEnabled:enabled];
}
- (void)didToggleCallsHideIPAddressSwitch:(UISwitch *)sender

@ -52,9 +52,6 @@ extern NSString *const OWSPreferencesKeyEnableDebugLog;
- (void)setIOSUpgradeNagVersion:(NSString *)value;
- (nullable NSString *)iOSUpgradeNagVersion;
- (BOOL)areReadReceiptsEnabled;
- (void)setAreReadReceiptsEnabled:(BOOL)value;
#pragma mark - Calling
#pragma mark Callkit

@ -22,7 +22,6 @@ NSString *const OWSPreferencesKeyCallKitPrivacyEnabled = @"CallKitPrivacyEnabled
NSString *const OWSPreferencesKeyCallsHideIPAddress = @"CallsHideIPAddress";
NSString *const OWSPreferencesKeyHasDeclinedNoContactsView = @"hasDeclinedNoContactsView";
NSString *const OWSPreferencesKeyIOSUpgradeNagVersion = @"iOSUpgradeNagVersion";
NSString *const OWSPreferencesKeyAreReadReceiptsEnabled = @"areReadReceiptsEnabled";
@implementation OWSPreferences
@ -145,18 +144,6 @@ NSString *const OWSPreferencesKeyAreReadReceiptsEnabled = @"areReadReceiptsEnabl
return [self tryGetValueForKey:OWSPreferencesKeyIOSUpgradeNagVersion];
}
- (BOOL)areReadReceiptsEnabled
{
NSNumber *preference = [self tryGetValueForKey:OWSPreferencesKeyAreReadReceiptsEnabled];
// Default to NO.
return preference ? [preference boolValue] : NO;
}
- (void)setAreReadReceiptsEnabled:(BOOL)value
{
[self setValueForKey:OWSPreferencesKeyAreReadReceiptsEnabled toValue:@(value)];
}
#pragma mark - Calling
#pragma mark CallKit

@ -0,0 +1,13 @@
//
// Copyright (c) 2017 Open Whisper Systems. All rights reserved.
//
NS_ASSUME_NONNULL_BEGIN
@interface NSAttributedString (OWS)
- (NSAttributedString *)rtlSafeAppend:(NSAttributedString *)string referenceView:(UIView *)referenceView;
@end
NS_ASSUME_NONNULL_END

@ -0,0 +1,30 @@
//
// Copyright (c) 2017 Open Whisper Systems. All rights reserved.
//
#import "NSAttributedString+OWS.h"
#import "UIView+OWS.h"
NS_ASSUME_NONNULL_BEGIN
@implementation NSAttributedString (OWS)
- (NSAttributedString *)rtlSafeAppend:(NSAttributedString *)string referenceView:(UIView *)referenceView
{
OWSAssert(string);
OWSAssert(referenceView);
NSMutableAttributedString *result = [NSMutableAttributedString new];
if ([referenceView isRTL]) {
[result appendAttributedString:string];
[result appendAttributedString:self];
} else {
[result appendAttributedString:self];
[result appendAttributedString:string];
}
return [result copy];
}
@end
NS_ASSUME_NONNULL_END

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

@ -2,19 +2,19 @@
// Copyright (c) 2017 Open Whisper Systems. All rights reserved.
//
#import "OWSReadReceiptsMessage.h"
#import "OWSReadReceiptsForLinkedDevicesMessage.h"
#import "OWSReadReceipt.h"
#import "OWSSignalServiceProtos.pb.h"
NS_ASSUME_NONNULL_BEGIN
@interface OWSReadReceiptsMessage ()
@interface OWSReadReceiptsForLinkedDevicesMessage ()
@property (nonatomic, readonly) NSArray<OWSReadReceipt *> *readReceipts;
@end
@implementation OWSReadReceiptsMessage
@implementation OWSReadReceiptsForLinkedDevicesMessage
- (instancetype)initWithReadReceipts:(NSArray<OWSReadReceipt *> *)readReceipts
{

@ -0,0 +1,17 @@
//
// Copyright (c) 2017 Open Whisper Systems. All rights reserved.
//
#import "OWSOutgoingSyncMessage.h"
NS_ASSUME_NONNULL_BEGIN
@class OWSReadReceipt;
@interface OWSReadReceiptsForSenderMessage : TSOutgoingMessage
- (instancetype)initWithThread:(nullable TSThread *)thread messageTimestamps:(NSArray<NSNumber *> *)messageTimestamps;
@end
NS_ASSUME_NONNULL_END

@ -0,0 +1,92 @@
//
// Copyright (c) 2017 Open Whisper Systems. All rights reserved.
//
#import "OWSReadReceiptsForSenderMessage.h"
#import "NSDate+OWS.h"
#import "OWSReadReceipt.h"
#import "OWSSignalServiceProtos.pb.h"
#import "SignalRecipient.h"
NS_ASSUME_NONNULL_BEGIN
@interface OWSReadReceiptsForSenderMessage ()
@property (nonatomic, readonly) NSArray<NSNumber *> *messageTimestamps;
@end
@implementation OWSReadReceiptsForSenderMessage
- (instancetype)initWithThread:(nullable TSThread *)thread messageTimestamps:(NSArray<NSNumber *> *)messageTimestamps;
{
self = [super initWithTimestamp:[NSDate ows_millisecondTimeStamp] inThread:thread];
if (!self) {
return self;
}
_messageTimestamps = [messageTimestamps copy];
return self;
}
#pragma mark - TSOutgoingMessage overrides
- (BOOL)shouldSyncTranscript
{
return NO;
}
- (NSData *)buildPlainTextData:(SignalRecipient *)recipient
{
OWSAssert(recipient);
OWSSignalServiceProtosContentBuilder *contentBuilder = [OWSSignalServiceProtosContentBuilder new];
[contentBuilder setReceiptMessage:[self buildReceiptMessage:recipient.recipientId]];
return [[contentBuilder build] data];
}
- (OWSSignalServiceProtosReceiptMessage *)buildReceiptMessage:(NSString *)recipientId
{
OWSSignalServiceProtosReceiptMessageBuilder *builder = [OWSSignalServiceProtosReceiptMessageBuilder new];
[builder setType:OWSSignalServiceProtosReceiptMessageTypeRead];
OWSAssert(self.messageTimestamps.count > 0);
for (NSNumber *messageTimestamp in self.messageTimestamps) {
[builder addTimestamp:[messageTimestamp unsignedLongLongValue]];
}
return [builder build];
}
#pragma mark - TSYapDatabaseObject overrides
- (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.
//
// Should we find a need to save this in the future, we need to exclude any non-serializable properties.
}
- (NSString *)debugDescription
{
return [NSString stringWithFormat:@"%@ with message timestamps: %zd", self.tag, self.messageTimestamps.count];
}
#pragma mark - Logging
+ (NSString *)tag
{
return [NSString stringWithFormat:@"[%@]", self.class];
}
- (NSString *)tag
{
return self.class.tag;
}
@end
NS_ASSUME_NONNULL_END

@ -6,16 +6,23 @@ NS_ASSUME_NONNULL_BEGIN
@class OWSIncomingSentMessageTranscript;
@class OWSMessageSender;
@class TSNetworkManager;
@class OWSReadReceiptManager;
@class TSAttachmentStream;
@class TSNetworkManager;
@class TSStorageManager;
@class YapDatabaseReadWriteTransaction;
// This job is used to process "outgoing message" notifications from linked devices.
@interface OWSRecordTranscriptJob : NSObject
- (instancetype)init NS_UNAVAILABLE;
- (instancetype)initWithIncomingSentMessageTranscript:(OWSIncomingSentMessageTranscript *)incomingSentMessageTranscript;
- (instancetype)initWithIncomingSentMessageTranscript:(OWSIncomingSentMessageTranscript *)incomingSentMessageTranscript
messageSender:(OWSMessageSender *)messageSender
networkManager:(TSNetworkManager *)networkManager NS_DESIGNATED_INITIALIZER;
networkManager:(TSNetworkManager *)networkManager
storageManager:(TSStorageManager *)storageManager
readReceiptManager:(OWSReadReceiptManager *)readReceiptManager
NS_DESIGNATED_INITIALIZER;
- (void)runWithAttachmentHandler:(void (^)(TSAttachmentStream *attachmentStream))attachmentHandler
transaction:(YapDatabaseReadWriteTransaction *)transaction;

@ -6,26 +6,42 @@
#import "OWSAttachmentsProcessor.h"
#import "OWSIncomingSentMessageTranscript.h"
#import "OWSMessageSender.h"
#import "OWSReadReceiptManager.h"
#import "TSInfoMessage.h"
#import "TSNetworkManager.h"
#import "TSOutgoingMessage.h"
#import "TSStorageManager+SessionStore.h"
#import "TextSecureKitEnv.h"
NS_ASSUME_NONNULL_BEGIN
@interface OWSRecordTranscriptJob ()
@property (nonatomic, readonly) OWSIncomingSentMessageTranscript *incomingSentMessageTranscript;
@property (nonatomic, readonly) OWSMessageSender *messageSender;
@property (nonatomic, readonly) TSNetworkManager *networkManager;
@property (nonatomic, readonly) TSStorageManager *storageManager;
@property (nonatomic, readonly) OWSReadReceiptManager *readReceiptManager;
@property (nonatomic, readonly) OWSIncomingSentMessageTranscript *incomingSentMessageTranscript;
@end
@implementation OWSRecordTranscriptJob
- (instancetype)initWithIncomingSentMessageTranscript:(OWSIncomingSentMessageTranscript *)incomingSentMessageTranscript
{
return [self initWithIncomingSentMessageTranscript:incomingSentMessageTranscript
messageSender:[TextSecureKitEnv sharedEnv].messageSender
networkManager:TSNetworkManager.sharedManager
storageManager:TSStorageManager.sharedManager
readReceiptManager:OWSReadReceiptManager.sharedManager];
}
- (instancetype)initWithIncomingSentMessageTranscript:(OWSIncomingSentMessageTranscript *)incomingSentMessageTranscript
messageSender:(OWSMessageSender *)messageSender
networkManager:(TSNetworkManager *)networkManager
storageManager:(TSStorageManager *)storageManager
readReceiptManager:(OWSReadReceiptManager *)readReceiptManager
{
self = [super init];
if (!self) {
@ -35,7 +51,8 @@ NS_ASSUME_NONNULL_BEGIN
_incomingSentMessageTranscript = incomingSentMessageTranscript;
_messageSender = messageSender;
_networkManager = networkManager;
_storageManager = [TSStorageManager sharedManager];
_storageManager = storageManager;
_readReceiptManager = readReceiptManager;
return self;
}
@ -86,10 +103,18 @@ NS_ASSUME_NONNULL_BEGIN
return;
}
if (outgoingMessage.body.length < 1 && outgoingMessage.attachmentIds.count < 1) {
OWSFail(@"Ignoring message transcript for empty message.");
return;
}
// TODO: Refactor this logic. Most of it doesn't belong in `OWSMessageSender`.
[self.messageSender handleMessageSentRemotely:outgoingMessage
sentAt:transcript.expirationStartedAt
transaction:transaction];
[self.readReceiptManager updateOutgoingMessageFromLinkedDevice:outgoingMessage transaction:transaction];
[attachmentsProcessor
fetchAttachmentsForMessage:outgoingMessage
transaction:transaction

@ -26,8 +26,9 @@ NS_ASSUME_NONNULL_BEGIN
#pragma mark Utility Method
+ (instancetype)interactionForTimestamp:(uint64_t)timestamp
withTransaction:(YapDatabaseReadWriteTransaction *)transaction;
+ (NSArray<TSInteraction *> *)interactionsWithTimestamp:(uint64_t)timestamp
ofClass:(Class)clazz
withTransaction:(YapDatabaseReadWriteTransaction *)transaction;
- (NSDate *)dateForSorting;
- (uint64_t)timestampForSorting;

@ -12,29 +12,42 @@ NS_ASSUME_NONNULL_BEGIN
@implementation TSInteraction
+ (instancetype)interactionForTimestamp:(uint64_t)timestamp
withTransaction:(YapDatabaseReadWriteTransaction *)transaction {
+ (NSArray<TSInteraction *> *)interactionsWithTimestamp:(uint64_t)timestamp
ofClass:(Class)clazz
withTransaction:(YapDatabaseReadWriteTransaction *)transaction
{
OWSAssert(timestamp > 0);
// Accept any interaction.
return [self interactionsWithTimestamp:timestamp
filter:^(TSInteraction *interaction) {
return [interaction isKindOfClass:clazz];
}
withTransaction:transaction];
}
+ (NSArray<TSInteraction *> *)interactionsWithTimestamp:(uint64_t)timestamp
filter:(BOOL (^_Nonnull)(TSInteraction *))filter
withTransaction:(YapDatabaseReadWriteTransaction *)transaction
{
OWSAssert(timestamp > 0);
__block int counter = 0;
__block TSInteraction *interaction;
NSMutableArray<TSInteraction *> *interactions = [NSMutableArray new];
[TSDatabaseSecondaryIndexes
enumerateMessagesWithTimestamp:timestamp
withBlock:^(NSString *collection, NSString *key, BOOL *stop) {
if (counter != 0) {
DDLogWarn(@"The database contains two colliding timestamps at: %lld.", timestamp);
TSInteraction *interaction =
[TSInteraction fetchObjectWithUniqueID:key transaction:transaction];
if (!filter(interaction)) {
return;
}
interaction = [TSInteraction fetchObjectWithUniqueID:key transaction:transaction];
counter++;
[interactions addObject:interaction];
}
usingTransaction:transaction];
return interaction;
return [interactions copy];
}
+ (NSString *)collection {

@ -31,8 +31,8 @@ typedef NS_ENUM(NSInteger, TSGroupMetaMessage) {
};
@class OWSSignalServiceProtosAttachmentPointer;
@class OWSSignalServiceProtosDataMessageBuilder;
@class OWSSignalServiceProtosContentBuilder;
@class OWSSignalServiceProtosDataMessageBuilder;
@class SignalRecipient;
@interface TSOutgoingMessage : TSMessage
@ -104,6 +104,9 @@ typedef NS_ENUM(NSInteger, TSGroupMetaMessage) {
// This property won't be accurate for legacy messages.
@property (atomic, readonly) BOOL isFromLinkedDevice;
// The recipient ids of the recipients who have read the message.
@property (atomic, readonly) NSSet<NSString *> *readRecipientIds;
/**
* Signal Identifier (e.g. e164 number) or nil if in a group thread.
*/
@ -173,6 +176,7 @@ typedef NS_ENUM(NSInteger, TSGroupMetaMessage) {
- (void)updateWithWasSentFromLinkedDeviceWithTransaction:(YapDatabaseReadWriteTransaction *)transaction;
- (void)updateWithSingleGroupRecipient:(NSString *)singleGroupRecipient
transaction:(YapDatabaseReadWriteTransaction *)transaction;
- (void)updateWithReadRecipientId:(NSString *)recipientId transaction:(YapDatabaseReadWriteTransaction *)transaction;
#pragma mark - Sent Recipients

@ -39,6 +39,8 @@ NSString *const kTSOutgoingMessageSentRecipientAll = @"kTSOutgoingMessageSentRec
@property (atomic) TSGroupMetaMessage groupMetaMessage;
@property (atomic) NSSet<NSString *> *readRecipientIds;
@end
#pragma mark -
@ -409,6 +411,21 @@ NSString *const kTSOutgoingMessageSentRecipientAll = @"kTSOutgoingMessageSentRec
}];
}
- (void)updateWithReadRecipientId:(NSString *)recipientId transaction:(YapDatabaseReadWriteTransaction *)transaction
{
OWSAssert(recipientId.length > 0);
OWSAssert(transaction);
[self applyChangeToSelfAndLatestOutgoingMessage:transaction
changeBlock:^(TSOutgoingMessage *message) {
NSMutableSet<NSString *> *readRecipientIds
= (message.readRecipientIds ? [message.readRecipientIds mutableCopy]
: [NSMutableSet new]);
[readRecipientIds addObject:recipientId];
message.readRecipientIds = readRecipientIds;
}];
}
#pragma mark -
- (OWSSignalServiceProtosDataMessageBuilder *)dataMessageBuilder

@ -75,6 +75,8 @@ NSString *envelopeAddress(OWSSignalServiceProtosEnvelope *envelope)
return [NSString stringWithFormat:@"<CallMessage: %@ />", content.callMessage];
} else if (content.hasNullMessage) {
return [NSString stringWithFormat:@"<NullMessage: %@ />", content.nullMessage];
} else if (content.hasReceiptMessage) {
return [NSString stringWithFormat:@"<ReceiptMessage: %@ />", content.receiptMessage];
} else {
// Don't fire an analytics event; if we ever add a new content type, we'd generate a ton of
// analytics traffic.

@ -18,6 +18,7 @@
#import "OWSIncomingMessageFinder.h"
#import "OWSIncomingSentMessageTranscript.h"
#import "OWSMessageSender.h"
#import "OWSReadReceiptManager.h"
#import "OWSReadReceiptsProcessor.h"
#import "OWSRecordTranscriptJob.h"
#import "OWSSyncContactsMessage.h"
@ -186,14 +187,24 @@ NS_ASSUME_NONNULL_BEGIN
OWSAssert(envelope);
OWSAssert(transaction);
TSInteraction *interaction = [TSInteraction interactionForTimestamp:envelope.timestamp withTransaction:transaction];
if ([interaction isKindOfClass:[TSOutgoingMessage class]]) {
TSOutgoingMessage *outgoingMessage = (TSOutgoingMessage *)interaction;
[outgoingMessage updateWithWasDeliveredWithTransaction:transaction];
} else {
NSArray<TSOutgoingMessage *> *messages
= (NSArray<TSOutgoingMessage *> *)[TSInteraction interactionsWithTimestamp:envelope.timestamp
ofClass:[TSOutgoingMessage class]
withTransaction:transaction];
if (messages.count < 1) {
// Desktop currently sends delivery receipts for "unpersisted" messages
// like group updates, so these errors are expected to a certain extent.
DDLogInfo(@"%@ Unexpected message with timestamp: %llu", self.tag, envelope.timestamp);
DDLogInfo(@"%@ Missing message for delivery receipt: %llu", self.tag, envelope.timestamp);
} else {
if (messages.count > 1) {
DDLogInfo(@"%@ More than one message (%zd) for delivery receipt: %llu",
self.tag,
messages.count,
envelope.timestamp);
}
for (TSOutgoingMessage *outgoingMessage in messages) {
[outgoingMessage updateWithWasDeliveredWithTransaction:transaction];
}
}
}
@ -232,6 +243,8 @@ NS_ASSUME_NONNULL_BEGIN
[self handleIncomingEnvelope:envelope withCallMessage:content.callMessage];
} else if (content.hasNullMessage) {
DDLogInfo(@"%@ Received null message.", self.tag);
} else if (content.hasReceiptMessage) {
[self handleIncomingEnvelope:envelope withReceiptMessage:content.receiptMessage];
} else {
DDLogWarn(@"%@ Ignoring envelope. Content with no known payload", self.tag);
}
@ -326,6 +339,26 @@ NS_ASSUME_NONNULL_BEGIN
return [TextSecureKitEnv sharedEnv].profileManager;
}
- (void)handleIncomingEnvelope:(OWSSignalServiceProtosEnvelope *)envelope
withReceiptMessage:(OWSSignalServiceProtosReceiptMessage *)receiptMessage
{
OWSAssert(envelope);
OWSAssert(receiptMessage);
switch (receiptMessage.type) {
case OWSSignalServiceProtosReceiptMessageTypeDelivery:
DDLogInfo(@"%@ Ignoring receipt message with delivery receipt.", self.tag);
return;
case OWSSignalServiceProtosReceiptMessageTypeRead:
DDLogVerbose(@"%@ Processing receipt message with read receipts.", self.tag);
[OWSReadReceiptManager.sharedManager processReadReceiptsFromRecipient:receiptMessage envelope:envelope];
break;
default:
DDLogInfo(@"%@ Ignoring receipt message of unknown type: %d.", self.tag, (int)receiptMessage.type);
return;
}
}
- (void)handleIncomingEnvelope:(OWSSignalServiceProtosEnvelope *)envelope
withCallMessage:(OWSSignalServiceProtosCallMessage *)callMessage
{
@ -466,9 +499,7 @@ NS_ASSUME_NONNULL_BEGIN
[[OWSIncomingSentMessageTranscript alloc] initWithProto:syncMessage.sent relay:envelope.relay];
OWSRecordTranscriptJob *recordJob =
[[OWSRecordTranscriptJob alloc] initWithIncomingSentMessageTranscript:transcript
messageSender:self.messageSender
networkManager:self.networkManager];
[[OWSRecordTranscriptJob alloc] initWithIncomingSentMessageTranscript:transcript];
OWSSignalServiceProtosDataMessage *dataMessage = syncMessage.sent.message;
OWSAssert(dataMessage);

@ -32,7 +32,6 @@ NS_ASSUME_NONNULL_BEGIN
return self;
}
- (instancetype)initWithThread:(TSThread *)thread offerMessage:(OWSCallOfferMessage *)offerMessage
{
self = [self initWithThread:thread];

@ -4,7 +4,12 @@
NS_ASSUME_NONNULL_BEGIN
@class OWSSignalServiceProtosEnvelope;
@class OWSSignalServiceProtosReceiptMessage;
@class TSIncomingMessage;
@class TSOutgoingMessage;
@class TSThread;
@class YapDatabaseReadWriteTransaction;
// There are four kinds of read receipts:
//
@ -27,17 +32,34 @@ NS_ASSUME_NONNULL_BEGIN
- (instancetype)init NS_UNAVAILABLE;
+ (instancetype)sharedManager;
// This method can be called from any thread.
// This method should be called when we receive a read receipt
// from a user to whom we have sent a message.
//
// It cues this manager:
// This method can be called from any thread.
- (void)processReadReceiptsFromRecipient:(OWSSignalServiceProtosReceiptMessage *)receiptMessage
envelope:(OWSSignalServiceProtosEnvelope *)envelope;
- (void)updateOutgoingMessageFromLinkedDevice:(TSOutgoingMessage *)message
transaction:(YapDatabaseReadWriteTransaction *)transaction;
// This method cues this manager:
//
// * ...to inform the sender that this message was read (if read receipts
// are enabled).
// * ...to inform the local user's other devices that this message was read.
//
// Both types of messages are deduplicated.
//
// This method can be called from any thread.
- (void)messageWasReadLocally:(TSIncomingMessage *)message;
- (void)markAsReadLocallyBeforeTimestamp:(uint64_t)timestamp thread:(TSThread *)thread;
#pragma mark - Settings
- (BOOL)areReadReceiptsEnabled;
- (void)setAreReadReceiptsEnabled:(BOOL)value;
@end
NS_ASSUME_NONNULL_END

@ -5,29 +5,118 @@
#import "OWSReadReceiptManager.h"
#import "OWSMessageSender.h"
#import "OWSReadReceipt.h"
#import "OWSReadReceiptsMessage.h"
#import "OWSReadReceiptsForLinkedDevicesMessage.h"
#import "OWSReadReceiptsForSenderMessage.h"
#import "OWSSignalServiceProtos.pb.h"
#import "TSContactThread.h"
#import "TSDatabaseView.h"
#import "TSIncomingMessage.h"
#import "TSStorageManager.h"
#import "TextSecureKitEnv.h"
#import "Threading.h"
NS_ASSUME_NONNULL_BEGIN
@interface TSRecipientReadReceipt : TSYapDatabaseObject
@property (nonatomic, readonly) uint64_t timestamp;
@property (nonatomic, readonly) NSSet<NSString *> *recipientIds;
@end
#pragma mark -
@implementation TSRecipientReadReceipt
- (instancetype)initWithTimestamp:(uint64_t)timestamp
{
OWSAssert(timestamp > 0);
self = [super initWithUniqueId:[TSRecipientReadReceipt uniqueIdForTimestamp:timestamp]];
if (self) {
_timestamp = timestamp;
_recipientIds = [NSSet set];
}
return self;
}
+ (NSString *)uniqueIdForTimestamp:(uint64_t)timestamp
{
return [NSString stringWithFormat:@"%llu", timestamp];
}
- (void)addRecipientId:(NSString *)recipientId
{
NSMutableSet<NSString *> *recipientIdsCopy = [self.recipientIds mutableCopy];
[recipientIdsCopy addObject:recipientId];
_recipientIds = [recipientIdsCopy copy];
}
+ (void)addRecipientId:(NSString *)recipientId
timestamp:(uint64_t)timestamp
transaction:(YapDatabaseReadWriteTransaction *)transaction
{
OWSAssert(transaction);
TSRecipientReadReceipt *_Nullable recipientReadReceipt =
[transaction objectForKey:[self uniqueIdForTimestamp:timestamp] inCollection:[self collection]];
if (!recipientReadReceipt) {
recipientReadReceipt = [[TSRecipientReadReceipt alloc] initWithTimestamp:timestamp];
}
[recipientReadReceipt addRecipientId:recipientId];
[recipientReadReceipt saveWithTransaction:transaction];
}
+ (nullable NSSet<NSString *> *)recipientIdsForTimestamp:(uint64_t)timestamp
transaction:(YapDatabaseReadWriteTransaction *)transaction
{
OWSAssert(transaction);
TSRecipientReadReceipt *_Nullable recipientReadReceipt =
[transaction objectForKey:[self uniqueIdForTimestamp:timestamp] inCollection:[self collection]];
return recipientReadReceipt.recipientIds;
}
+ (void)removeRecipientIdsForTimestamp:(uint64_t)timestamp transaction:(YapDatabaseReadWriteTransaction *)transaction
{
OWSAssert(transaction);
[transaction removeObjectForKey:[self uniqueIdForTimestamp:timestamp] inCollection:[self collection]];
}
@end
#pragma mark -
NSString *const OWSReadReceiptManagerCollection = @"OWSReadReceiptManagerCollection";
NSString *const OWSReadReceiptManagerAreReadReceiptsEnabled = @"areReadReceiptsEnabled";
@interface OWSReadReceiptManager ()
@property (nonatomic, readonly) TSStorageManager *storageManager;
@property (nonatomic, readonly) OWSMessageSender *messageSender;
@property (nonatomic, readonly) YapDatabaseConnection *dbConnection;
// A map of "thread unique id"-to-"read receipt" for read receipts that
// we will send to our linked devices.
//
// Should only be accessed while synchronized on the OWSReadReceiptManager.
@property (nonatomic, readonly) NSMutableDictionary<NSString *, OWSReadReceipt *> *toLinkedDevicesReadReceiptMap;
// A map of "recipient id"-to-"timestamp list" for read receipts that
// we will send to senders.
//
// Should only be accessed while synchronized on the OWSReadReceiptManager.
@property (nonatomic, readonly) NSMutableDictionary<NSString *, NSMutableSet<NSNumber *> *> *toSenderReadReceiptMap;
// Should only be accessed while synchronized on the OWSReadReceiptManager.
@property (nonatomic) BOOL isProcessing;
// Should only be accessed while synchronized on the OWSReadReceiptManager.
@property (nonatomic) NSNumber *areReadReceiptsEnabledCached;
@end
#pragma mark -
@ -47,11 +136,13 @@ NS_ASSUME_NONNULL_BEGIN
- (instancetype)initDefault
{
OWSMessageSender *messageSender = [TextSecureKitEnv sharedEnv].messageSender;
TSStorageManager *storageManager = [TSStorageManager sharedManager];
return [self initWithMessageSender:messageSender];
return [self initWithMessageSender:messageSender storageManager:storageManager];
}
- (instancetype)initWithMessageSender:(OWSMessageSender *)messageSender
storageManager:(TSStorageManager *)storageManager
{
self = [super init];
@ -60,8 +151,10 @@ NS_ASSUME_NONNULL_BEGIN
}
_messageSender = messageSender;
_dbConnection = storageManager.newDatabaseConnection;
_toLinkedDevicesReadReceiptMap = [NSMutableDictionary new];
_toSenderReadReceiptMap = [NSMutableDictionary new];
OWSSingletonAssert();
@ -95,6 +188,8 @@ NS_ASSUME_NONNULL_BEGIN
@synchronized(self)
{
if ([TSDatabaseView hasPendingViewRegistrations]) {
DDLogInfo(
@"%@ Deferring read receipt processing due to pending database view registrations.", self.tag);
return;
}
if (self.isProcessing) {
@ -122,51 +217,239 @@ NS_ASSUME_NONNULL_BEGIN
{
@synchronized(self)
{
DDLogVerbose(@"%@ Processing read receipts.", self.tag);
self.isProcessing = NO;
NSArray<OWSReadReceipt *> *readReceiptsToSend = [self.toLinkedDevicesReadReceiptMap allValues];
NSArray<OWSReadReceipt *> *readReceiptsForLinkedDevices = [self.toLinkedDevicesReadReceiptMap allValues];
[self.toLinkedDevicesReadReceiptMap removeAllObjects];
if (readReceiptsToSend.count > 0) {
OWSReadReceiptsMessage *message = [[OWSReadReceiptsMessage alloc] initWithReadReceipts:readReceiptsToSend];
if (readReceiptsForLinkedDevices.count > 0) {
OWSReadReceiptsForLinkedDevicesMessage *message =
[[OWSReadReceiptsForLinkedDevicesMessage alloc] initWithReadReceipts:readReceiptsForLinkedDevices];
dispatch_async(dispatch_get_main_queue(), ^{
[self.messageSender sendMessage:message
success:^{
DDLogInfo(@"%@ Successfully sent %zd read receipt to linked devices.",
self.tag,
readReceiptsToSend.count);
readReceiptsForLinkedDevices.count);
}
failure:^(NSError *error) {
DDLogError(@"%@ Failed to send read receipt to linked devices with error: %@", self.tag, error);
}];
});
}
NSArray<OWSReadReceipt *> *readReceiptsToSend = [self.toLinkedDevicesReadReceiptMap allValues];
[self.toLinkedDevicesReadReceiptMap removeAllObjects];
if (self.toSenderReadReceiptMap.count > 0) {
for (NSString *recipientId in self.toSenderReadReceiptMap) {
NSSet<NSNumber *> *timestamps = self.toSenderReadReceiptMap[recipientId];
OWSAssert(timestamps.count > 0);
TSThread *thread = [TSContactThread getOrCreateThreadWithContactId:recipientId];
OWSReadReceiptsForSenderMessage *message =
[[OWSReadReceiptsForSenderMessage alloc] initWithThread:thread
messageTimestamps:timestamps.allObjects];
dispatch_async(dispatch_get_main_queue(), ^{
[self.messageSender sendMessage:message
success:^{
DDLogInfo(@"%@ Successfully sent %zd read receipts to sender.",
self.tag,
readReceiptsToSend.count);
}
failure:^(NSError *error) {
DDLogError(@"%@ Failed to send read receipts to sender with error: %@", self.tag, error);
}];
});
}
[self.toSenderReadReceiptMap removeAllObjects];
}
}
}
- (void)messageWasReadLocally:(TSIncomingMessage *)message;
#pragma mark - Mark as Read Locally
- (void)markAsReadLocallyBeforeTimestamp:(uint64_t)timestamp thread:(TSThread *)thread
{
@synchronized(self)
{
NSString *threadUniqueId = message.uniqueThreadId;
OWSAssert(threadUniqueId.length > 0);
OWSAssert(thread);
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];
}
}];
});
}
NSString *messageAuthorId = message.messageAuthorId;
OWSAssert(messageAuthorId.length > 0);
- (void)messageWasReadLocally:(TSIncomingMessage *)message
{
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
@synchronized(self)
{
NSString *threadUniqueId = message.uniqueThreadId;
OWSAssert(threadUniqueId.length > 0);
NSString *messageAuthorId = message.messageAuthorId;
OWSAssert(messageAuthorId.length > 0);
OWSReadReceipt *newReadReceipt =
[[OWSReadReceipt alloc] initWithSenderId:messageAuthorId timestamp:message.timestamp];
OWSReadReceipt *_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.
DDLogVerbose(@"%@ Ignoring redundant read receipt for linked devices.", self.tag);
} else {
DDLogVerbose(@"%@ Enqueuing read receipt for linked devices.", self.tag);
self.toLinkedDevicesReadReceiptMap[threadUniqueId] = newReadReceipt;
}
OWSReadReceipt *newReadReceipt =
[[OWSReadReceipt alloc] initWithSenderId:messageAuthorId timestamp:message.timestamp];
if ([self areReadReceiptsEnabled]) {
DDLogVerbose(@"%@ Enqueuing read receipt for sender.", self.tag);
NSMutableSet<NSNumber *> *_Nullable timestamps = self.toSenderReadReceiptMap[messageAuthorId];
if (!timestamps) {
timestamps = [NSMutableSet new];
self.toSenderReadReceiptMap[messageAuthorId] = timestamps;
}
[timestamps addObject:@(message.timestamp)];
}
OWSReadReceipt *_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.
return;
[self scheduleProcessing];
}
});
}
self.toLinkedDevicesReadReceiptMap[threadUniqueId] = newReadReceipt;
#pragma mark - Read Receipts From Recipient
[self scheduleProcessing];
- (void)processReadReceiptsFromRecipient:(OWSSignalServiceProtosReceiptMessage *)receiptMessage
envelope:(OWSSignalServiceProtosEnvelope *)envelope
{
OWSAssert(receiptMessage);
OWSAssert(envelope);
OWSAssert(receiptMessage.type == OWSSignalServiceProtosReceiptMessageTypeRead);
if (![self areReadReceiptsEnabled]) {
DDLogInfo(@"%@ Ignoring incoming receipt message as read receipts are disabled.", self.tag);
return;
}
NSString *recipientId = envelope.source;
OWSAssert(recipientId.length > 0);
PBArray *timestamps = receiptMessage.timestamp;
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
[self.dbConnection readWriteWithBlock:^(YapDatabaseReadWriteTransaction *transaction) {
for (int i = 0; i < timestamps.count; i++) {
UInt64 timestamp = [timestamps uint64AtIndex:i];
NSArray<TSOutgoingMessage *> *messages
= (NSArray<TSOutgoingMessage *> *)[TSInteraction interactionsWithTimestamp:timestamp
ofClass:[TSOutgoingMessage class]
withTransaction:transaction];
OWSAssert(messages.count <= 1);
if (messages.count > 0) {
// TODO: We might also need to "mark as read by recipient" any older messages
// from us in that thread. Or maybe this state should hang on the thread?
for (TSOutgoingMessage *message in messages) {
[message updateWithReadRecipientId:recipientId transaction:transaction];
}
} else {
// Persist the read receipts so that we can apply them to outgoing messages
// that we learn about later through sync messages.
[TSRecipientReadReceipt addRecipientId:recipientId timestamp:timestamp transaction:transaction];
}
}
}];
});
}
- (void)updateOutgoingMessageFromLinkedDevice:(TSOutgoingMessage *)message
transaction:(YapDatabaseReadWriteTransaction *)transaction
{
OWSAssert(message);
OWSAssert(transaction);
NSSet<NSString *> *_Nullable recipientIds =
[TSRecipientReadReceipt recipientIdsForTimestamp:message.timestamp transaction:transaction];
if (!recipientIds) {
return;
}
OWSAssert(recipientIds.count > 0);
for (NSString *recipientId in recipientIds) {
[message updateWithReadRecipientId:recipientId transaction:transaction];
}
[TSRecipientReadReceipt removeRecipientIdsForTimestamp:message.timestamp transaction:transaction];
}
#pragma mark - Settings
- (BOOL)areReadReceiptsEnabled
{
@synchronized(self)
{
if (!self.areReadReceiptsEnabledCached) {
// Default to NO.
self.areReadReceiptsEnabledCached =
@([self.dbConnection boolForKey:OWSReadReceiptManagerAreReadReceiptsEnabled
inCollection:OWSReadReceiptManagerCollection]);
}
return [self.areReadReceiptsEnabledCached boolValue];
}
}
- (void)setAreReadReceiptsEnabled:(BOOL)value
{
DDLogInfo(@"%@ areReadReceiptsEnabled: %d.", self.tag, value);
@synchronized(self)
{
[self.dbConnection setBool:value
forKey:OWSReadReceiptManagerAreReadReceiptsEnabled
inCollection:OWSReadReceiptManagerCollection];
self.areReadReceiptsEnabledCached = @(value);
}
}

Loading…
Cancel
Save