From 03e5d2973ba2c03a2c34ce87941ba81f8a5cedc3 Mon Sep 17 00:00:00 2001 From: Matthew Chen Date: Mon, 16 Jul 2018 15:46:48 -0400 Subject: [PATCH 1/5] Delta contact intersections. --- SignalMessaging/contacts/OWSContactsManager.m | 211 ++++++++++++++---- .../src/Contacts/ContactsUpdater.h | 6 - .../src/Contacts/ContactsUpdater.m | 33 +-- .../src/Contacts/SignalRecipient.m | 2 + .../src/Storage/YapDatabaseTransaction+OWS.h | 2 + .../src/Storage/YapDatabaseTransaction+OWS.m | 5 + SignalServiceKit/src/Util/NSDate+OWS.h | 3 + SignalServiceKit/src/Util/NSDate+OWS.mm | 10 + 8 files changed, 192 insertions(+), 80 deletions(-) diff --git a/SignalMessaging/contacts/OWSContactsManager.m b/SignalMessaging/contacts/OWSContactsManager.m index 8c0ac14a7..9e3b43a95 100644 --- a/SignalMessaging/contacts/OWSContactsManager.m +++ b/SignalMessaging/contacts/OWSContactsManager.m @@ -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 () @property (nonatomic) BOOL isContactsUpdateInFlight; @@ -42,6 +49,8 @@ NSString *const OWSContactsManagerSignalAccountsDidChangeNotification @end +#pragma mark - + @implementation OWSContactsManager - (id)init @@ -110,7 +119,7 @@ NSString *const OWSContactsManagerSignalAccountsDidChangeNotification [self requestSystemContactsOnceWithCompletion:nil]; } -- (void)requestSystemContactsOnceWithCompletion:(void (^_Nullable)(NSError *_Nullable error))completion +- (void)requestSystemContactsOnceWithCompletion:(void (^_Nullable)(NSError *error))completion { [self.systemContactsFetcher requestOnceWithCompletion:completion]; } @@ -120,7 +129,7 @@ NSString *const OWSContactsManagerSignalAccountsDidChangeNotification [self.systemContactsFetcher fetchOnceIfAlreadyAuthorized]; } -- (void)userRequestedSystemContactsRefreshWithCompletion:(void (^)(NSError *_Nullable error))completionHandler +- (void)userRequestedSystemContactsRefreshWithCompletion:(void (^)(NSError *error))completionHandler { [self.systemContactsFetcher userRequestedRefreshWithCompletion:completionHandler]; } @@ -155,7 +164,7 @@ NSString *const OWSContactsManagerSignalAccountsDidChangeNotification return nil; } - CNContact *_Nullable cnContact; + CNContact *cnContact; @synchronized(self.cnContactCache) { cnContact = [self.cnContactCache objectForKey:contactId]; if (!cnContact) { @@ -172,7 +181,7 @@ NSString *const OWSContactsManagerSignalAccountsDidChangeNotification - (nullable NSData *)avatarDataForCNContactId:(nullable NSString *)contactId { // Don't bother to cache avatar data. - CNContact *_Nullable cnContact = [self cnContactWithId:contactId]; + CNContact *cnContact = [self cnContactWithId:contactId]; return [Contact avatarDataForCNContact:cnContact]; } @@ -184,11 +193,11 @@ NSString *const OWSContactsManagerSignalAccountsDidChangeNotification return nil; } - UIImage *_Nullable avatarImage; + UIImage *avatarImage; @synchronized(self.cnContactAvatarCache) { avatarImage = [self.cnContactAvatarCache objectForKey:contactId]; if (!avatarImage) { - NSData *_Nullable avatarData = [self avatarDataForCNContactId:contactId]; + NSData *avatarData = [self avatarDataForCNContactId:contactId]; if (avatarData) { avatarImage = [UIImage imageWithData:avatarData]; } @@ -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,139 @@ 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 *)recipientIdsForIntersectionWithContacts:(NSArray *)contacts +{ + OWSAssert(contacts); + + NSMutableSet *recipientIds = [NSMutableSet set]; + + for (Contact *contact in contacts) { + for (PhoneNumber *phoneNumber in contact.parsedPhoneNumbers) { + [recipientIds addObject:phoneNumber.toE164]; + } + } + + return recipientIds; +} + +- (void)intersectContacts:(NSArray *)contacts + isUserRequested:(BOOL)isUserRequested + completion:(void (^)(NSError *_Nullable error))completion +{ + OWSAssert(contacts); + OWSAssert(completion); + + dispatch_async(self.serialQueue, ^{ + __block BOOL isFullIntersection = YES; + __block NSSet *allContactRecipientIds; + __block NSSet *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) { + NSSet *_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 *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 %zd contacts.", self.logTag, recipientIdsForIntersection.count); + } else { + DDLogInfo( + @"%@ Doing delta intersection with %zd contacts.", self.logTag, recipientIdsForIntersection.count); + } + + [self intersectContacts:recipientIdsForIntersection + retryDelaySeconds:1.0 + success:^(NSSet *registeredRecipients) { + [self markIntersectionAsComplete:allContactRecipientIds isFullIntersection:isFullIntersection]; + + completion(nil); + } + failure:^(NSError *error) { + completion(error); + }]; + }); +} + +- (void)markIntersectionAsComplete:(NSSet *)recipientIdsForIntersection + isFullIntersection:(BOOL)isFullIntersection { - [self intersectContactsWithRetryDelay:1 completion:completionBlock]; + 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 kFullIntersectionRate = 6 * kHourInterval; + NSDate *nextFullIntersectionDate = + [NSDate dateWithTimeIntervalSince1970:[NSDate new].timeIntervalSince1970 + kFullIntersectionRate]; + [transaction setDate:nextFullIntersectionDate + forKey:OWSContactsManagerKeyNextFullIntersectionDate + inCollection:OWSContactsManagerCollection]; + } + }]; + }); } -- (void)intersectContactsWithRetryDelay:(double)retryDelaySeconds - completion:(void (^)(NSError *_Nullable error))completionBlock +- (void)intersectContacts:(NSSet *)recipientIds + retryDelaySeconds:(double)retryDelaySeconds + success:(void (^)(NSSet *))successParameter + failure:(void (^)(NSError *))failureParameter { - void (^success)(void) = ^{ + OWSAssert(recipientIds.count > 0); + OWSAssert(retryDelaySeconds > 0); + OWSAssert(successParameter); + OWSAssert(failureParameter); + + void (^success)(NSArray *) = ^(NSArray *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 +377,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 +404,9 @@ NSString *const OWSContactsManagerSignalAccountsDidChangeNotification [self.avatarCache removeAllImagesForKey:recipientId]; } -- (void)updateWithContacts:(NSArray *)contacts shouldClearStaleCache:(BOOL)shouldClearStaleCache +- (void)updateWithContacts:(NSArray *)contacts + isUserRequested:(BOOL)isUserRequested + shouldClearStaleCache:(BOOL)shouldClearStaleCache { dispatch_async(self.serialQueue, ^{ NSMutableDictionary *allContactsMap = [NSMutableDictionary new]; @@ -305,9 +427,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]; + }]; }); }); } @@ -364,7 +489,7 @@ NSString *const OWSContactsManagerSignalAccountsDidChangeNotification NSMutableArray *accountsToSave = [NSMutableArray new]; for (SignalAccount *signalAccount in signalAccounts) { - SignalAccount *_Nullable oldSignalAccount = oldSignalAccounts[signalAccount.uniqueId]; + SignalAccount *oldSignalAccount = oldSignalAccounts[signalAccount.uniqueId]; // keep track of which accounts are still relevant, so we can clean up orphans [oldSignalAccounts removeObjectForKey:signalAccount.uniqueId]; @@ -460,11 +585,11 @@ NSString *const OWSContactsManagerSignalAccountsDidChangeNotification { OWSAssert(recipientId.length > 0); - SignalAccount *_Nullable signalAccount = [self signalAccountForRecipientId:recipientId]; + SignalAccount *signalAccount = [self signalAccountForRecipientId:recipientId]; if (!signalAccount) { // search system contacts for no-longer-registered signal users, for which there will be no SignalAccount DDLogDebug(@"%@ no signal account", self.logTag); - Contact *_Nullable nonSignalContact = self.allContactsMap[recipientId]; + Contact *nonSignalContact = self.allContactsMap[recipientId]; if (!nonSignalContact) { return nil; } @@ -488,7 +613,7 @@ NSString *const OWSContactsManagerSignalAccountsDidChangeNotification { OWSAssert(recipientId.length > 0); - SignalAccount *_Nullable signalAccount = [self signalAccountForRecipientId:recipientId]; + SignalAccount *signalAccount = [self signalAccountForRecipientId:recipientId]; return signalAccount.contact.firstName.filterStringForDisplay; } @@ -496,7 +621,7 @@ NSString *const OWSContactsManagerSignalAccountsDidChangeNotification { OWSAssert(recipientId.length > 0); - SignalAccount *_Nullable signalAccount = [self signalAccountForRecipientId:recipientId]; + SignalAccount *signalAccount = [self signalAccountForRecipientId:recipientId]; return signalAccount.contact.lastName.filterStringForDisplay; } @@ -574,7 +699,7 @@ NSString *const OWSContactsManagerSignalAccountsDidChangeNotification - (nullable NSString *)formattedProfileNameForRecipientId:(NSString *)recipientId { - NSString *_Nullable profileName = [self.profileManager profileNameForRecipientId:recipientId]; + NSString *profileName = [self.profileManager profileNameForRecipientId:recipientId]; if (profileName.length == 0) { return nil; } @@ -601,7 +726,7 @@ NSString *const OWSContactsManagerSignalAccountsDidChangeNotification return self.unknownContactName; } - NSString *_Nullable displayName = [self nameFromSystemContactsForRecipientId:recipientId]; + NSString *displayName = [self nameFromSystemContactsForRecipientId:recipientId]; // Fall back to just using their recipientId if (displayName.length < 1) { @@ -653,8 +778,8 @@ NSString *const OWSContactsManagerSignalAccountsDidChangeNotification NSAttributedString *lastName = [[NSAttributedString alloc] initWithString:cachedLastName attributes:lastNameAttributes]; - NSString *_Nullable cnContactId = self.allContactsMap[recipientId].cnContactId; - CNContact *_Nullable cnContact = [self cnContactWithId:cnContactId]; + NSString *cnContactId = self.allContactsMap[recipientId].cnContactId; + CNContact *cnContact = [self cnContactWithId:cnContactId]; if (!cnContact) { // If we don't have a CNContact for this recipient id, make one. // Presumably [CNContactFormatter nameOrderForContact:] tries @@ -666,7 +791,7 @@ NSString *const OWSContactsManagerSignalAccountsDidChangeNotification cnContact = formatContact; } CNContactDisplayNameOrder nameOrder = [CNContactFormatter nameOrderForContact:cnContact]; - NSAttributedString *_Nullable leftName, *_Nullable rightName; + NSAttributedString *leftName, *rightName; if (nameOrder == CNContactDisplayNameOrderGivenNameFirst) { leftName = firstName; rightName = lastName; @@ -712,12 +837,12 @@ NSString *const OWSContactsManagerSignalAccountsDidChangeNotification - (NSString *)contactOrProfileNameForPhoneIdentifier:(NSString *)recipientId { // Prefer a saved name from system contacts, if available - NSString *_Nullable savedContactName = [self cachedContactNameForRecipientId:recipientId]; + NSString *savedContactName = [self cachedContactNameForRecipientId:recipientId]; if (savedContactName.length > 0) { return savedContactName; } - NSString *_Nullable profileName = [self.profileManager profileNameForRecipientId:recipientId]; + NSString *profileName = [self.profileManager profileNameForRecipientId:recipientId]; if (profileName.length > 0) { NSString *numberAndProfileNameFormat = NSLocalizedString(@"PROFILE_NAME_AND_PHONE_NUMBER_LABEL_FORMAT", @"Label text combining the phone number and profile name separated by a simple demarcation character. " @@ -762,12 +887,12 @@ NSString *const OWSContactsManagerSignalAccountsDidChangeNotification OWSAssert(secondaryAttributes.count > 0); // Prefer a saved name from system contacts, if available - NSString *_Nullable savedContactName = [self cachedContactNameForRecipientId:recipientId]; + NSString *savedContactName = [self cachedContactNameForRecipientId:recipientId]; if (savedContactName.length > 0) { return [[NSAttributedString alloc] initWithString:savedContactName attributes:primaryAttributes]; } - NSString *_Nullable profileName = [self.profileManager profileNameForRecipientId:recipientId]; + NSString *profileName = [self.profileManager profileNameForRecipientId:recipientId]; if (profileName.length > 0) { NSAttributedString *result = [[NSAttributedString alloc] initWithString:recipientId attributes:primaryAttributes]; @@ -786,14 +911,14 @@ NSString *const OWSContactsManagerSignalAccountsDidChangeNotification - (NSString *)stringForConversationTitleWithPhoneIdentifier:(NSString *)recipientId { // Prefer a saved name from system contacts, if available - NSString *_Nullable savedContactName = [self cachedContactNameForRecipientId:recipientId]; + NSString *savedContactName = [self cachedContactNameForRecipientId:recipientId]; if (savedContactName.length > 0) { return savedContactName; } NSString *formattedPhoneNumber = [PhoneNumber bestEffortFormatPartialUserSpecifiedTextToLookLikeAPhoneNumber:recipientId]; - NSString *_Nullable profileName = [self.profileManager profileNameForRecipientId:recipientId]; + NSString *profileName = [self.profileManager profileNameForRecipientId:recipientId]; if (profileName.length > 0) { NSString *numberAndProfileNameFormat = NSLocalizedString(@"PROFILE_NAME_AND_PHONE_NUMBER_LABEL_FORMAT", @"Label text combining the phone number and profile name separated by a simple demarcation character. " @@ -874,7 +999,7 @@ NSString *const OWSContactsManagerSignalAccountsDidChangeNotification } // Prefer the contact image from the local address book if available - UIImage *_Nullable image = [self systemContactImageForPhoneIdentifier:identifier]; + UIImage *image = [self systemContactImageForPhoneIdentifier:identifier]; // Else try to use the image from their profile if (image == nil) { @@ -911,7 +1036,7 @@ NSString *const OWSContactsManagerSignalAccountsDidChangeNotification - (NSString *)comparableNameForSignalAccount:(SignalAccount *)signalAccount { - NSString *_Nullable name; + NSString *name; if (signalAccount.contact) { if (self.shouldSortByGivenName) { name = signalAccount.contact.comparableNameFirstLast; @@ -927,4 +1052,6 @@ NSString *const OWSContactsManagerSignalAccountsDidChangeNotification return name; } +NS_ASSUME_NONNULL_END + @end diff --git a/SignalServiceKit/src/Contacts/ContactsUpdater.h b/SignalServiceKit/src/Contacts/ContactsUpdater.h index a686db178..714f9c04b 100644 --- a/SignalServiceKit/src/Contacts/ContactsUpdater.h +++ b/SignalServiceKit/src/Contacts/ContactsUpdater.h @@ -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 *recipients))success failure:(void (^)(NSError *error))failure; -- (void)updateSignalContactIntersectionWithABContacts:(NSArray *)abContacts - success:(void (^)(void))success - failure:(void (^)(NSError *error))failure; - @end NS_ASSUME_NONNULL_END diff --git a/SignalServiceKit/src/Contacts/ContactsUpdater.m b/SignalServiceKit/src/Contacts/ContactsUpdater.m index cdd883651..dfd0d69c0 100644 --- a/SignalServiceKit/src/Contacts/ContactsUpdater.m +++ b/SignalServiceKit/src/Contacts/ContactsUpdater.m @@ -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 *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 *)abContacts - success:(void (^)(void))success - failure:(void (^)(NSError *error))failure -{ - NSMutableSet *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 *allContacts = [[abPhoneNumbers setByAddingObjectsFromSet:recipientIds] mutableCopy]; - - [self contactIntersectionWithSet:allContacts - success:^(NSSet *recipients) { - DDLogInfo(@"%@ successfully intersected contacts.", self.logTag); - success(); - } - failure:failure]; -} - - (void)contactIntersectionWithSet:(NSSet *)recipientIdsToLookup success:(void (^)(NSSet *recipients))success failure:(void (^)(NSError *error))failure { diff --git a/SignalServiceKit/src/Contacts/SignalRecipient.m b/SignalServiceKit/src/Contacts/SignalRecipient.m index 2790e59ab..93d228f3b 100644 --- a/SignalServiceKit/src/Contacts/SignalRecipient.m +++ b/SignalServiceKit/src/Contacts/SignalRecipient.m @@ -3,7 +3,9 @@ // #import "SignalRecipient.h" +#import "PhoneNumber.h" #import "TSAccountManager.h" +#import "YapDatabaseTransaction+OWS.h" #import NS_ASSUME_NONNULL_BEGIN diff --git a/SignalServiceKit/src/Storage/YapDatabaseTransaction+OWS.h b/SignalServiceKit/src/Storage/YapDatabaseTransaction+OWS.h index db9581683..3a98dad0f 100644 --- a/SignalServiceKit/src/Storage/YapDatabaseTransaction+OWS.h +++ b/SignalServiceKit/src/Storage/YapDatabaseTransaction+OWS.h @@ -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 diff --git a/SignalServiceKit/src/Storage/YapDatabaseTransaction+OWS.m b/SignalServiceKit/src/Storage/YapDatabaseTransaction+OWS.m index 63fdeeaaa..e3e638e61 100644 --- a/SignalServiceKit/src/Storage/YapDatabaseTransaction+OWS.m +++ b/SignalServiceKit/src/Storage/YapDatabaseTransaction+OWS.m @@ -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 diff --git a/SignalServiceKit/src/Util/NSDate+OWS.h b/SignalServiceKit/src/Util/NSDate+OWS.h index 024924a9b..10167abad 100755 --- a/SignalServiceKit/src/Util/NSDate+OWS.h +++ b/SignalServiceKit/src/Util/NSDate+OWS.h @@ -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 diff --git a/SignalServiceKit/src/Util/NSDate+OWS.mm b/SignalServiceKit/src/Util/NSDate+OWS.mm index f6238a7e0..2e81a44e0 100644 --- a/SignalServiceKit/src/Util/NSDate+OWS.mm +++ b/SignalServiceKit/src/Util/NSDate+OWS.mm @@ -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 From bf1642052af7c0f211d60ce41ecf7433bd4aa140 Mon Sep 17 00:00:00 2001 From: Matthew Chen Date: Tue, 17 Jul 2018 11:36:21 -0400 Subject: [PATCH 2/5] Fix nullability. --- SignalMessaging/contacts/OWSContactsManager.m | 48 +++++++++---------- 1 file changed, 24 insertions(+), 24 deletions(-) diff --git a/SignalMessaging/contacts/OWSContactsManager.m b/SignalMessaging/contacts/OWSContactsManager.m index 9e3b43a95..4a9869b33 100644 --- a/SignalMessaging/contacts/OWSContactsManager.m +++ b/SignalMessaging/contacts/OWSContactsManager.m @@ -119,7 +119,7 @@ NSString *const OWSContactsManagerKeyNextFullIntersectionDate = @"OWSContactsMan [self requestSystemContactsOnceWithCompletion:nil]; } -- (void)requestSystemContactsOnceWithCompletion:(void (^_Nullable)(NSError *error))completion +- (void)requestSystemContactsOnceWithCompletion:(void (^_Nullable)(NSError *_Nullable error))completion { [self.systemContactsFetcher requestOnceWithCompletion:completion]; } @@ -129,7 +129,7 @@ NSString *const OWSContactsManagerKeyNextFullIntersectionDate = @"OWSContactsMan [self.systemContactsFetcher fetchOnceIfAlreadyAuthorized]; } -- (void)userRequestedSystemContactsRefreshWithCompletion:(void (^)(NSError *error))completionHandler +- (void)userRequestedSystemContactsRefreshWithCompletion:(void (^)(NSError *_Nullable error))completionHandler { [self.systemContactsFetcher userRequestedRefreshWithCompletion:completionHandler]; } @@ -164,7 +164,7 @@ NSString *const OWSContactsManagerKeyNextFullIntersectionDate = @"OWSContactsMan return nil; } - CNContact *cnContact; + CNContact *_Nullable cnContact; @synchronized(self.cnContactCache) { cnContact = [self.cnContactCache objectForKey:contactId]; if (!cnContact) { @@ -181,7 +181,7 @@ NSString *const OWSContactsManagerKeyNextFullIntersectionDate = @"OWSContactsMan - (nullable NSData *)avatarDataForCNContactId:(nullable NSString *)contactId { // Don't bother to cache avatar data. - CNContact *cnContact = [self cnContactWithId:contactId]; + CNContact *_Nullable cnContact = [self cnContactWithId:contactId]; return [Contact avatarDataForCNContact:cnContact]; } @@ -193,11 +193,11 @@ NSString *const OWSContactsManagerKeyNextFullIntersectionDate = @"OWSContactsMan return nil; } - UIImage *avatarImage; + UIImage *_Nullable avatarImage; @synchronized(self.cnContactAvatarCache) { avatarImage = [self.cnContactAvatarCache objectForKey:contactId]; if (!avatarImage) { - NSData *avatarData = [self avatarDataForCNContactId:contactId]; + NSData *_Nullable avatarData = [self avatarDataForCNContactId:contactId]; if (avatarData) { avatarImage = [UIImage imageWithData:avatarData]; } @@ -489,7 +489,7 @@ NSString *const OWSContactsManagerKeyNextFullIntersectionDate = @"OWSContactsMan NSMutableArray *accountsToSave = [NSMutableArray new]; for (SignalAccount *signalAccount in signalAccounts) { - SignalAccount *oldSignalAccount = oldSignalAccounts[signalAccount.uniqueId]; + SignalAccount *_Nullable oldSignalAccount = oldSignalAccounts[signalAccount.uniqueId]; // keep track of which accounts are still relevant, so we can clean up orphans [oldSignalAccounts removeObjectForKey:signalAccount.uniqueId]; @@ -585,11 +585,11 @@ NSString *const OWSContactsManagerKeyNextFullIntersectionDate = @"OWSContactsMan { OWSAssert(recipientId.length > 0); - SignalAccount *signalAccount = [self signalAccountForRecipientId:recipientId]; + SignalAccount *_Nullable signalAccount = [self signalAccountForRecipientId:recipientId]; if (!signalAccount) { // search system contacts for no-longer-registered signal users, for which there will be no SignalAccount DDLogDebug(@"%@ no signal account", self.logTag); - Contact *nonSignalContact = self.allContactsMap[recipientId]; + Contact *_Nullable nonSignalContact = self.allContactsMap[recipientId]; if (!nonSignalContact) { return nil; } @@ -613,7 +613,7 @@ NSString *const OWSContactsManagerKeyNextFullIntersectionDate = @"OWSContactsMan { OWSAssert(recipientId.length > 0); - SignalAccount *signalAccount = [self signalAccountForRecipientId:recipientId]; + SignalAccount *_Nullable signalAccount = [self signalAccountForRecipientId:recipientId]; return signalAccount.contact.firstName.filterStringForDisplay; } @@ -621,7 +621,7 @@ NSString *const OWSContactsManagerKeyNextFullIntersectionDate = @"OWSContactsMan { OWSAssert(recipientId.length > 0); - SignalAccount *signalAccount = [self signalAccountForRecipientId:recipientId]; + SignalAccount *_Nullable signalAccount = [self signalAccountForRecipientId:recipientId]; return signalAccount.contact.lastName.filterStringForDisplay; } @@ -699,7 +699,7 @@ NSString *const OWSContactsManagerKeyNextFullIntersectionDate = @"OWSContactsMan - (nullable NSString *)formattedProfileNameForRecipientId:(NSString *)recipientId { - NSString *profileName = [self.profileManager profileNameForRecipientId:recipientId]; + NSString *_Nullable profileName = [self.profileManager profileNameForRecipientId:recipientId]; if (profileName.length == 0) { return nil; } @@ -726,7 +726,7 @@ NSString *const OWSContactsManagerKeyNextFullIntersectionDate = @"OWSContactsMan return self.unknownContactName; } - NSString *displayName = [self nameFromSystemContactsForRecipientId:recipientId]; + NSString *_Nullable displayName = [self nameFromSystemContactsForRecipientId:recipientId]; // Fall back to just using their recipientId if (displayName.length < 1) { @@ -778,8 +778,8 @@ NSString *const OWSContactsManagerKeyNextFullIntersectionDate = @"OWSContactsMan NSAttributedString *lastName = [[NSAttributedString alloc] initWithString:cachedLastName attributes:lastNameAttributes]; - NSString *cnContactId = self.allContactsMap[recipientId].cnContactId; - CNContact *cnContact = [self cnContactWithId:cnContactId]; + NSString *_Nullable cnContactId = self.allContactsMap[recipientId].cnContactId; + CNContact *_Nullable cnContact = [self cnContactWithId:cnContactId]; if (!cnContact) { // If we don't have a CNContact for this recipient id, make one. // Presumably [CNContactFormatter nameOrderForContact:] tries @@ -791,7 +791,7 @@ NSString *const OWSContactsManagerKeyNextFullIntersectionDate = @"OWSContactsMan cnContact = formatContact; } CNContactDisplayNameOrder nameOrder = [CNContactFormatter nameOrderForContact:cnContact]; - NSAttributedString *leftName, *rightName; + NSAttributedString *_Nullable leftName, *_Nullable rightName; if (nameOrder == CNContactDisplayNameOrderGivenNameFirst) { leftName = firstName; rightName = lastName; @@ -837,12 +837,12 @@ NSString *const OWSContactsManagerKeyNextFullIntersectionDate = @"OWSContactsMan - (NSString *)contactOrProfileNameForPhoneIdentifier:(NSString *)recipientId { // Prefer a saved name from system contacts, if available - NSString *savedContactName = [self cachedContactNameForRecipientId:recipientId]; + NSString *_Nullable savedContactName = [self cachedContactNameForRecipientId:recipientId]; if (savedContactName.length > 0) { return savedContactName; } - NSString *profileName = [self.profileManager profileNameForRecipientId:recipientId]; + NSString *_Nullable profileName = [self.profileManager profileNameForRecipientId:recipientId]; if (profileName.length > 0) { NSString *numberAndProfileNameFormat = NSLocalizedString(@"PROFILE_NAME_AND_PHONE_NUMBER_LABEL_FORMAT", @"Label text combining the phone number and profile name separated by a simple demarcation character. " @@ -887,12 +887,12 @@ NSString *const OWSContactsManagerKeyNextFullIntersectionDate = @"OWSContactsMan OWSAssert(secondaryAttributes.count > 0); // Prefer a saved name from system contacts, if available - NSString *savedContactName = [self cachedContactNameForRecipientId:recipientId]; + NSString *_Nullable savedContactName = [self cachedContactNameForRecipientId:recipientId]; if (savedContactName.length > 0) { return [[NSAttributedString alloc] initWithString:savedContactName attributes:primaryAttributes]; } - NSString *profileName = [self.profileManager profileNameForRecipientId:recipientId]; + NSString *_Nullable profileName = [self.profileManager profileNameForRecipientId:recipientId]; if (profileName.length > 0) { NSAttributedString *result = [[NSAttributedString alloc] initWithString:recipientId attributes:primaryAttributes]; @@ -911,14 +911,14 @@ NSString *const OWSContactsManagerKeyNextFullIntersectionDate = @"OWSContactsMan - (NSString *)stringForConversationTitleWithPhoneIdentifier:(NSString *)recipientId { // Prefer a saved name from system contacts, if available - NSString *savedContactName = [self cachedContactNameForRecipientId:recipientId]; + NSString *_Nullable savedContactName = [self cachedContactNameForRecipientId:recipientId]; if (savedContactName.length > 0) { return savedContactName; } NSString *formattedPhoneNumber = [PhoneNumber bestEffortFormatPartialUserSpecifiedTextToLookLikeAPhoneNumber:recipientId]; - NSString *profileName = [self.profileManager profileNameForRecipientId:recipientId]; + NSString *_Nullable profileName = [self.profileManager profileNameForRecipientId:recipientId]; if (profileName.length > 0) { NSString *numberAndProfileNameFormat = NSLocalizedString(@"PROFILE_NAME_AND_PHONE_NUMBER_LABEL_FORMAT", @"Label text combining the phone number and profile name separated by a simple demarcation character. " @@ -999,7 +999,7 @@ NSString *const OWSContactsManagerKeyNextFullIntersectionDate = @"OWSContactsMan } // Prefer the contact image from the local address book if available - UIImage *image = [self systemContactImageForPhoneIdentifier:identifier]; + UIImage *_Nullable image = [self systemContactImageForPhoneIdentifier:identifier]; // Else try to use the image from their profile if (image == nil) { @@ -1036,7 +1036,7 @@ NSString *const OWSContactsManagerKeyNextFullIntersectionDate = @"OWSContactsMan - (NSString *)comparableNameForSignalAccount:(SignalAccount *)signalAccount { - NSString *name; + NSString *_Nullable name; if (signalAccount.contact) { if (self.shouldSortByGivenName) { name = signalAccount.contact.comparableNameFirstLast; From 3c3742aae9795c0fc1e525cf64ea71e1c020e892 Mon Sep 17 00:00:00 2001 From: Matthew Chen Date: Tue, 17 Jul 2018 11:40:34 -0400 Subject: [PATCH 3/5] Clean up ahead of PR. --- SignalMessaging/contacts/OWSContactsManager.m | 3 +++ 1 file changed, 3 insertions(+) diff --git a/SignalMessaging/contacts/OWSContactsManager.m b/SignalMessaging/contacts/OWSContactsManager.m index 4a9869b33..cc973ca4c 100644 --- a/SignalMessaging/contacts/OWSContactsManager.m +++ b/SignalMessaging/contacts/OWSContactsManager.m @@ -281,6 +281,9 @@ NSString *const OWSContactsManagerKeyNextFullIntersectionDate = @"OWSContactsMan 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 *_Nullable lastKnownContactPhoneNumbers = [transaction objectForKey:OWSContactsManagerKeyLastKnownContactPhoneNumbers inCollection:OWSContactsManagerCollection]; From 3aa28aee365a91a9ca09d880e8d842c7104953f4 Mon Sep 17 00:00:00 2001 From: Matthew Chen Date: Wed, 18 Jul 2018 15:09:29 -0400 Subject: [PATCH 4/5] Respond to CR. --- SignalServiceKit/src/Contacts/SignalRecipient.m | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/SignalServiceKit/src/Contacts/SignalRecipient.m b/SignalServiceKit/src/Contacts/SignalRecipient.m index 93d228f3b..4bacea3e9 100644 --- a/SignalServiceKit/src/Contacts/SignalRecipient.m +++ b/SignalServiceKit/src/Contacts/SignalRecipient.m @@ -161,7 +161,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); From 39c7fd9f15caa91caac4ba568eaffef38bac0612 Mon Sep 17 00:00:00 2001 From: Matthew Chen Date: Thu, 19 Jul 2018 11:31:04 -0400 Subject: [PATCH 5/5] Respond to CR. --- SignalMessaging/contacts/OWSContactsManager.m | 10 +++++----- SignalServiceKit/src/Contacts/SignalRecipient.m | 2 -- 2 files changed, 5 insertions(+), 7 deletions(-) diff --git a/SignalMessaging/contacts/OWSContactsManager.m b/SignalMessaging/contacts/OWSContactsManager.m index cc973ca4c..da1d9864b 100644 --- a/SignalMessaging/contacts/OWSContactsManager.m +++ b/SignalMessaging/contacts/OWSContactsManager.m @@ -308,10 +308,10 @@ NSString *const OWSContactsManagerKeyNextFullIntersectionDate = @"OWSContactsMan }); return; } else if (isFullIntersection) { - DDLogInfo(@"%@ Doing full intersection with %zd contacts.", self.logTag, recipientIdsForIntersection.count); + DDLogInfo(@"%@ Doing full intersection with %zu contacts.", self.logTag, recipientIdsForIntersection.count); } else { DDLogInfo( - @"%@ Doing delta intersection with %zd contacts.", self.logTag, recipientIdsForIntersection.count); + @"%@ Doing delta intersection with %zu contacts.", self.logTag, recipientIdsForIntersection.count); } [self intersectContacts:recipientIdsForIntersection @@ -340,9 +340,9 @@ NSString *const OWSContactsManagerKeyNextFullIntersectionDate = @"OWSContactsMan if (isFullIntersection) { // Don't do a full intersection more often than once every 6 hours. - const NSTimeInterval kFullIntersectionRate = 6 * kHourInterval; - NSDate *nextFullIntersectionDate = - [NSDate dateWithTimeIntervalSince1970:[NSDate new].timeIntervalSince1970 + kFullIntersectionRate]; + const NSTimeInterval kMinFullIntersectionInterval = 6 * kHourInterval; + NSDate *nextFullIntersectionDate = [NSDate + dateWithTimeIntervalSince1970:[NSDate new].timeIntervalSince1970 + kMinFullIntersectionInterval]; [transaction setDate:nextFullIntersectionDate forKey:OWSContactsManagerKeyNextFullIntersectionDate inCollection:OWSContactsManagerCollection]; diff --git a/SignalServiceKit/src/Contacts/SignalRecipient.m b/SignalServiceKit/src/Contacts/SignalRecipient.m index 4bacea3e9..db163a015 100644 --- a/SignalServiceKit/src/Contacts/SignalRecipient.m +++ b/SignalServiceKit/src/Contacts/SignalRecipient.m @@ -3,9 +3,7 @@ // #import "SignalRecipient.h" -#import "PhoneNumber.h" #import "TSAccountManager.h" -#import "YapDatabaseTransaction+OWS.h" #import NS_ASSUME_NONNULL_BEGIN