Merge branch 'charlesmchen/readReceiptsManager'

pull/1/head
Matthew Chen 8 years ago
commit 1382270c66

@ -203,8 +203,6 @@
45BB93381E688E14001E3939 /* UIDevice+featureSupport.swift in Sources */ = {isa = PBXBuildFile; fileRef = 45BB93371E688E14001E3939 /* UIDevice+featureSupport.swift */; }; 45BB93381E688E14001E3939 /* UIDevice+featureSupport.swift in Sources */ = {isa = PBXBuildFile; fileRef = 45BB93371E688E14001E3939 /* UIDevice+featureSupport.swift */; };
45BB93391E688E14001E3939 /* UIDevice+featureSupport.swift in Sources */ = {isa = PBXBuildFile; fileRef = 45BB93371E688E14001E3939 /* UIDevice+featureSupport.swift */; }; 45BB93391E688E14001E3939 /* UIDevice+featureSupport.swift in Sources */ = {isa = PBXBuildFile; fileRef = 45BB93371E688E14001E3939 /* UIDevice+featureSupport.swift */; };
45BD60821DE9547E00A8F436 /* Contacts.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 45BD60811DE9547E00A8F436 /* Contacts.framework */; settings = {ATTRIBUTES = (Weak, ); }; }; 45BD60821DE9547E00A8F436 /* Contacts.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 45BD60811DE9547E00A8F436 /* Contacts.framework */; settings = {ATTRIBUTES = (Weak, ); }; };
45BFFFA81D898AF0004A12A7 /* OWSStaleNotificationObserver.m in Sources */ = {isa = PBXBuildFile; fileRef = 45BFFFA71D898AF0004A12A7 /* OWSStaleNotificationObserver.m */; };
45BFFFA91D898AF0004A12A7 /* OWSStaleNotificationObserver.m in Sources */ = {isa = PBXBuildFile; fileRef = 45BFFFA71D898AF0004A12A7 /* OWSStaleNotificationObserver.m */; };
45C0DC1B1E68FE9000E04C47 /* UIApplication+OWS.swift in Sources */ = {isa = PBXBuildFile; fileRef = 45C0DC1A1E68FE9000E04C47 /* UIApplication+OWS.swift */; }; 45C0DC1B1E68FE9000E04C47 /* UIApplication+OWS.swift in Sources */ = {isa = PBXBuildFile; fileRef = 45C0DC1A1E68FE9000E04C47 /* UIApplication+OWS.swift */; };
45C0DC1C1E68FE9000E04C47 /* UIApplication+OWS.swift in Sources */ = {isa = PBXBuildFile; fileRef = 45C0DC1A1E68FE9000E04C47 /* UIApplication+OWS.swift */; }; 45C0DC1C1E68FE9000E04C47 /* UIApplication+OWS.swift in Sources */ = {isa = PBXBuildFile; fileRef = 45C0DC1A1E68FE9000E04C47 /* UIApplication+OWS.swift */; };
45C0DC1E1E69011F00E04C47 /* UIStoryboard+OWS.swift in Sources */ = {isa = PBXBuildFile; fileRef = 45C0DC1D1E69011F00E04C47 /* UIStoryboard+OWS.swift */; }; 45C0DC1E1E69011F00E04C47 /* UIStoryboard+OWS.swift in Sources */ = {isa = PBXBuildFile; fileRef = 45C0DC1D1E69011F00E04C47 /* UIStoryboard+OWS.swift */; };
@ -668,8 +666,6 @@
45B201741DAECBFD00C461E0 /* Signal-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "Signal-Bridging-Header.h"; sourceTree = "<group>"; }; 45B201741DAECBFD00C461E0 /* Signal-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "Signal-Bridging-Header.h"; sourceTree = "<group>"; };
45BB93371E688E14001E3939 /* UIDevice+featureSupport.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "UIDevice+featureSupport.swift"; sourceTree = "<group>"; }; 45BB93371E688E14001E3939 /* UIDevice+featureSupport.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "UIDevice+featureSupport.swift"; sourceTree = "<group>"; };
45BD60811DE9547E00A8F436 /* Contacts.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Contacts.framework; path = System/Library/Frameworks/Contacts.framework; sourceTree = SDKROOT; }; 45BD60811DE9547E00A8F436 /* Contacts.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Contacts.framework; path = System/Library/Frameworks/Contacts.framework; sourceTree = SDKROOT; };
45BFFFA61D898AF0004A12A7 /* OWSStaleNotificationObserver.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = OWSStaleNotificationObserver.h; path = Observers/OWSStaleNotificationObserver.h; sourceTree = "<group>"; };
45BFFFA71D898AF0004A12A7 /* OWSStaleNotificationObserver.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = OWSStaleNotificationObserver.m; path = Observers/OWSStaleNotificationObserver.m; sourceTree = "<group>"; };
45C0DC1A1E68FE9000E04C47 /* UIApplication+OWS.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "UIApplication+OWS.swift"; sourceTree = "<group>"; }; 45C0DC1A1E68FE9000E04C47 /* UIApplication+OWS.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "UIApplication+OWS.swift"; sourceTree = "<group>"; };
45C0DC1D1E69011F00E04C47 /* UIStoryboard+OWS.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "UIStoryboard+OWS.swift"; sourceTree = "<group>"; }; 45C0DC1D1E69011F00E04C47 /* UIStoryboard+OWS.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "UIStoryboard+OWS.swift"; sourceTree = "<group>"; };
45C681B51D305A580050903A /* OWSCall.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = OWSCall.h; sourceTree = "<group>"; }; 45C681B51D305A580050903A /* OWSCall.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = OWSCall.h; sourceTree = "<group>"; };
@ -1260,15 +1256,6 @@
name = Models; name = Models;
sourceTree = "<group>"; sourceTree = "<group>";
}; };
45BFFFA51D898AB8004A12A7 /* Observers */ = {
isa = PBXGroup;
children = (
45BFFFA61D898AF0004A12A7 /* OWSStaleNotificationObserver.h */,
45BFFFA71D898AF0004A12A7 /* OWSStaleNotificationObserver.m */,
);
name = Observers;
sourceTree = "<group>";
};
45CD81A41DBFF8CF004C9430 /* Storyboards */ = { 45CD81A41DBFF8CF004C9430 /* Storyboards */ = {
isa = PBXGroup; isa = PBXGroup;
children = ( children = (
@ -1339,7 +1326,6 @@
45D231751DC7E8C50034FA89 /* Jobs */, 45D231751DC7E8C50034FA89 /* Jobs */,
457F3AC01D14A0F700C51351 /* Models */, 457F3AC01D14A0F700C51351 /* Models */,
76EB041D18170B33006006FC /* network */, 76EB041D18170B33006006FC /* network */,
45BFFFA51D898AB8004A12A7 /* Observers */,
34CE88E81F3237260098030F /* Profiles */, 34CE88E81F3237260098030F /* Profiles */,
B60959791C2C0FA9004E8797 /* rating */, B60959791C2C0FA9004E8797 /* rating */,
45B201741DAECBFD00C461E0 /* Signal-Bridging-Header.h */, 45B201741DAECBFD00C461E0 /* Signal-Bridging-Header.h */,
@ -2260,7 +2246,6 @@
76EB064218170B33006006FC /* StringUtil.m in Sources */, 76EB064218170B33006006FC /* StringUtil.m in Sources */,
452037D11EE84975004E4CDF /* DebugUISessionState.m in Sources */, 452037D11EE84975004E4CDF /* DebugUISessionState.m in Sources */,
342FCE6B1EF9C375002690AD /* OWS105AttachmentFilePaths.m in Sources */, 342FCE6B1EF9C375002690AD /* OWS105AttachmentFilePaths.m in Sources */,
45BFFFA81D898AF0004A12A7 /* OWSStaleNotificationObserver.m in Sources */,
45C681B71D305A580050903A /* OWSCall.m in Sources */, 45C681B71D305A580050903A /* OWSCall.m in Sources */,
D221A09A169C9E5E00537ABF /* main.m in Sources */, D221A09A169C9E5E00537ABF /* main.m in Sources */,
345671011E89A5F1006EE662 /* ThreadUtil.m in Sources */, 345671011E89A5F1006EE662 /* ThreadUtil.m in Sources */,
@ -2423,7 +2408,6 @@
456AC8351E3A776300A3C7FC /* WeakTimer.swift in Sources */, 456AC8351E3A776300A3C7FC /* WeakTimer.swift in Sources */,
458E383A1D6699FA0094BD24 /* OWSDeviceProvisioningURLParserTest.m in Sources */, 458E383A1D6699FA0094BD24 /* OWSDeviceProvisioningURLParserTest.m in Sources */,
452D1EE81DCA90D100A57EC4 /* MesssagesBubblesSizeCalculatorTest.swift in Sources */, 452D1EE81DCA90D100A57EC4 /* MesssagesBubblesSizeCalculatorTest.swift in Sources */,
45BFFFA91D898AF0004A12A7 /* OWSStaleNotificationObserver.m in Sources */,
451DE9FE1DC1A28200810E42 /* SyncPushTokensJob.swift in Sources */, 451DE9FE1DC1A28200810E42 /* SyncPushTokensJob.swift in Sources */,
45F170AF1E2F0393003FC1F2 /* CallAudioSessionTest.swift in Sources */, 45F170AF1E2F0393003FC1F2 /* CallAudioSessionTest.swift in Sources */,
34B3F8991E8DF1B90035BE1A /* TSMessageAdapterTest.m in Sources */, 34B3F8991E8DF1B90035BE1A /* TSMessageAdapterTest.m in Sources */,

@ -14,7 +14,6 @@
#import "OWSNavigationController.h" #import "OWSNavigationController.h"
#import "OWSPreferences.h" #import "OWSPreferences.h"
#import "OWSProfileManager.h" #import "OWSProfileManager.h"
#import "OWSStaleNotificationObserver.h"
#import "Pastelog.h" #import "Pastelog.h"
#import "PushManager.h" #import "PushManager.h"
#import "RegistrationViewController.h" #import "RegistrationViewController.h"
@ -29,9 +28,9 @@
#import <SignalServiceKit/OWSDisappearingMessagesJob.h> #import <SignalServiceKit/OWSDisappearingMessagesJob.h>
#import <SignalServiceKit/OWSFailedAttachmentDownloadsJob.h> #import <SignalServiceKit/OWSFailedAttachmentDownloadsJob.h>
#import <SignalServiceKit/OWSFailedMessagesJob.h> #import <SignalServiceKit/OWSFailedMessagesJob.h>
#import <SignalServiceKit/OWSIncomingMessageReadObserver.h>
#import <SignalServiceKit/OWSMessageManager.h> #import <SignalServiceKit/OWSMessageManager.h>
#import <SignalServiceKit/OWSMessageSender.h> #import <SignalServiceKit/OWSMessageSender.h>
#import <SignalServiceKit/OWSReadReceiptManager.h>
#import <SignalServiceKit/TSAccountManager.h> #import <SignalServiceKit/TSAccountManager.h>
#import <SignalServiceKit/TSDatabaseView.h> #import <SignalServiceKit/TSDatabaseView.h>
#import <SignalServiceKit/TSPreKeyManager.h> #import <SignalServiceKit/TSPreKeyManager.h>
@ -51,8 +50,6 @@ static NSString *const kURLHostVerifyPrefix = @"verify";
@interface AppDelegate () @interface AppDelegate ()
@property (nonatomic) UIWindow *screenProtectionWindow; @property (nonatomic) UIWindow *screenProtectionWindow;
@property (nonatomic) OWSIncomingMessageReadObserver *incomingMessageReadObserver;
@property (nonatomic) OWSStaleNotificationObserver *staleNotificationObserver;
@property (nonatomic) OWSContactsSyncing *contactsSyncing; @property (nonatomic) OWSContactsSyncing *contactsSyncing;
@property (nonatomic) BOOL hasInitialRootViewController; @property (nonatomic) BOOL hasInitialRootViewController;
@ -270,13 +267,6 @@ static NSString *const kURLHostVerifyPrefix = @"verify";
[VersionMigrations runSafeBlockingMigrations]; [VersionMigrations runSafeBlockingMigrations];
}]; }];
[[Environment getCurrent].contactsManager startObserving]; [[Environment getCurrent].contactsManager startObserving];
self.incomingMessageReadObserver =
[[OWSIncomingMessageReadObserver alloc] initWithMessageSender:[Environment getCurrent].messageSender];
[self.incomingMessageReadObserver startObserving];
self.staleNotificationObserver = [OWSStaleNotificationObserver new];
[self.staleNotificationObserver startObserving];
} }
- (void)application:(UIApplication *)application didRegisterForRemoteNotificationsWithDeviceToken:(NSData *)deviceToken - (void)application:(UIApplication *)application didRegisterForRemoteNotificationsWithDeviceToken:(NSData *)deviceToken
@ -820,6 +810,8 @@ static NSString *const kURLHostVerifyPrefix = @"verify";
[[OWSBatchMessageProcessor sharedInstance] handleAnyUnprocessedEnvelopesAsync]; [[OWSBatchMessageProcessor sharedInstance] handleAnyUnprocessedEnvelopesAsync];
[OWSProfileManager.sharedManager fetchLocalUsersProfile]; [OWSProfileManager.sharedManager fetchLocalUsersProfile];
// Make sure this manager is started.
[OWSReadReceiptManager sharedManager];
} }
- (void)registrationStateDidChange - (void)registrationStateDidChange

@ -1,15 +0,0 @@
// Created by Michael Kirk on 9/14/16.
// Copyright © 2016 Open Whisper Systems. All rights reserved.
NS_ASSUME_NONNULL_BEGIN
@class PushManager;
@interface OWSStaleNotificationObserver : NSObject
- (instancetype)initWithPushManager:(PushManager *)pushManager NS_DESIGNATED_INITIALIZER;
- (void)startObserving;
@end
NS_ASSUME_NONNULL_END

@ -1,71 +0,0 @@
// Created by Michael Kirk on 9/14/16.
// Copyright © 2016 Open Whisper Systems. All rights reserved.
#import "OWSStaleNotificationObserver.h"
#import "PushManager.h"
#import <SignalServiceKit/OWSReadReceiptsProcessor.h>
#import <SignalServiceKit/TSIncomingMessage.h>
NS_ASSUME_NONNULL_BEGIN
@interface OWSStaleNotificationObserver ()
@property (nonatomic, readonly) PushManager *pushManager;
@end
@implementation OWSStaleNotificationObserver
- (void)dealloc
{
[[NSNotificationCenter defaultCenter] removeObserver:self];
}
- (instancetype)init
{
return [self initWithPushManager:[PushManager sharedManager]];
}
- (instancetype)initWithPushManager:(PushManager *)pushManager
{
self = [super init];
if (!self) {
return self;
}
_pushManager = pushManager;
return self;
}
- (void)startObserving
{
[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(handleMessageRead:)
name:OWSReadReceiptsProcessorMarkedMessageAsReadNotification
object:nil];
}
- (void)handleMessageRead:(NSNotification *)notification
{
if ([notification.object isKindOfClass:[TSIncomingMessage class]]) {
TSIncomingMessage *message = (TSIncomingMessage *)notification.object;
DDLogDebug(@"%@ canceled notification for message:%@", self.tag, message);
[self.pushManager cancelNotificationsWithThreadId:message.uniqueThreadId];
}
}
+ (NSString *)tag
{
return [NSString stringWithFormat:@"[%@]", self.class];
}
- (NSString *)tag
{
return self.class.tag;
}
@end
NS_ASSUME_NONNULL_END

@ -11,6 +11,7 @@
#import <SignalServiceKit/NSDate+millisecondTimeStamp.h> #import <SignalServiceKit/NSDate+millisecondTimeStamp.h>
#import <SignalServiceKit/OWSMessageReceiver.h> #import <SignalServiceKit/OWSMessageReceiver.h>
#import <SignalServiceKit/OWSMessageSender.h> #import <SignalServiceKit/OWSMessageSender.h>
#import <SignalServiceKit/OWSReadReceiptsProcessor.h>
#import <SignalServiceKit/OWSSignalService.h> #import <SignalServiceKit/OWSSignalService.h>
#import <SignalServiceKit/TSAccountManager.h> #import <SignalServiceKit/TSAccountManager.h>
#import <SignalServiceKit/TSOutgoingMessage.h> #import <SignalServiceKit/TSOutgoingMessage.h>
@ -81,9 +82,24 @@ NSString *const Signal_Message_MarkAsRead_Identifier = @"Signal_Message_MarkAsRe
OWSSingletonAssert(); OWSSingletonAssert();
[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(handleMessageRead:)
name:OWSReadReceiptsProcessorMarkedMessageAsReadNotification
object:nil];
return self; return self;
} }
- (void)handleMessageRead:(NSNotification *)notification
{
if ([notification.object isKindOfClass:[TSIncomingMessage class]]) {
TSIncomingMessage *message = (TSIncomingMessage *)notification.object;
DDLogDebug(@"%@ canceled notification for message:%@", self.tag, message);
[self cancelNotificationsWithThreadId:message.uniqueThreadId];
}
}
#pragma mark Manage Incoming Push #pragma mark Manage Incoming Push
- (void)application:(UIApplication *)application didReceiveRemoteNotification:(NSDictionary *)userInfo { - (void)application:(UIApplication *)application didReceiveRemoteNotification:(NSDictionary *)userInfo {

@ -114,13 +114,35 @@ def sort_include_block(text, filepath, filename, file_extension):
for include in includes: for include in includes:
include.isInclude = False include.isInclude = False
# Make sure matching header is first.
matching_header_includes = []
other_includes = []
def is_matching_header(include):
filename_wo_ext = os.path.splitext(filename)[0]
include_filename_wo_ext = os.path.splitext(os.path.basename(include.body))[0]
return filename_wo_ext == include_filename_wo_ext
for include in includes:
if is_matching_header(include):
matching_header_includes.append(include)
else:
other_includes.append(include)
includes = other_includes
def formatBlock(includes): def formatBlock(includes):
lines = [include.format() for include in includes] lines = [include.format() for include in includes]
lines = list(set(lines)) lines = list(set(lines))
def include_sorter(a, b): def include_sorter(a, b):
return cmp(a.lower(), b.lower()) # return cmp(a.lower(), b.lower())
return cmp(a, b)
# print 'before'
# for line in lines:
# print '\t', line
# print
lines.sort(include_sorter) lines.sort(include_sorter)
# print 'after'
# for line in lines:
# print '\t', line
# print
# print # print
# print 'filepath' # print 'filepath'
# for line in lines: # for line in lines:
@ -132,6 +154,8 @@ def sort_include_block(text, filepath, filename, file_extension):
includeQuotes = [include for include in includes if include.isInclude and include.isQuote] includeQuotes = [include for include in includes if include.isInclude and include.isQuote]
importAngles = [include for include in includes if (not include.isInclude) and not include.isQuote] importAngles = [include for include in includes if (not include.isInclude) and not include.isQuote]
importQuotes = [include for include in includes if (not include.isInclude) and include.isQuote] importQuotes = [include for include in includes if (not include.isInclude) and include.isQuote]
if matching_header_includes:
blocks.append(formatBlock(matching_header_includes))
if includeQuotes: if includeQuotes:
blocks.append(formatBlock(includeQuotes)) blocks.append(formatBlock(includeQuotes))
if includeAngles: if includeAngles:
@ -189,7 +213,7 @@ def find_matching_section(text, match_test):
return text0, text1, text2 return text0, text1, text2
def sort_matching_blocks(filepath, filename, file_extension, text, match_func, sort_func): def sort_matching_blocks(sort_name, filepath, filename, file_extension, text, match_func, sort_func):
unprocessed = text unprocessed = text
processed = None processed = None
while True: while True:
@ -230,6 +254,8 @@ def sort_matching_blocks(filepath, filename, file_extension, text, match_func, s
else: else:
break break
if text != processed:
print sort_name, filepath
return processed return processed
@ -249,17 +275,17 @@ def find_include_section(text):
def sort_includes(filepath, filename, file_extension, text): def sort_includes(filepath, filename, file_extension, text):
print 'sort_includes', filepath # print 'sort_includes', filepath
if file_extension not in ('.h', '.m', '.mm'): if file_extension not in ('.h', '.m', '.mm'):
return text return text
return sort_matching_blocks(filepath, filename, file_extension, text, find_include_section, sort_include_block) return sort_matching_blocks('sort_includes', filepath, filename, file_extension, text, find_include_section, sort_include_block)
def sort_class_statements(filepath, filename, file_extension, text): def sort_class_statements(filepath, filename, file_extension, text):
print 'sort_class_statements', filepath # print 'sort_class_statements', filepath
if file_extension not in ('.h', '.m', '.mm'): if file_extension not in ('.h', '.m', '.mm'):
return text return text
return sort_matching_blocks(filepath, filename, file_extension, text, find_class_statement_section, sort_class_statement_block) return sort_matching_blocks('sort_class_statements', filepath, filename, file_extension, text, find_class_statement_section, sort_class_statement_block)
def splitall(path): def splitall(path):

@ -12,6 +12,7 @@ NS_ASSUME_NONNULL_BEGIN
extern NSString *const OWSReadReceiptsProcessorMarkedMessageAsReadNotification; extern NSString *const OWSReadReceiptsProcessorMarkedMessageAsReadNotification;
// TODO:
@interface OWSReadReceiptsProcessor : NSObject @interface OWSReadReceiptsProcessor : NSObject
/** /**

@ -59,14 +59,8 @@ NSString *const OWSReadReceiptsProcessorMarkedMessageAsReadNotification =
- (instancetype)initWithIncomingMessage:(TSIncomingMessage *)message storageManager:(TSStorageManager *)storageManager - (instancetype)initWithIncomingMessage:(TSIncomingMessage *)message storageManager:(TSStorageManager *)storageManager
{ {
// authorId isn't set on all legacy messages, so we take NSString *messageAuthorId = message.messageAuthorId;
// extra measures to ensure we obtain a valid value. OWSAssert(messageAuthorId.length > 0);
NSString *messageAuthorId;
if (message.authorId) { // Group Thread
messageAuthorId = message.authorId;
} else { // Contact Thread
messageAuthorId = [TSContactThread contactIdFromThreadId:message.uniqueThreadId];
}
OWSReadReceipt *readReceipt = [OWSReadReceipt firstWithSenderId:messageAuthorId timestamp:message.timestamp]; OWSReadReceipt *readReceipt = [OWSReadReceipt firstWithSenderId:messageAuthorId timestamp:message.timestamp];
if (readReceipt) { if (readReceipt) {

@ -1,18 +0,0 @@
// Created by Michael Kirk on 9/24/16.
// Copyright © 2016 Open Whisper Systems. All rights reserved.
NS_ASSUME_NONNULL_BEGIN
@class OWSMessageSender;
@class TSIncomingMessage;
@interface OWSSendReadReceiptsJob : NSObject
- (instancetype)init NS_UNAVAILABLE;
- (instancetype)initWithMessageSender:(OWSMessageSender *)messageSender NS_DESIGNATED_INITIALIZER;
- (void)runWith:(TSIncomingMessage *)message;
@end
NS_ASSUME_NONNULL_END

@ -1,108 +0,0 @@
//
// Copyright (c) 2017 Open Whisper Systems. All rights reserved.
//
#import "OWSSendReadReceiptsJob.h"
#import "OWSMessageSender.h"
#import "OWSReadReceipt.h"
#import "OWSReadReceiptsMessage.h"
#import "TSContactThread.h"
#import "TSIncomingMessage.h"
NS_ASSUME_NONNULL_BEGIN
@interface OWSSendReadReceiptsJob ()
@property (atomic) NSMutableArray<OWSReadReceipt *> *readReceiptsQueue;
@property (nonatomic, readonly) OWSMessageSender *messageSender;
@property BOOL isObserving;
@end
@implementation OWSSendReadReceiptsJob
- (void)dealloc
{
[[NSNotificationCenter defaultCenter] removeObserver:self];
}
- (instancetype)initWithMessageSender:(OWSMessageSender *)messageSender
{
self = [super init];
if (!self) {
return self;
}
_readReceiptsQueue = [NSMutableArray new];
_messageSender = messageSender;
_isObserving = NO;
return self;
}
- (void)runWith:(TSIncomingMessage *)message
{
// authorId isn't set on all legacy messages, so we take
// extra measures to ensure we obtain a valid value.
NSString *messageAuthorId;
if (message.authorId) { // Group Thread
messageAuthorId = message.authorId;
} else { // Contact Thread
messageAuthorId = [TSContactThread contactIdFromThreadId:message.uniqueThreadId];
}
OWSReadReceipt *readReceipt = [[OWSReadReceipt alloc] initWithSenderId:messageAuthorId timestamp:message.timestamp];
[self.readReceiptsQueue addObject:readReceipt];
// Wait a bit to bundle up read receipts into one request.
__weak typeof(self) weakSelf = self;
[weakSelf performSelector:@selector(sendAllReadReceiptsInQueue) withObject:nil afterDelay:2.0];
}
- (void)sendAllReadReceiptsInQueue
{
// Synchronized so we don't lose any read receipts while replacing the queue
__block NSArray<OWSReadReceipt *> *_Nullable receiptsToSend;
@synchronized(self)
{
if (self.readReceiptsQueue.count > 0) {
receiptsToSend = self.readReceiptsQueue;
self.readReceiptsQueue = [NSMutableArray new];
}
}
if (receiptsToSend) {
[self sendReadReceipts:receiptsToSend];
} else {
DDLogVerbose(@"Read receipts queue already drained.");
}
}
- (void)sendReadReceipts:(NSArray<OWSReadReceipt *> *)readReceipts
{
OWSReadReceiptsMessage *message = [[OWSReadReceiptsMessage alloc] initWithReadReceipts:readReceipts];
[self.messageSender sendMessage:message
success:^{
DDLogInfo(@"%@ Successfully sent %ld read receipt", self.tag, (unsigned long)readReceipts.count);
}
failure:^(NSError *error) {
DDLogError(@"%@ Failed to send read receipt with error: %@", self.tag, error);
}];
}
#pragma mark - Logging
+ (NSString *)tag
{
return [NSString stringWithFormat:@"[%@]", self.class];
}
- (NSString *)tag
{
return self.class.tag;
}
@end
NS_ASSUME_NONNULL_END

@ -10,8 +10,6 @@ NS_ASSUME_NONNULL_BEGIN
@class TSContactThread; @class TSContactThread;
@class TSGroupThread; @class TSGroupThread;
extern NSString *const TSIncomingMessageWasReadOnThisDeviceNotification;
@interface TSIncomingMessage : TSMessage <OWSReadTracking> @interface TSIncomingMessage : TSMessage <OWSReadTracking>
/** /**
@ -105,11 +103,11 @@ extern NSString *const TSIncomingMessageWasReadOnThisDeviceNotification;
timestamp:(uint64_t)timestamp timestamp:(uint64_t)timestamp
transaction:(YapDatabaseReadWriteTransaction *)transaction; transaction:(YapDatabaseReadWriteTransaction *)transaction;
@property (nonatomic, readonly) NSString *authorId;
// This will be 0 for messages created before we were tracking sourceDeviceId // This will be 0 for messages created before we were tracking sourceDeviceId
@property (nonatomic, readonly) UInt32 sourceDeviceId; @property (nonatomic, readonly) UInt32 sourceDeviceId;
- (NSString *)messageAuthorId;
@end @end
NS_ASSUME_NONNULL_END NS_ASSUME_NONNULL_END

@ -5,6 +5,7 @@
#import "TSIncomingMessage.h" #import "TSIncomingMessage.h"
#import "OWSDisappearingMessagesConfiguration.h" #import "OWSDisappearingMessagesConfiguration.h"
#import "OWSDisappearingMessagesJob.h" #import "OWSDisappearingMessagesJob.h"
#import "OWSReadReceiptManager.h"
#import "TSContactThread.h" #import "TSContactThread.h"
#import "TSDatabaseSecondaryIndexes.h" #import "TSDatabaseSecondaryIndexes.h"
#import "TSGroupThread.h" #import "TSGroupThread.h"
@ -12,10 +13,10 @@
NS_ASSUME_NONNULL_BEGIN NS_ASSUME_NONNULL_BEGIN
NSString *const TSIncomingMessageWasReadOnThisDeviceNotification = @"TSIncomingMessageWasReadOnThisDeviceNotification";
@interface TSIncomingMessage () @interface TSIncomingMessage ()
@property (nonatomic, readonly) NSString *authorId;
@property (nonatomic, getter=wasRead) BOOL read; @property (nonatomic, getter=wasRead) BOOL read;
@end @end
@ -92,15 +93,8 @@ NSString *const TSIncomingMessageWasReadOnThisDeviceNotification = @"TSIncomingM
if ([interaction isKindOfClass:[TSIncomingMessage class]]) { if ([interaction isKindOfClass:[TSIncomingMessage class]]) {
TSIncomingMessage *message = (TSIncomingMessage *)interaction; TSIncomingMessage *message = (TSIncomingMessage *)interaction;
// authorId isn't set on all legacy messages, so we take NSString *messageAuthorId = message.messageAuthorId;
// extra measures to ensure we obtain a valid value. OWSAssert(messageAuthorId.length > 0);
NSString *messageAuthorId;
if (message.authorId) { // Group Thread
messageAuthorId = message.authorId;
} else { // Contact Thread
messageAuthorId =
[TSContactThread contactIdFromThreadId:message.uniqueThreadId];
}
if ([messageAuthorId isEqualToString:authorId]) { if ([messageAuthorId isEqualToString:authorId]) {
foundMessage = message; foundMessage = message;
@ -112,6 +106,22 @@ NSString *const TSIncomingMessageWasReadOnThisDeviceNotification = @"TSIncomingM
return foundMessage; return foundMessage;
} }
- (NSString *)messageAuthorId
{
// authorId isn't set on all legacy messages, so we take
// extra measures to ensure we obtain a valid value.
NSString *messageAuthorId;
if (self.authorId) {
// Group Thread
messageAuthorId = self.authorId;
} else {
// Contact Thread
messageAuthorId = [TSContactThread contactIdFromThreadId:self.uniqueThreadId];
}
OWSAssert(messageAuthorId.length > 0);
return messageAuthorId;
}
#pragma mark - OWSReadTracking #pragma mark - OWSReadTracking
- (BOOL)shouldAffectUnreadCounts - (BOOL)shouldAffectUnreadCounts
@ -139,12 +149,7 @@ NSString *const TSIncomingMessageWasReadOnThisDeviceNotification = @"TSIncomingM
} }
if (sendReadReceipt) { if (sendReadReceipt) {
// Notification must happen outside of the transaction, else we'll likely crash when the notification receiver [OWSReadReceiptManager.sharedManager messageWasReadLocally:self];
// tries to do anything with the DB.
dispatch_async(dispatch_get_main_queue(), ^{
[[NSNotificationCenter defaultCenter] postNotificationName:TSIncomingMessageWasReadOnThisDeviceNotification
object:self];
});
} }
} }

@ -264,7 +264,7 @@ NS_ASSUME_NONNULL_BEGIN
if ([message isKindOfClass:[TSIncomingMessage class]]) { if ([message isKindOfClass:[TSIncomingMessage class]]) {
TSIncomingMessage *incomingMessage = (TSIncomingMessage *)message; TSIncomingMessage *incomingMessage = (TSIncomingMessage *)message;
NSString *contactName = [contactsManager displayNameForPhoneIdentifier:incomingMessage.authorId]; NSString *contactName = [contactsManager displayNameForPhoneIdentifier:incomingMessage.messageAuthorId];
[[[OWSDisappearingConfigurationUpdateInfoMessage alloc] initWithTimestamp:message.timestamp [[[OWSDisappearingConfigurationUpdateInfoMessage alloc] initWithTimestamp:message.timestamp
thread:message.thread thread:message.thread

@ -1,19 +0,0 @@
//
// Copyright (c) 2017 Open Whisper Systems. All rights reserved.
//
NS_ASSUME_NONNULL_BEGIN
@class TSStorageManager;
@class OWSMessageSender;
@interface OWSIncomingMessageReadObserver : NSObject
- (instancetype)init NS_UNAVAILABLE;
- (instancetype)initWithMessageSender:(OWSMessageSender *)messageSender NS_DESIGNATED_INITIALIZER;
- (void)startObserving;
@end
NS_ASSUME_NONNULL_END

@ -1,79 +0,0 @@
//
// Copyright (c) 2017 Open Whisper Systems. All rights reserved.
//
#import "OWSIncomingMessageReadObserver.h"
#import "NSDate+millisecondTimeStamp.h"
#import "OWSSendReadReceiptsJob.h"
#import "TSIncomingMessage.h"
NS_ASSUME_NONNULL_BEGIN
@interface OWSIncomingMessageReadObserver ()
@property (nonatomic) BOOL isObserving;
@property (nonatomic, readonly) OWSSendReadReceiptsJob *sendReadReceiptsJob;
@end
@implementation OWSIncomingMessageReadObserver
- (void)dealloc
{
[[NSNotificationCenter defaultCenter] removeObserver:self];
}
- (instancetype)initWithMessageSender:(OWSMessageSender *)messageSender
{
self = [super init];
if (!self) {
return self;
}
_isObserving = NO;
_sendReadReceiptsJob = [[OWSSendReadReceiptsJob alloc] initWithMessageSender:messageSender];
return self;
}
- (void)startObserving
{
OWSAssert([NSThread isMainThread]);
if (self.isObserving) {
return;
}
self.isObserving = true;
[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(handleLocalReadNotification:)
name:TSIncomingMessageWasReadOnThisDeviceNotification
object:nil];
}
- (void)handleLocalReadNotification:(NSNotification *)notification
{
if (![notification.object isKindOfClass:[TSIncomingMessage class]]) {
DDLogError(@"%@ Read receipt notifier got unexpected object: %@", self.tag, notification.object);
return;
}
TSIncomingMessage *message = (TSIncomingMessage *)notification.object;
[self.sendReadReceiptsJob runWith:message];
}
#pragma mark - Logging
+ (NSString *)tag
{
return [NSString stringWithFormat:@"[%@]", self.class];
}
- (NSString *)tag
{
return self.class.tag;
}
@end
NS_ASSUME_NONNULL_END

@ -0,0 +1,43 @@
//
// Copyright (c) 2017 Open Whisper Systems. All rights reserved.
//
NS_ASSUME_NONNULL_BEGIN
@class TSIncomingMessage;
// There are four kinds of read receipts:
//
// * Read receipts that this client sends to linked
// devices to inform them that a message has been read.
// * Read receipts that this client receives from linked
// devices that inform this client that a message has been read.
// * These read receipts are saved so that they can be applied
// if they arrive before the corresponding message.
// * Read receipts that this client sends to other users
// to inform them that a message has been read.
// * Read receipts that this client receives from other users
// that inform this client that a message has been read.
// * These read receipts are saved so that they can be applied
// if they arrive before the corresponding message.
//
// TODO: Merge OWSReadReceiptsProcessor into this class.
@interface OWSReadReceiptManager : NSObject
- (instancetype)init NS_UNAVAILABLE;
+ (instancetype)sharedManager;
// This method can be called from any thread.
//
// It 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.
- (void)messageWasReadLocally:(TSIncomingMessage *)message;
@end
NS_ASSUME_NONNULL_END

@ -0,0 +1,187 @@
//
// Copyright (c) 2017 Open Whisper Systems. All rights reserved.
//
#import "OWSReadReceiptManager.h"
#import "OWSMessageSender.h"
#import "OWSReadReceipt.h"
#import "OWSReadReceiptsMessage.h"
#import "TSContactThread.h"
#import "TSDatabaseView.h"
#import "TSIncomingMessage.h"
#import "TextSecureKitEnv.h"
#import "Threading.h"
NS_ASSUME_NONNULL_BEGIN
@interface OWSReadReceiptManager ()
@property (nonatomic, readonly) TSStorageManager *storageManager;
@property (nonatomic, readonly) OWSMessageSender *messageSender;
// 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;
// Should only be accessed while synchronized on the OWSReadReceiptManager.
@property (nonatomic) BOOL isProcessing;
@end
#pragma mark -
@implementation OWSReadReceiptManager
+ (instancetype)sharedManager
{
static OWSReadReceiptManager *sharedMyManager = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
sharedMyManager = [[self alloc] initDefault];
});
return sharedMyManager;
}
- (instancetype)initDefault
{
OWSMessageSender *messageSender = [TextSecureKitEnv sharedEnv].messageSender;
return [self initWithMessageSender:messageSender];
}
- (instancetype)initWithMessageSender:(OWSMessageSender *)messageSender
{
self = [super init];
if (!self) {
return self;
}
_messageSender = messageSender;
_toLinkedDevicesReadReceiptMap = [NSMutableDictionary new];
OWSSingletonAssert();
[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(databaseViewRegistrationComplete)
name:kNSNotificationName_DatabaseViewRegistrationComplete
object:nil];
// Try to start processing.
dispatch_async(dispatch_get_main_queue(), ^{
[self scheduleProcessing];
});
return self;
}
- (void)dealloc
{
[[NSNotificationCenter defaultCenter] removeObserver:self];
}
- (void)databaseViewRegistrationComplete
{
[self scheduleProcessing];
}
// Schedules a processing pass, unless one is already scheduled.
- (void)scheduleProcessing
{
DispatchMainThreadSafe(^{
@synchronized(self)
{
if ([TSDatabaseView hasPendingViewRegistrations]) {
return;
}
if (self.isProcessing) {
return;
}
self.isProcessing = YES;
// Process read receipts every N seconds.
//
// We want a value high enough to allow us to effectively deduplicate,
// read receipts without being so high that we risk not sending read
// receipts due to app exit.
const CGFloat kProcessingFrequencySeconds = 3.f;
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(kProcessingFrequencySeconds * NSEC_PER_SEC)),
dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0),
^{
[self process];
});
}
});
}
- (void)process
{
@synchronized(self)
{
self.isProcessing = NO;
NSArray<OWSReadReceipt *> *readReceiptsToSend = [self.toLinkedDevicesReadReceiptMap allValues];
[self.toLinkedDevicesReadReceiptMap removeAllObjects];
if (readReceiptsToSend.count > 0) {
OWSReadReceiptsMessage *message = [[OWSReadReceiptsMessage alloc] initWithReadReceipts:readReceiptsToSend];
dispatch_async(dispatch_get_main_queue(), ^{
[self.messageSender sendMessage:message
success:^{
DDLogInfo(@"%@ Successfully sent %zd read receipt to linked devices.",
self.tag,
readReceiptsToSend.count);
}
failure:^(NSError *error) {
DDLogError(@"%@ Failed to send read receipt to linked devices with error: %@", self.tag, error);
}];
});
}
}
}
- (void)messageWasReadLocally:(TSIncomingMessage *)message;
{
@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.
return;
}
self.toLinkedDevicesReadReceiptMap[threadUniqueId] = newReadReceipt;
[self scheduleProcessing];
}
}
#pragma mark - Logging
+ (NSString *)tag
{
return [NSString stringWithFormat:@"[%@]", self.class];
}
- (NSString *)tag
{
return self.class.tag;
}
@end
NS_ASSUME_NONNULL_END
Loading…
Cancel
Save