Merge tag '2.19.3.0'

This commit is contained in:
Michael Kirk 2017-12-14 11:42:57 -05:00
commit 16448e2a0b
10 changed files with 118 additions and 216 deletions

View File

@ -735,6 +735,8 @@ static NSString *const kURLHostVerifyPrefix = @"verify";
[AppVersion.instance appLaunchDidComplete];
[Environment.current.contactsManager loadSignalAccountsFromCache];
[self ensureRootViewController];
// If there were any messages in our local queue which we hadn't yet processed.
@ -758,7 +760,6 @@ static NSString *const kURLHostVerifyPrefix = @"verify";
[OWSProfileManager.sharedManager fetchLocalUsersProfile];
[[OWSReadReceiptManager sharedManager] prepareCachedValues];
[[Environment current].contactsManager loadLastKnownContactRecipientIds];
}
- (void)registrationStateDidChange

View File

@ -172,7 +172,7 @@ NS_ASSUME_NONNULL_BEGIN
OWSAssert([self.thread isKindOfClass:[TSContactThread class]]);
TSContactThread *contactThread = (TSContactThread *)self.thread;
NSString *recipientId = contactThread.contactIdentifier;
return [self.contactsManager.lastKnownContactRecipientIds containsObject:recipientId];
return [self.contactsManager hasSignalAccountForRecipientId:recipientId];
}
#pragma mark - ContactEditingDelegate

View File

@ -30,33 +30,12 @@ extern NSString *const OWSContactsManagerSignalAccountsDidChangeNotification;
@property (atomic, readonly) NSDictionary<NSString *, Contact *> *allContactsMap;
// signalAccountMap and signalAccounts hold the same data.
// signalAccountMap is for lookup. signalAccounts contains the accounts
// ordered by display order.
@property (atomic, readonly) NSDictionary<NSString *, SignalAccount *> *signalAccountMap;
// order of the signalAccounts array respects the systems contact sorting preference
@property (atomic, readonly) NSArray<SignalAccount *> *signalAccounts;
// This value is cached and is available immediately, before system contacts
// fetch or contacts intersection.
//
// In some cases, its better if our UI reflects these values
// which haven't been updated yet rather than assume that
// we have no contacts until the first contacts intersection
// successfully completes.
//
// This significantly improves the user experience when:
//
// * No contacts intersection has completed because the app has just launched.
// * Contacts intersection can't complete due to an unreliable connection or
// the contacts intersection rate limit.
@property (atomic, readonly) NSArray<NSString *> *lastKnownContactRecipientIds;
- (nullable SignalAccount *)signalAccountForRecipientId:(NSString *)recipientId;
- (BOOL)hasSignalAccountForRecipientId:(NSString *)recipientId;
- (Contact *)getOrBuildContactForPhoneIdentifier:(NSString *)identifier;
- (void)loadLastKnownContactRecipientIds;
- (void)loadSignalAccountsFromCache;
#pragma mark - System Contact Fetching
// Must call `requestSystemContactsOnce` before accessing this method

View File

@ -23,12 +23,6 @@
NSString *const OWSContactsManagerSignalAccountsDidChangeNotification
= @"OWSContactsManagerSignalAccountsDidChangeNotification";
NSString *const kTSStorageManager_AccountDisplayNames = @"kTSStorageManager_AccountDisplayNames";
NSString *const kTSStorageManager_AccountFirstNames = @"kTSStorageManager_AccountFirstNames";
NSString *const kTSStorageManager_AccountLastNames = @"kTSStorageManager_AccountLastNames";
NSString *const kTSStorageManager_OWSContactsManager = @"kTSStorageManager_OWSContactsManager";
NSString *const kTSStorageManager_lastKnownContactRecipientIds = @"lastKnownContactRecipientIds";
@interface OWSContactsManager () <SystemContactsFetcherDelegate>
@property (nonatomic) BOOL isContactsUpdateInFlight;
@ -38,12 +32,9 @@ NSString *const kTSStorageManager_lastKnownContactRecipientIds = @"lastKnownCont
@property (atomic) NSDictionary<NSString *, Contact *> *allContactsMap;
@property (atomic) NSArray<SignalAccount *> *signalAccounts;
@property (atomic) NSDictionary<NSString *, SignalAccount *> *signalAccountMap;
@property (atomic) NSArray<NSString *> *lastKnownContactRecipientIds;
@property (nonatomic, readonly) SystemContactsFetcher *systemContactsFetcher;
@property (atomic) NSDictionary<NSString *, NSString *> *cachedAccountNameMap;
@property (atomic) NSDictionary<NSString *, NSString *> *cachedFirstNameMap;
@property (atomic) NSDictionary<NSString *, NSString *> *cachedLastNameMap;
@property (nonatomic, readonly) YapDatabaseConnection *dbReadConnection;
@property (nonatomic, readonly) YapDatabaseConnection *dbWriteConnection;
@end
@ -58,31 +49,34 @@ NSString *const kTSStorageManager_lastKnownContactRecipientIds = @"lastKnownCont
// TODO: We need to configure the limits of this cache.
_avatarCache = [ImageCache new];
_dbReadConnection = [TSStorageManager sharedManager].newDatabaseConnection;
_dbWriteConnection = [TSStorageManager sharedManager].newDatabaseConnection;
_allContacts = @[];
_allContactsMap = @{};
_signalAccountMap = @{};
_signalAccounts = @[];
_lastKnownContactRecipientIds = @[];
_systemContactsFetcher = [SystemContactsFetcher new];
_systemContactsFetcher.delegate = self;
OWSSingletonAssert();
[self loadCachedDisplayNames];
return self;
}
- (void)loadLastKnownContactRecipientIds
- (void)loadSignalAccountsFromCache
{
[TSStorageManager.sharedManager.newDatabaseConnection readWithBlock:^(
YapDatabaseReadTransaction *_Nonnull transaction) {
NSArray<NSString *> *_Nullable value = [transaction objectForKey:kTSStorageManager_lastKnownContactRecipientIds
inCollection:kTSStorageManager_OWSContactsManager];
if (value) {
self.lastKnownContactRecipientIds = value;
}
__block NSMutableArray<SignalAccount *> *signalAccounts;
[self.dbReadConnection readWithBlock:^(YapDatabaseReadTransaction * _Nonnull transaction) {
signalAccounts = [[NSMutableArray alloc] initWithCapacity:[SignalAccount numberOfKeysInCollectionWithTransaction:transaction]];
[SignalAccount enumerateCollectionObjectsWithTransaction:transaction usingBlock:^(SignalAccount *signalAccount, BOOL * _Nonnull stop) {
[signalAccounts addObject:signalAccount];
}];
}];
[self updateSignalAccounts:signalAccounts];
}
#pragma mark - System Contact Fetching
@ -142,7 +136,7 @@ NSString *const kTSStorageManager_lastKnownContactRecipientIds = @"lastKnownCont
{
void (^success)(void) = ^{
DDLogInfo(@"%@ Successfully intersected contacts.", self.logTag);
[self updateSignalAccounts];
[self buildSignalAccounts];
};
void (^failure)(NSError *error) = ^(NSError *error) {
if ([error.domain isEqualToString:OWSSignalServiceKitErrorDomain]
@ -206,14 +200,12 @@ NSString *const kTSStorageManager_lastKnownContactRecipientIds = @"lastKnownCont
[self intersectContacts];
[self updateSignalAccounts];
[self updateCachedDisplayNames];
[self buildSignalAccounts];
});
});
}
- (void)updateSignalAccounts
- (void)buildSignalAccounts
{
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
NSMutableDictionary<NSString *, SignalAccount *> *signalAccountMap = [NSMutableDictionary new];
@ -224,7 +216,7 @@ NSString *const kTSStorageManager_lastKnownContactRecipientIds = @"lastKnownCont
// in order to avoid database deadlock.
NSMutableDictionary<NSString *, NSArray<SignalRecipient *> *> *contactIdToSignalRecipientsMap =
[NSMutableDictionary new];
[[TSStorageManager sharedManager].dbReadConnection readWithBlock:^(YapDatabaseReadTransaction *transaction) {
[self.dbReadConnection readWithBlock:^(YapDatabaseReadTransaction *transaction) {
for (Contact *contact in contacts) {
NSArray<SignalRecipient *> *signalRecipients = [contact signalRecipientsWithTransaction:transaction];
contactIdToSignalRecipientsMap[contact.uniqueId] = signalRecipients;
@ -246,170 +238,77 @@ NSString *const kTSStorageManager_lastKnownContactRecipientIds = @"lastKnownCont
DDLogDebug(@"Ignoring duplicate contact: %@, %@", signalAccount.recipientId, contact.fullName);
continue;
}
signalAccountMap[signalAccount.recipientId] = signalAccount;
[signalAccounts addObject:signalAccount];
}
}
NSArray<NSString *> *lastKnownContactRecipientIds = [signalAccountMap allKeys];
[TSStorageManager.sharedManager.newDatabaseConnection
readWriteWithBlock:^(YapDatabaseReadWriteTransaction *_Nonnull transaction) {
[transaction setObject:lastKnownContactRecipientIds
forKey:kTSStorageManager_lastKnownContactRecipientIds
inCollection:kTSStorageManager_OWSContactsManager];
}];
// Update cached SignalAccounts on disk
[self.dbWriteConnection readWriteWithBlock:^(YapDatabaseReadWriteTransaction *_Nonnull transaction) {
NSArray<NSString *> *allKeys = [transaction allKeysInCollection:[SignalAccount collection]];
NSMutableSet<NSString *> *orphanedKeys = [NSMutableSet setWithArray:allKeys];
DDLogInfo(@"%@ Saving %lu SignalAccounts", self.logTag, signalAccounts.count);
for (SignalAccount *signalAccount in signalAccounts) {
// TODO only save the ones that changed
[orphanedKeys removeObject:signalAccount.uniqueId];
[signalAccount saveWithTransaction:transaction];
}
if (orphanedKeys.count > 0) {
DDLogInfo(@"%@ Removing %lu orphaned SignalAccounts", self.logTag, (unsigned long)orphanedKeys.count);
[transaction removeObjectsForKeys:orphanedKeys.allObjects inCollection:[SignalAccount collection]];
}
}];
dispatch_async(dispatch_get_main_queue(), ^{
self.lastKnownContactRecipientIds = lastKnownContactRecipientIds;
self.signalAccountMap = [signalAccountMap copy];
self.signalAccounts = [signalAccounts copy];
[self.profileManager setContactRecipientIds:signalAccountMap.allKeys];
[self updateCachedDisplayNames];
[self updateSignalAccounts:signalAccounts];
});
});
}
- (void)updateSignalAccounts:(NSArray<SignalAccount *> *)signalAccounts
{
AssertIsOnMainThread();
NSMutableDictionary<NSString *, SignalAccount *> *signalAccountMap = [NSMutableDictionary new];
for (SignalAccount *signalAccount in signalAccounts) {
signalAccountMap[signalAccount.recipientId] = signalAccount;
}
self.signalAccountMap = [signalAccountMap copy];
self.signalAccounts = [signalAccounts copy];
[self.profileManager setContactRecipientIds:signalAccountMap.allKeys];
}
// TODO dependency inject, avoid circular dependencies.
- (OWSProfileManager *)profileManager
{
return [OWSProfileManager sharedManager];
}
- (void)updateCachedDisplayNames
{
OWSAssert([NSThread isMainThread]);
NSMutableDictionary<NSString *, NSString *> *cachedAccountNameMap = [NSMutableDictionary new];
NSMutableDictionary<NSString *, NSString *> *cachedFirstNameMap = [NSMutableDictionary new];
NSMutableDictionary<NSString *, NSString *> *cachedLastNameMap = [NSMutableDictionary new];
for (SignalAccount *signalAccount in self.signalAccounts) {
NSString *baseName
= (signalAccount.contact.fullName.length > 0 ? signalAccount.contact.fullName : signalAccount.recipientId);
OWSAssert(signalAccount.hasMultipleAccountContact == (signalAccount.multipleAccountLabelText != nil));
NSString *displayName = (signalAccount.multipleAccountLabelText
? [NSString stringWithFormat:@"%@ (%@)", baseName, signalAccount.multipleAccountLabelText]
: baseName);
if (![displayName isEqualToString:signalAccount.recipientId]) {
cachedAccountNameMap[signalAccount.recipientId] = displayName;
}
if (signalAccount.contact.firstName.length > 0) {
cachedFirstNameMap[signalAccount.recipientId] = signalAccount.contact.firstName;
}
if (signalAccount.contact.lastName.length > 0) {
cachedLastNameMap[signalAccount.recipientId] = signalAccount.contact.lastName;
}
}
// As a fallback, make sure we can also display names for not-yet-registered
// and no-longer-registered users.
for (Contact *contact in self.allContacts) {
NSString *displayName = contact.fullName;
if (displayName.length > 0) {
for (PhoneNumber *phoneNumber in contact.parsedPhoneNumbers) {
NSString *e164 = phoneNumber.toE164;
if (!cachedAccountNameMap[e164]) {
cachedAccountNameMap[e164] = displayName;
}
}
}
}
self.cachedAccountNameMap = [cachedAccountNameMap copy];
self.cachedFirstNameMap = [cachedFirstNameMap copy];
self.cachedLastNameMap = [cachedLastNameMap copy];
// Write to database off the main thread.
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
[TSStorageManager.sharedManager.newDatabaseConnection readWriteWithBlock:^(
YapDatabaseReadWriteTransaction *_Nonnull transaction) {
for (NSString *recipientId in cachedAccountNameMap) {
NSString *displayName = cachedAccountNameMap[recipientId];
[transaction setObject:displayName
forKey:recipientId
inCollection:kTSStorageManager_AccountDisplayNames];
}
for (NSString *recipientId in cachedFirstNameMap) {
NSString *firstName = cachedFirstNameMap[recipientId];
[transaction setObject:firstName forKey:recipientId inCollection:kTSStorageManager_AccountFirstNames];
}
for (NSString *recipientId in cachedLastNameMap) {
NSString *lastName = cachedLastNameMap[recipientId];
[transaction setObject:lastName forKey:recipientId inCollection:kTSStorageManager_AccountLastNames];
}
}];
});
[[NSNotificationCenter defaultCenter]
postNotificationNameAsync:OWSContactsManagerSignalAccountsDidChangeNotification
object:nil];
}
- (void)loadCachedDisplayNames
{
// Read from database off the main thread.
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
NSMutableDictionary<NSString *, NSString *> *cachedAccountNameMap = [NSMutableDictionary new];
NSMutableDictionary<NSString *, NSString *> *cachedFirstNameMap = [NSMutableDictionary new];
NSMutableDictionary<NSString *, NSString *> *cachedLastNameMap = [NSMutableDictionary new];
[TSStorageManager.sharedManager.newDatabaseConnection readWithBlock:^(
YapDatabaseReadTransaction *_Nonnull transaction) {
[transaction
enumerateKeysAndObjectsInCollection:kTSStorageManager_AccountDisplayNames
usingBlock:^(
NSString *_Nonnull key, NSString *_Nonnull object, BOOL *_Nonnull stop) {
cachedAccountNameMap[key] = object;
}];
[transaction
enumerateKeysAndObjectsInCollection:kTSStorageManager_AccountFirstNames
usingBlock:^(
NSString *_Nonnull key, NSString *_Nonnull object, BOOL *_Nonnull stop) {
cachedFirstNameMap[key] = object;
}];
[transaction
enumerateKeysAndObjectsInCollection:kTSStorageManager_AccountLastNames
usingBlock:^(
NSString *_Nonnull key, NSString *_Nonnull object, BOOL *_Nonnull stop) {
cachedLastNameMap[key] = object;
}];
}];
if (self.cachedAccountNameMap || self.cachedFirstNameMap || self.cachedLastNameMap) {
// If these properties have already been populated from system contacts,
// don't overwrite. In practice this should never happen.
OWSFail(@"%@ Unexpected cache state", self.logTag);
return;
}
self.cachedAccountNameMap = [cachedAccountNameMap copy];
self.cachedFirstNameMap = [cachedFirstNameMap copy];
self.cachedLastNameMap = [cachedLastNameMap copy];
});
}
- (NSString *_Nullable)cachedDisplayNameForRecipientId:(NSString *)recipientId
{
OWSAssert(recipientId.length > 0);
return self.cachedAccountNameMap[recipientId];
SignalAccount *_Nullable signalAccount = [self signalAccountForRecipientId:recipientId];
return signalAccount.displayName;
}
- (NSString *_Nullable)cachedFirstNameForRecipientId:(NSString *)recipientId
{
OWSAssert(recipientId.length > 0);
return self.cachedFirstNameMap[recipientId];
SignalAccount *_Nullable signalAccount = [self signalAccountForRecipientId:recipientId];
return signalAccount.contact.firstName;
}
- (NSString *_Nullable)cachedLastNameForRecipientId:(NSString *)recipientId
{
OWSAssert(recipientId.length > 0);
return self.cachedLastNameMap[recipientId];
SignalAccount *_Nullable signalAccount = [self signalAccountForRecipientId:recipientId];
return signalAccount.contact.lastName;
}
#pragma mark - View Helpers
@ -689,26 +588,30 @@ NSString *const kTSStorageManager_lastKnownContactRecipientIds = @"lastKnownCont
{
OWSAssert(recipientId.length > 0);
return self.signalAccountMap[recipientId];
__block SignalAccount *signalAccount = self.signalAccountMap[recipientId];
// If contact intersection hasn't completed, it might exist on disk
// even if it doesn't exist in memory yet.
if (!signalAccount) {
[self.dbReadConnection readWithBlock:^(YapDatabaseReadTransaction * _Nonnull transaction) {
signalAccount = [SignalAccount fetchObjectWithUniqueID:recipientId transaction: transaction];
}];
}
return signalAccount;
}
- (Contact *)getOrBuildContactForPhoneIdentifier:(NSString *)identifier
- (BOOL)hasSignalAccountForRecipientId:(NSString *)recipientId
{
Contact *savedContact = self.allContactsMap[identifier];
if (savedContact) {
return savedContact;
} else {
return [[Contact alloc] initWithContactWithFirstName:self.unknownContactName
andLastName:nil
andUserTextPhoneNumbers:@[ identifier ]
andImage:nil
andContactID:0];
}
return [self signalAccountForRecipientId:recipientId] != nil;
}
- (UIImage *_Nullable)imageForPhoneIdentifier:(NSString *_Nullable)identifier
{
Contact *contact = self.allContactsMap[identifier];
if (!contact) {
contact = [self signalAccountForRecipientId:identifier].contact;
}
// Prefer the contact image from the local address book if available
UIImage *_Nullable image = contact.image;

View File

@ -488,6 +488,7 @@ public class SystemContactsFetcher: NSObject {
completion(nil)
}
Logger.info("\(self.TAG) fetched \(contacts.count) contacts.")
let contactsHash = HashableArray(contacts).hashValue
DispatchQueue.main.async {

View File

@ -391,8 +391,7 @@ NS_ASSUME_NONNULL_BEGIN
shouldHaveAddToProfileWhitelistOffer = NO;
}
BOOL isContact = [contactsManager.lastKnownContactRecipientIds containsObject:recipientId];
if (isContact) {
if ([contactsManager hasSignalAccountForRecipientId:recipientId]) {
// Only create "add to contacts" offers for non-contacts.
shouldHaveAddToContactsOffer = NO;
// Only create block offers for non-contacts.

View File

@ -3,6 +3,7 @@
//
#import <AddressBook/AddressBook.h>
#import <Mantle/MTLModel.h>
NS_ASSUME_NONNULL_BEGIN
@ -19,7 +20,7 @@ NS_ASSUME_NONNULL_BEGIN
@class SignalRecipient;
@class YapDatabaseReadTransaction;
@interface Contact : NSObject
@interface Contact : MTLModel
@property (nullable, readonly, nonatomic) NSString *firstName;
@property (nullable, readonly, nonatomic) NSString *lastName;
@ -30,13 +31,13 @@ NS_ASSUME_NONNULL_BEGIN
@property (readonly, nonatomic) NSArray<NSString *> *userTextPhoneNumbers;
@property (readonly, nonatomic) NSArray<NSString *> *emails;
@property (readonly, nonatomic) NSString *uniqueId;
@property (nonatomic, readonly) BOOL isSignalContact;
#if TARGET_OS_IOS
@property (nullable, readonly, nonatomic) UIImage *image;
@property (readonly, nonatomic) ABRecordID recordID;
@property (nullable, nonatomic, readonly) CNContact *cnContact;
#endif // TARGET_OS_IOS
- (BOOL)isSignalContact;
- (NSArray<SignalRecipient *> *)signalRecipientsWithTransaction:(YapDatabaseReadTransaction *)transaction;
// TODO: Remove this method.
- (NSArray<NSString *> *)textSecureIdentifiers;

View File

@ -2,6 +2,8 @@
// Copyright (c) 2017 Open Whisper Systems. All rights reserved.
//
#import "TSYapDatabaseObject.h"
NS_ASSUME_NONNULL_BEGIN
@class Contact;
@ -14,10 +16,7 @@ NS_ASSUME_NONNULL_BEGIN
// multiple instances of SignalAccount.
// * For non-contacts, the contact property will be nil.
//
// New instances of SignalAccount for active accounts are
// created every time we do a contacts intersection (e.g.
// in response to a change to the device contacts).
@interface SignalAccount : NSObject
@interface SignalAccount : TSYapDatabaseObject
// An E164 value identifying the signal account.
//
@ -35,6 +34,8 @@ NS_ASSUME_NONNULL_BEGIN
// this is a label for the account.
@property (nonatomic) NSString *multipleAccountLabelText;
- (NSString *)displayName;
- (instancetype)init NS_UNAVAILABLE;
- (instancetype)initWithSignalRecipient:(SignalRecipient *)signalRecipient;

View File

@ -3,6 +3,7 @@
//
#import "SignalAccount.h"
#import "Contact.h"
#import "SignalRecipient.h"
#import "TSStorageManager.h"
@ -20,12 +21,8 @@ NS_ASSUME_NONNULL_BEGIN
- (instancetype)initWithSignalRecipient:(SignalRecipient *)signalRecipient
{
if (self = [super init]) {
OWSAssert(signalRecipient);
_recipientId = signalRecipient.uniqueId;
}
return self;
OWSAssert(signalRecipient);
return [self initWithRecipientId:signalRecipient.recipientId];
}
- (instancetype)initWithRecipientId:(NSString *)recipientId
@ -43,9 +40,27 @@ NS_ASSUME_NONNULL_BEGIN
OWSAssert([NSThread isMainThread]);
OWSAssert(transaction);
OWSAssert(self.recipientId.length > 0);
return [SignalRecipient recipientWithTextSecureIdentifier:self.recipientId withTransaction:transaction];
}
- (nullable NSString *)uniqueId
{
return _recipientId;
}
- (NSString *)displayName
{
NSString *baseName = (self.contact.fullName.length > 0 ? self.contact.fullName : self.recipientId);
OWSAssert(self.hasMultipleAccountContact == (self.multipleAccountLabelText != nil));
NSString *displayName = (self.multipleAccountLabelText
? [NSString stringWithFormat:@"%@ (%@)", baseName, self.multipleAccountLabelText]
: baseName);
return displayName;
}
@end
NS_ASSUME_NONNULL_END

View File

@ -194,21 +194,23 @@ public class ShareViewController: UINavigationController, ShareViewDelegate, SAE
// TODO: Should we distinguish main app and SAE "completion"?
AppVersion.instance().appLaunchDidComplete()
Environment.current().contactsManager.loadSignalAccountsFromCache()
ensureRootViewController()
// We don't need to use OWSMessageReceiver in the SAE.
// We don't need to use OWSBatchMessageProcessor in the SAE.
OWSProfileManager.shared().ensureLocalProfileCached()
// We don't need to use OWSOrphanedDataCleaner in the SAE.
OWSProfileManager.shared().fetchLocalUsersProfile()
OWSReadReceiptManager.shared().prepareCachedValues()
Environment.current().contactsManager.loadLastKnownContactRecipientIds()
}
@objc