Merge branch 'charlesmchen/deltaContactIntersection2'

pull/1/head
Matthew Chen 7 years ago
commit a9353ed4e8

@ -22,9 +22,16 @@
@import Contacts;
NS_ASSUME_NONNULL_BEGIN
NSString *const OWSContactsManagerSignalAccountsDidChangeNotification
= @"OWSContactsManagerSignalAccountsDidChangeNotification";
NSString *const OWSContactsManagerCollection = @"OWSContactsManagerCollection";
NSString *const OWSContactsManagerKeyLastKnownContactPhoneNumbers
= @"OWSContactsManagerKeyLastKnownContactPhoneNumbers";
NSString *const OWSContactsManagerKeyNextFullIntersectionDate = @"OWSContactsManagerKeyNextFullIntersectionDate2";
@interface OWSContactsManager () <SystemContactsFetcherDelegate>
@property (nonatomic) BOOL isContactsUpdateInFlight;
@ -42,6 +49,8 @@ NSString *const OWSContactsManagerSignalAccountsDidChangeNotification
@end
#pragma mark -
@implementation OWSContactsManager
- (id)init
@ -216,7 +225,7 @@ NSString *const OWSContactsManagerSignalAccountsDidChangeNotification
} else {
shouldClearStaleCache = YES;
}
[self updateWithContacts:contacts shouldClearStaleCache:shouldClearStaleCache];
[self updateWithContacts:contacts isUserRequested:isUserRequested shouldClearStaleCache:shouldClearStaleCache];
}
- (void)systemContactsFetcher:(SystemContactsFetcher *)systemContactsFetcher
@ -225,29 +234,142 @@ NSString *const OWSContactsManagerSignalAccountsDidChangeNotification
if (authorizationStatus == ContactStoreAuthorizationStatusRestricted
|| authorizationStatus == ContactStoreAuthorizationStatusDenied) {
// Clear the contacts cache if access to the system contacts is revoked.
[self updateWithContacts:@[] shouldClearStaleCache:YES];
[self updateWithContacts:@[] isUserRequested:NO shouldClearStaleCache:YES];
}
}
#pragma mark - Intersection
- (void)intersectContactsWithCompletion:(void (^)(NSError *_Nullable error))completionBlock
- (NSSet<NSString *> *)recipientIdsForIntersectionWithContacts:(NSArray<Contact *> *)contacts
{
[self intersectContactsWithRetryDelay:1 completion:completionBlock];
OWSAssert(contacts);
NSMutableSet<NSString *> *recipientIds = [NSMutableSet set];
for (Contact *contact in contacts) {
for (PhoneNumber *phoneNumber in contact.parsedPhoneNumbers) {
[recipientIds addObject:phoneNumber.toE164];
}
}
return recipientIds;
}
- (void)intersectContactsWithRetryDelay:(double)retryDelaySeconds
completion:(void (^)(NSError *_Nullable error))completionBlock
- (void)intersectContacts:(NSArray<Contact *> *)contacts
isUserRequested:(BOOL)isUserRequested
completion:(void (^)(NSError *_Nullable error))completion
{
void (^success)(void) = ^{
OWSAssert(contacts);
OWSAssert(completion);
dispatch_async(self.serialQueue, ^{
__block BOOL isFullIntersection = YES;
__block NSSet<NSString *> *allContactRecipientIds;
__block NSSet<NSString *> *recipientIdsForIntersection;
[self.dbReadConnection readWithBlock:^(YapDatabaseReadTransaction *transaction) {
// Contact updates initiated by the user should always do a full intersection.
if (!isUserRequested) {
NSDate *_Nullable nextFullIntersectionDate =
[transaction dateForKey:OWSContactsManagerKeyNextFullIntersectionDate
inCollection:OWSContactsManagerCollection];
if (nextFullIntersectionDate && [nextFullIntersectionDate isAfterNow]) {
isFullIntersection = NO;
}
}
allContactRecipientIds = [self recipientIdsForIntersectionWithContacts:contacts];
recipientIdsForIntersection = allContactRecipientIds;
if (!isFullIntersection) {
// Do a "delta" intersection instead of a "full" intersection:
// only intersect new contacts which were not in the last successful
// "full" intersection.
NSSet<NSString *> *_Nullable lastKnownContactPhoneNumbers =
[transaction objectForKey:OWSContactsManagerKeyLastKnownContactPhoneNumbers
inCollection:OWSContactsManagerCollection];
if (lastKnownContactPhoneNumbers) {
// Do a "delta" sync which only intersects recipient ids not included
// in the last full intersection.
NSMutableSet<NSString *> *newRecipientIds = [allContactRecipientIds mutableCopy];
[newRecipientIds minusSet:lastKnownContactPhoneNumbers];
recipientIdsForIntersection = newRecipientIds;
} else {
// Without a list of "last known" contact phone numbers, we'll have to do a full intersection.
isFullIntersection = YES;
}
}
}];
OWSAssert(recipientIdsForIntersection);
if (recipientIdsForIntersection.count < 1) {
DDLogInfo(@"%@ Skipping intersection; no contacts to intersect.", self.logTag);
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
completion(nil);
});
return;
} else if (isFullIntersection) {
DDLogInfo(@"%@ Doing full intersection with %zu contacts.", self.logTag, recipientIdsForIntersection.count);
} else {
DDLogInfo(
@"%@ Doing delta intersection with %zu contacts.", self.logTag, recipientIdsForIntersection.count);
}
[self intersectContacts:recipientIdsForIntersection
retryDelaySeconds:1.0
success:^(NSSet<SignalRecipient *> *registeredRecipients) {
[self markIntersectionAsComplete:allContactRecipientIds isFullIntersection:isFullIntersection];
completion(nil);
}
failure:^(NSError *error) {
completion(error);
}];
});
}
- (void)markIntersectionAsComplete:(NSSet<NSString *> *)recipientIdsForIntersection
isFullIntersection:(BOOL)isFullIntersection
{
OWSAssert(recipientIdsForIntersection.count > 0);
dispatch_async(self.serialQueue, ^{
[self.dbReadConnection readWriteWithBlock:^(YapDatabaseReadWriteTransaction *transaction) {
[transaction setObject:recipientIdsForIntersection
forKey:OWSContactsManagerKeyLastKnownContactPhoneNumbers
inCollection:OWSContactsManagerCollection];
if (isFullIntersection) {
// Don't do a full intersection more often than once every 6 hours.
const NSTimeInterval kMinFullIntersectionInterval = 6 * kHourInterval;
NSDate *nextFullIntersectionDate = [NSDate
dateWithTimeIntervalSince1970:[NSDate new].timeIntervalSince1970 + kMinFullIntersectionInterval];
[transaction setDate:nextFullIntersectionDate
forKey:OWSContactsManagerKeyNextFullIntersectionDate
inCollection:OWSContactsManagerCollection];
}
}];
});
}
- (void)intersectContacts:(NSSet<NSString *> *)recipientIds
retryDelaySeconds:(double)retryDelaySeconds
success:(void (^)(NSSet<SignalRecipient *> *))successParameter
failure:(void (^)(NSError *))failureParameter
{
OWSAssert(recipientIds.count > 0);
OWSAssert(retryDelaySeconds > 0);
OWSAssert(successParameter);
OWSAssert(failureParameter);
void (^success)(NSArray<SignalRecipient *> *) = ^(NSArray<SignalRecipient *> *registeredRecipientIds) {
DDLogInfo(@"%@ Successfully intersected contacts.", self.logTag);
completionBlock(nil);
successParameter([NSSet setWithArray:registeredRecipientIds]);
};
void (^failure)(NSError *error) = ^(NSError *error) {
void (^failure)(NSError *) = ^(NSError *error) {
if ([error.domain isEqualToString:OWSSignalServiceKitErrorDomain]
&& error.code == OWSErrorCodeContactsUpdaterRateLimit) {
DDLogError(@"Contact intersection hit rate limit with error: %@", error);
completionBlock(error);
failureParameter(error);
return;
}
@ -258,12 +380,13 @@ NSString *const OWSContactsManagerSignalAccountsDidChangeNotification
// TODO: Abort if another contact intersection succeeds in the meantime.
dispatch_after(
dispatch_time(DISPATCH_TIME_NOW, (int64_t)(retryDelaySeconds * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
[self intersectContactsWithRetryDelay:retryDelaySeconds * 2 completion:completionBlock];
[self intersectContacts:recipientIds
retryDelaySeconds:retryDelaySeconds * 2.0
success:successParameter
failure:failureParameter];
});
};
[[ContactsUpdater sharedUpdater] updateSignalContactIntersectionWithABContacts:self.allContacts
success:success
failure:failure];
[[ContactsUpdater sharedUpdater] lookupIdentifiers:recipientIds.allObjects success:success failure:failure];
}
- (void)startObserving
@ -284,7 +407,9 @@ NSString *const OWSContactsManagerSignalAccountsDidChangeNotification
[self.avatarCache removeAllImagesForKey:recipientId];
}
- (void)updateWithContacts:(NSArray<Contact *> *)contacts shouldClearStaleCache:(BOOL)shouldClearStaleCache
- (void)updateWithContacts:(NSArray<Contact *> *)contacts
isUserRequested:(BOOL)isUserRequested
shouldClearStaleCache:(BOOL)shouldClearStaleCache
{
dispatch_async(self.serialQueue, ^{
NSMutableDictionary<NSString *, Contact *> *allContactsMap = [NSMutableDictionary new];
@ -305,9 +430,12 @@ NSString *const OWSContactsManagerSignalAccountsDidChangeNotification
[self.avatarCache removeAllImages];
[self intersectContactsWithCompletion:^(NSError *_Nullable error) {
[self buildSignalAccountsAndClearStaleCache:shouldClearStaleCache];
}];
[self intersectContacts:contacts
isUserRequested:isUserRequested
completion:^(NSError *_Nullable error) {
// TODO: Should we do this on error?
[self buildSignalAccountsAndClearStaleCache:shouldClearStaleCache];
}];
});
});
}
@ -927,4 +1055,6 @@ NSString *const OWSContactsManagerSignalAccountsDidChangeNotification
return name;
}
NS_ASSUME_NONNULL_END
@end

@ -6,8 +6,6 @@
NS_ASSUME_NONNULL_BEGIN
@class Contact;
@interface ContactsUpdater : NSObject
+ (instancetype)sharedUpdater;
@ -31,10 +29,6 @@ NS_ASSUME_NONNULL_BEGIN
success:(void (^)(NSArray<SignalRecipient *> *recipients))success
failure:(void (^)(NSError *error))failure;
- (void)updateSignalContactIntersectionWithABContacts:(NSArray<Contact *> *)abContacts
success:(void (^)(void))success
failure:(void (^)(NSError *error))failure;
@end
NS_ASSUME_NONNULL_END

@ -3,7 +3,6 @@
//
#import "ContactsUpdater.h"
#import "Contact.h"
#import "Cryptography.h"
#import "OWSError.h"
#import "OWSPrimaryStorage.h"
@ -79,7 +78,7 @@ NS_ASSUME_NONNULL_BEGIN
[self contactIntersectionWithSet:[NSSet setWithArray:identifiers]
success:^(NSSet<SignalRecipient *> *recipients) {
if (recipients.count > 0) {
success([recipients copy]);
success(recipients.allObjects);
} else {
failure(OWSErrorMakeNoSuchSignalRecipientError());
}
@ -87,36 +86,6 @@ NS_ASSUME_NONNULL_BEGIN
failure:failure];
}
// TODO: Modify this to support delta lookups.
- (void)updateSignalContactIntersectionWithABContacts:(NSArray<Contact *> *)abContacts
success:(void (^)(void))success
failure:(void (^)(NSError *error))failure
{
NSMutableSet<NSString *> *abPhoneNumbers = [NSMutableSet set];
for (Contact *contact in abContacts) {
for (PhoneNumber *phoneNumber in contact.parsedPhoneNumbers) {
[abPhoneNumbers addObject:phoneNumber.toE164];
}
}
NSMutableSet *recipientIds = [NSMutableSet set];
[OWSPrimaryStorage.dbReadConnection readWithBlock:^(YapDatabaseReadTransaction * transaction) {
// TODO: Don't do this.
NSArray *allRecipientKeys = [transaction allKeysInCollection:[SignalRecipient collection]];
[recipientIds addObjectsFromArray:allRecipientKeys];
}];
NSMutableSet<NSString *> *allContacts = [[abPhoneNumbers setByAddingObjectsFromSet:recipientIds] mutableCopy];
[self contactIntersectionWithSet:allContacts
success:^(NSSet<SignalRecipient *> *recipients) {
DDLogInfo(@"%@ successfully intersected contacts.", self.logTag);
success();
}
failure:failure];
}
- (void)contactIntersectionWithSet:(NSSet<NSString *> *)recipientIdsToLookup
success:(void (^)(NSSet<SignalRecipient *> *recipients))success
failure:(void (^)(NSError *error))failure {

@ -159,7 +159,7 @@ NS_ASSUME_NONNULL_BEGIN
SignalRecipient *latest =
[SignalRecipient markRecipientAsRegisteredAndGet:self.recipientId transaction:transaction];
if (![devices isSubsetOfSet:latest.devices.set]) {
if (![devices intersectsSet:latest.devices.set]) {
return;
}
DDLogDebug(@"%@ removing devices: %@, from recipient: %@", self.logTag, devices, latest.recipientId);

@ -36,6 +36,8 @@ NS_ASSUME_NONNULL_BEGIN
- (void)restoreSnapshotOfCollection:(NSString *)collection snapshotFilePath:(NSString *)snapshotFilePath;
#endif
- (void)setDate:(NSDate *)value forKey:(NSString *)key inCollection:(NSString *)collection;
@end
NS_ASSUME_NONNULL_END

@ -149,6 +149,11 @@ NS_ASSUME_NONNULL_BEGIN
}
#endif
- (void)setDate:(NSDate *)value forKey:(NSString *)key inCollection:(NSString *)collection
{
[self setObject:@(value.timeIntervalSince1970) forKey:key inCollection:collection];
}
@end
NS_ASSUME_NONNULL_END

@ -34,6 +34,9 @@ extern const NSTimeInterval kYearInterval;
- (BOOL)isAfterDate:(NSDate *)otherDate;
- (BOOL)isBeforeDate:(NSDate *)otherDate;
- (BOOL)isAfterNow;
- (BOOL)isBeforeNow;
@end
NS_ASSUME_NONNULL_END

@ -45,6 +45,16 @@ const NSTimeInterval kYearInterval = 365 * kDayInterval;
return [self compare:otherDate] == NSOrderedAscending;
}
- (BOOL)isAfterNow
{
return [self isAfterDate:[NSDate new]];
}
- (BOOL)isBeforeNow
{
return [self isBeforeDate:[NSDate new]];
}
@end
NS_ASSUME_NONNULL_END

Loading…
Cancel
Save