Rework the “disappearing messages” logic.

// FREEBIE
This commit is contained in:
Matthew Chen 2017-05-09 14:38:49 -04:00
parent 9f569d376c
commit 6e52009ff0
11 changed files with 257 additions and 69 deletions

View File

@ -17,7 +17,6 @@ NSString *const OWSReadReceiptsProcessorMarkedMessageAsReadNotification =
@interface OWSReadReceiptsProcessor ()
@property (nonatomic, readonly) NSArray<OWSReadReceipt *> *readReceipts;
@property (nonatomic, readonly) OWSDisappearingMessagesJob *disappearingMessagesJob;
@property (nonatomic, readonly) TSStorageManager *storageManager;
@end
@ -34,7 +33,6 @@ NSString *const OWSReadReceiptsProcessorMarkedMessageAsReadNotification =
_readReceipts = [readReceipts copy];
_storageManager = storageManager;
_disappearingMessagesJob = [[OWSDisappearingMessagesJob alloc] initWithStorageManager:storageManager];
return self;
}
@ -85,7 +83,7 @@ NSString *const OWSReadReceiptsProcessorMarkedMessageAsReadNotification =
[TSIncomingMessage findMessageWithAuthorId:readReceipt.senderId timestamp:readReceipt.timestamp];
if (message) {
[message markAsReadFromReadReceipt];
[self.disappearingMessagesJob setExpirationForMessage:message expirationStartedAt:readReceipt.timestamp];
[OWSDisappearingMessagesJob setExpirationForMessage:message expirationStartedAt:readReceipt.timestamp];
// If it was previously saved, no need to keep it around any longer.
[readReceipt remove];
[[NSNotificationCenter defaultCenter]

View File

@ -4,7 +4,6 @@
#import "OWSRecordTranscriptJob.h"
#import "OWSAttachmentsProcessor.h"
#import "OWSDisappearingMessagesJob.h"
#import "OWSIncomingSentMessageTranscript.h"
#import "OWSMessageSender.h"
#import "TSInfoMessage.h"

View File

@ -4,7 +4,6 @@
#import "TSMessage.h"
#import "NSDate+millisecondTimeStamp.h"
#import "OWSDisappearingMessagesJob.h"
#import "TSAttachment.h"
#import "TSAttachmentPointer.h"
#import "TSThread.h"

View File

@ -1,5 +1,6 @@
// Created by Michael Kirk on 9/23/16.
// Copyright © 2016 Open Whisper Systems. All rights reserved.
//
// Copyright (c) 2017 Open Whisper Systems. All rights reserved.
//
NS_ASSUME_NONNULL_BEGIN
@ -10,15 +11,13 @@ NS_ASSUME_NONNULL_BEGIN
@interface OWSDisappearingMessagesJob : NSObject
+ (instancetype)sharedJob;
- (instancetype)init NS_UNAVAILABLE;
- (instancetype)initWithStorageManager:(TSStorageManager *)storageManager NS_DESIGNATED_INITIALIZER;
- (void)run;
- (void)setExpirationsForThread:(TSThread *)thread;
- (void)setExpirationForMessage:(TSMessage *)message;
- (void)setExpirationForMessage:(TSMessage *)message expirationStartedAt:(uint64_t)expirationStartedAt;
- (void)runBy:(uint64_t)millisecondTimestamp;
+ (void)setExpirationsForThread:(TSThread *)thread;
+ (void)setExpirationForMessage:(TSMessage *)message;
+ (void)setExpirationForMessage:(TSMessage *)message expirationStartedAt:(uint64_t)expirationStartedAt;
/**
* Synchronize our disappearing messages settings with that of the given message. Useful so we can
@ -31,9 +30,13 @@ NS_ASSUME_NONNULL_BEGIN
* @param contactsManager
* Provides the contact name responsible for any configuration changes in an info message.
*/
- (void)becomeConsistentWithConfigurationForMessage:(TSMessage *)message
+ (void)becomeConsistentWithConfigurationForMessage:(TSMessage *)message
contactsManager:(id<ContactsManagerProtocol>)contactsManager;
// Clean up any messages that expired since last launch immediately
// and continue cleaning in the background.
- (void)startIfNecessary;
@end
NS_ASSUME_NONNULL_END

View File

@ -1,26 +1,46 @@
// Created by Michael Kirk on 9/23/16.
// Copyright © 2016 Open Whisper Systems. All rights reserved.
//
// Copyright (c) 2017 Open Whisper Systems. All rights reserved.
//
#import "OWSDisappearingMessagesJob.h"
#import "ContactsManagerProtocol.h"
#import "NSDate+millisecondTimeStamp.h"
#import "NSTimer+OWS.h"
#import "OWSDisappearingConfigurationUpdateInfoMessage.h"
#import "OWSDisappearingMessagesConfiguration.h"
#import "OWSDisappearingMessagesFinder.h"
#import "TSIncomingMessage.h"
#import "TSMessage.h"
#import "TSStorageManager.h"
NS_ASSUME_NONNULL_BEGIN
@interface OWSDisappearingMessagesJob ()
// This property should only be accessed on the serialQueue.
@property (nonatomic, readonly) OWSDisappearingMessagesFinder *disappearingMessagesFinder;
@property (atomic) uint64_t scheduledAt;
// These three properties should only be accessed on the main thread.
@property (nonatomic) BOOL hasStarted;
@property (nonatomic, nullable) NSTimer *timer;
@property (nonatomic, nullable) NSDate *timerScheduleDate;
@end
#pragma mark -
@implementation OWSDisappearingMessagesJob
+ (instancetype)sharedJob
{
static OWSDisappearingMessagesJob *sharedJob = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
sharedJob = [[self alloc] initWithStorageManager:[TSStorageManager sharedManager]];
});
return sharedJob;
}
- (instancetype)initWithStorageManager:(TSStorageManager *)storageManager
{
self = [super init];
@ -28,12 +48,23 @@ NS_ASSUME_NONNULL_BEGIN
return self;
}
_scheduledAt = ULLONG_MAX;
_disappearingMessagesFinder = [[OWSDisappearingMessagesFinder alloc] initWithStorageManager:storageManager];
OWSSingletonAssert();
return self;
}
+ (dispatch_queue_t)serialQueue
{
static dispatch_queue_t queue = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
queue = dispatch_queue_create("org.whispersystems.disappearing.messages", DISPATCH_QUEUE_SERIAL);
});
return queue;
}
- (void)run
{
uint64_t now = [NSDate ows_millisecondTimeStamp];
@ -46,18 +77,17 @@ NS_ASSUME_NONNULL_BEGIN
return;
}
DDLogDebug(@"%@ removing message which expired at: %lld", self.tag, message.expiresAt);
DDLogDebug(@"%@ Removing message which expired at: %lld", self.tag, message.expiresAt);
[message remove];
expirationCount++;
}];
DDLogDebug(@"%@ removed %u expired messages", self.tag, expirationCount);
DDLogDebug(@"%@ Removed %u expired messages", self.tag, expirationCount);
}
- (void)runLoop
{
// allow next runAt to schedule.
self.scheduledAt = ULLONG_MAX;
DDLogVerbose(@"%@ Run", self.tag);
[self run];
@ -69,23 +99,19 @@ NS_ASSUME_NONNULL_BEGIN
unsigned int delaySeconds = (10 * 60); // 10 minutes.
DDLogDebug(
@"%@ No more expiring messages. Setting next check %u seconds into the future", self.tag, delaySeconds);
[self runBy:now + delaySeconds * 1000];
[self runByDate:[NSDate ows_dateWithMillisecondsSince1970:now + delaySeconds * 1000]];
return;
}
uint64_t nextExpirationAt = [nextExpirationTimestampNumber unsignedLongLongValue];
uint64_t runByMilliseconds;
if (nextExpirationAt < now + 1000) {
DDLogWarn(@"%@ Next run requested at %llu, which is too soon. Delaying by 1 sec to prevent churn",
self.tag,
nextExpirationAt);
runByMilliseconds = now + 1000;
} else {
runByMilliseconds = nextExpirationAt;
}
[self runByDate:[NSDate ows_dateWithMillisecondsSince1970:MAX(nextExpirationAt, now)]];
}
DDLogVerbose(@"%@ Requesting next expiration to run by: %llu", self.tag, nextExpirationAt);
[self runBy:runByMilliseconds];
+ (void)setExpirationForMessage:(TSMessage *)message
{
dispatch_async(self.serialQueue, ^{
[[self sharedJob] setExpirationForMessage:message];
});
}
- (void)setExpirationForMessage:(TSMessage *)message
@ -104,6 +130,13 @@ NS_ASSUME_NONNULL_BEGIN
[self setExpirationForMessage:message expirationStartedAt:[NSDate ows_millisecondTimeStamp]];
}
+ (void)setExpirationForMessage:(TSMessage *)message expirationStartedAt:(uint64_t)expirationStartedAt
{
dispatch_async(self.serialQueue, ^{
[[self sharedJob] setExpirationForMessage:message expirationStartedAt:expirationStartedAt];
});
}
- (void)setExpirationForMessage:(TSMessage *)message expirationStartedAt:(uint64_t)expirationStartedAt
{
if (!message.isExpiringMessage) {
@ -120,7 +153,14 @@ NS_ASSUME_NONNULL_BEGIN
}
// Necessary that the async expiration run happens *after* the message is saved with expiration configuration.
[self runBy:message.expiresAt];
[self runByDate:[NSDate ows_dateWithMillisecondsSince1970:message.expiresAt]];
}
+ (void)setExpirationsForThread:(TSThread *)thread
{
dispatch_async(self.serialQueue, ^{
[[self sharedJob] setExpirationsForThread:thread];
});
}
- (void)setExpirationsForThread:(TSThread *)thread
@ -138,26 +178,14 @@ NS_ASSUME_NONNULL_BEGIN
}];
}
- (void)runBy:(uint64_t)timestamp
+ (void)becomeConsistentWithConfigurationForMessage:(TSMessage *)message
contactsManager:(id<ContactsManagerProtocol>)contactsManager
{
// Prevent amplification.
if (timestamp >= self.scheduledAt) {
DDLogVerbose(@"%@ expiration already scheduled before %llu", self.tag, timestamp);
return;
}
// Update Schedule
DDLogVerbose(@"%@ Scheduled expiration run at %llu", self.tag, timestamp);
self.scheduledAt = timestamp;
uint64_t millisecondsDelay = timestamp - [NSDate ows_millisecondTimeStamp];
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, NSEC_PER_MSEC * millisecondsDelay),
dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0),
^{
[self runLoop];
});
dispatch_async(self.serialQueue, ^{
[[self sharedJob] becomeConsistentWithConfigurationForMessage:message contactsManager:contactsManager];
});
}
- (void)becomeConsistentWithConfigurationForMessage:(TSMessage *)message
contactsManager:(id<ContactsManagerProtocol>)contactsManager
{
@ -206,6 +234,83 @@ NS_ASSUME_NONNULL_BEGIN
}
}
- (void)startIfNecessary
{
dispatch_async(dispatch_get_main_queue(), ^{
if (self.hasStarted) {
return;
}
self.hasStarted = YES;
[self runNow];
});
}
- (void)runNow
{
[self runByDate:[NSDate new] ignoreMinDelay:YES];
}
- (void)runByDate:(NSDate *)date
{
[self runByDate:date ignoreMinDelay:NO];
}
- (void)runByDate:(NSDate *)date ignoreMinDelay:(BOOL)ignoreMinDelay
{
OWSAssert(date);
NSDateFormatter *dateFormatter = [NSDateFormatter new];
dateFormatter.dateStyle = NSDateFormatterShortStyle;
dateFormatter.timeStyle = kCFDateFormatterMediumStyle;
dispatch_async(dispatch_get_main_queue(), ^{
// Don't run more often than once per second.
const NSTimeInterval kMinDelaySeconds = ignoreMinDelay ? 0.f : 1.f;
// Don't run less often than once per N minutes.
const NSTimeInterval kMaxDelaySeconds = 5 * 60.f;
NSTimeInterval delaySeconds
= MAX(kMinDelaySeconds, MIN(kMaxDelaySeconds, [date timeIntervalSinceDate:[NSDate new]]));
NSDate *timerScheduleDate = [NSDate dateWithTimeIntervalSinceNow:delaySeconds];
if (self.timerScheduleDate && [timerScheduleDate timeIntervalSinceDate:self.timerScheduleDate] > 0) {
DDLogVerbose(@"%@ Request to run at %@ (%d sec.) ignored due to scheduled run at %@ (%d sec.)",
self.tag,
[dateFormatter stringFromDate:date],
(int)round(MAX(0, [date timeIntervalSinceDate:[NSDate new]])),
[dateFormatter stringFromDate:self.timerScheduleDate],
(int)round(MAX(0, [self.timerScheduleDate timeIntervalSinceDate:[NSDate new]])));
return;
}
// Update Schedule
NSDateFormatter *dateFormatter = [NSDateFormatter new];
dateFormatter.dateStyle = NSDateFormatterShortStyle;
dateFormatter.timeStyle = kCFDateFormatterMediumStyle;
DDLogVerbose(@"%@ Scheduled run at %@ (%d sec.)",
self.tag,
[dateFormatter stringFromDate:timerScheduleDate],
(int)round(MAX(0, [timerScheduleDate timeIntervalSinceDate:[NSDate new]])));
self.timerScheduleDate = timerScheduleDate;
[self.timer invalidate];
self.timer = [NSTimer weakScheduledTimerWithTimeInterval:delaySeconds
target:self
selector:@selector(timerDidFire)
userInfo:nil
repeats:NO];
});
}
- (void)timerDidFire
{
[self.timer invalidate];
self.timer = nil;
self.timerScheduleDate = nil;
dispatch_async(OWSDisappearingMessagesJob.serialQueue, ^{
[self runLoop];
});
}
#pragma mark - Logging
+ (NSString *)tag

View File

@ -1,5 +1,6 @@
// Created by Michael Kirk on 9/24/16.
// Copyright © 2016 Open Whisper Systems. All rights reserved.
//
// Copyright (c) 2017 Open Whisper Systems. All rights reserved.
//
#import "OWSIncomingMessageReadObserver.h"
#import "NSDate+millisecondTimeStamp.h"
@ -13,7 +14,6 @@ NS_ASSUME_NONNULL_BEGIN
@interface OWSIncomingMessageReadObserver ()
@property BOOL isObserving;
@property (nonatomic, readonly) OWSDisappearingMessagesJob *disappearingMessagesJob;
@property (nonatomic, readonly) OWSSendReadReceiptsJob *sendReadReceiptsJob;
@end
@ -34,7 +34,6 @@ NS_ASSUME_NONNULL_BEGIN
}
_isObserving = NO;
_disappearingMessagesJob = [[OWSDisappearingMessagesJob alloc] initWithStorageManager:storageManager];
_sendReadReceiptsJob = [[OWSSendReadReceiptsJob alloc] initWithMessageSender:messageSender];
return self;
@ -61,7 +60,7 @@ NS_ASSUME_NONNULL_BEGIN
}
TSIncomingMessage *message = (TSIncomingMessage *)notification.object;
[self.disappearingMessagesJob setExpirationForMessage:message];
[OWSDisappearingMessagesJob setExpirationForMessage:message];
[self.sendReadReceiptsJob runWith:message];
}

View File

@ -341,7 +341,6 @@ NSString *const OWSMessageSenderRateLimitedException = @"RateLimitedException";
@property (nonatomic, readonly) YapDatabaseConnection *dbConnection;
@property (nonatomic, readonly) id<ContactsManagerProtocol> contactsManager;
@property (nonatomic, readonly) ContactsUpdater *contactsUpdater;
@property (nonatomic, readonly) OWSDisappearingMessagesJob *disappearingMessagesJob;
@property (atomic, readonly) NSMutableDictionary<NSString *, NSOperationQueue *> *sendingQueueMap;
@end
@ -366,7 +365,6 @@ NSString *const OWSMessageSenderRateLimitedException = @"RateLimitedException";
_uploadingService = [[OWSUploadingService alloc] initWithNetworkManager:networkManager];
_dbConnection = storageManager.newDatabaseConnection;
_disappearingMessagesJob = [[OWSDisappearingMessagesJob alloc] initWithStorageManager:storageManager];
OWSSingletonAssert();
@ -1060,20 +1058,20 @@ NSString *const OWSMessageSenderRateLimitedException = @"RateLimitedException";
[self sendSyncTranscriptForMessage:message];
}
[self.disappearingMessagesJob setExpirationForMessage:message];
[OWSDisappearingMessagesJob setExpirationForMessage:message];
}
- (void)handleMessageSentRemotely:(TSOutgoingMessage *)message sentAt:(uint64_t)sentAt
{
[message updateWithWasSentAndDelivered];
[self becomeConsistentWithDisappearingConfigurationForMessage:message];
[self.disappearingMessagesJob setExpirationForMessage:message expirationStartedAt:sentAt];
[OWSDisappearingMessagesJob setExpirationForMessage:message expirationStartedAt:sentAt];
}
- (void)becomeConsistentWithDisappearingConfigurationForMessage:(TSOutgoingMessage *)outgoingMessage
{
[self.disappearingMessagesJob becomeConsistentWithConfigurationForMessage:outgoingMessage
contactsManager:self.contactsManager];
[OWSDisappearingMessagesJob becomeConsistentWithConfigurationForMessage:outgoingMessage
contactsManager:self.contactsManager];
}
- (void)handleSendToMyself:(TSOutgoingMessage *)outgoingMessage

View File

@ -13,7 +13,6 @@ NS_ASSUME_NONNULL_BEGIN
@class OWSSignalServiceProtosEnvelope;
@class OWSSignalServiceProtosDataMessage;
@class ContactsUpdater;
@class OWSDisappearingMessagesJob;
@class OWSMessageSender;
@protocol ContactsManagerProtocol;
@protocol OWSCallMessageHandler;

View File

@ -48,12 +48,13 @@ NS_ASSUME_NONNULL_BEGIN
@property (nonatomic, readonly) id<ContactsManagerProtocol> contactsManager;
@property (nonatomic, readonly) TSStorageManager *storageManager;
@property (nonatomic, readonly) OWSMessageSender *messageSender;
@property (nonatomic, readonly) OWSDisappearingMessagesJob *disappearingMessagesJob;
@property (nonatomic, readonly) OWSIncomingMessageFinder *incomingMessageFinder;
@property (nonatomic, readonly) OWSBlockingManager *blockingManager;
@end
#pragma mark -
@implementation TSMessagesManager
+ (instancetype)sharedManager {
@ -103,7 +104,6 @@ NS_ASSUME_NONNULL_BEGIN
_messageSender = messageSender;
_dbConnection = storageManager.newDatabaseConnection;
_disappearingMessagesJob = [[OWSDisappearingMessagesJob alloc] initWithStorageManager:storageManager];
_incomingMessageFinder = [[OWSIncomingMessageFinder alloc] initWithDatabase:storageManager.database];
_blockingManager = [OWSBlockingManager sharedManager];
@ -940,8 +940,8 @@ NS_ASSUME_NONNULL_BEGIN
storageManager:self.storageManager];
[readReceiptsProcessor process];
[self.disappearingMessagesJob becomeConsistentWithConfigurationForMessage:incomingMessage
contactsManager:self.contactsManager];
[OWSDisappearingMessagesJob becomeConsistentWithConfigurationForMessage:incomingMessage
contactsManager:self.contactsManager];
// Update thread preview in inbox
[thread touch];

19
src/Util/NSTimer+OWS.h Normal file
View File

@ -0,0 +1,19 @@
//
// Copyright (c) 2017 Open Whisper Systems. All rights reserved.
//
NS_ASSUME_NONNULL_BEGIN
@interface NSTimer (OWS)
// This method avoids the classic NSTimer retain cycle bug
// by using a weak reference to the target.
+ (NSTimer *)weakScheduledTimerWithTimeInterval:(NSTimeInterval)timeInterval
target:(id)target
selector:(SEL)selector
userInfo:(nullable id)userInfo
repeats:(BOOL)repeats;
@end
NS_ASSUME_NONNULL_END

69
src/Util/NSTimer+OWS.m Normal file
View File

@ -0,0 +1,69 @@
//
// Copyright (c) 2017 Open Whisper Systems. All rights reserved.
//
#import "NSTimer+OWS.h"
#import <objc/runtime.h>
NS_ASSUME_NONNULL_BEGIN
@interface NSTimerProxy : NSObject
@property (nonatomic, weak) id target;
@property (nonatomic) SEL selector;
@end
#pragma mark -
@implementation NSTimerProxy
- (void)timerFired:(NSDictionary *)userInfo
{
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Warc-performSelector-leaks"
[self.target performSelector:self.selector withObject:userInfo];
#pragma clang diagnostic pop
}
@end
#pragma mark -
static void *kNSTimer_OWS_Proxy = &kNSTimer_OWS_Proxy;
@implementation NSTimer (OWS)
- (NSTimerProxy *)ows_proxy
{
return objc_getAssociatedObject(self, kNSTimer_OWS_Proxy);
}
- (void)ows_setProxy:(NSTimerProxy *)proxy
{
OWSAssert(proxy);
objc_setAssociatedObject(self, kNSTimer_OWS_Proxy, proxy, OBJC_ASSOCIATION_RETAIN);
}
+ (NSTimer *)weakScheduledTimerWithTimeInterval:(NSTimeInterval)timeInterval
target:(id)target
selector:(SEL)selector
userInfo:(nullable id)userInfo
repeats:(BOOL)repeats
{
NSTimerProxy *proxy = [NSTimerProxy new];
proxy.target = target;
proxy.selector = selector;
NSTimer *timer = [NSTimer scheduledTimerWithTimeInterval:timeInterval
target:proxy
selector:@selector(timerFired:)
userInfo:userInfo
repeats:repeats];
[timer ows_setProxy:proxy];
return timer;
}
@end
NS_ASSUME_NONNULL_END