|
|
|
@ -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
|
|
|
|
|