From 42dc872c9af1157bf29cc52a647a2f0db5161bfb Mon Sep 17 00:00:00 2001 From: Michael Kirk Date: Wed, 13 Dec 2017 19:08:47 -0500 Subject: [PATCH] use dedicated read connection to pre-populate cache // FREEBIE --- Signal/Signal-Info.plist | 8 +-- Signal/src/AppDelegate.m | 2 + Signal/src/contact/OWSContactsManager.h | 1 + Signal/src/contact/OWSContactsManager.m | 83 ++++++++++++++++++------- 4 files changed, 68 insertions(+), 26 deletions(-) diff --git a/Signal/Signal-Info.plist b/Signal/Signal-Info.plist index e58dc7a95..4cfb71736 100644 --- a/Signal/Signal-Info.plist +++ b/Signal/Signal-Info.plist @@ -38,7 +38,7 @@ CFBundlePackageType APPL CFBundleShortVersionString - 2.19.2 + 2.19.3 CFBundleSignature ???? CFBundleURLTypes @@ -55,7 +55,7 @@ CFBundleVersion - 2.19.2.0 + 2.19.3.0 ITSAppUsesNonExemptEncryption LOGS_EMAIL @@ -101,10 +101,10 @@ Signal uses your contacts to find users you know. We do not store your contacts on the server. NSMicrophoneUsageDescription Signal needs access to your microphone to make and receive phone calls and record voice messages. - NSPhotoLibraryUsageDescription - Signal will let you choose which photos from your library to send. NSPhotoLibraryAddUsageDescription Signal will save photos to your library. + NSPhotoLibraryUsageDescription + Signal will let you choose which photos from your library to send. UIAppFonts dripicons-v2.ttf diff --git a/Signal/src/AppDelegate.m b/Signal/src/AppDelegate.m index d9c475323..9466f34ee 100644 --- a/Signal/src/AppDelegate.m +++ b/Signal/src/AppDelegate.m @@ -858,6 +858,8 @@ static NSString *const kURLHostVerifyPrefix = @"verify"; [AppVersion.instance appLaunchDidComplete]; + [[Environment getCurrent].contactsManager loadSignalAccountsFromCache]; + [self ensureRootViewController]; // If there were any messages in our local queue which we hadn't yet processed. diff --git a/Signal/src/contact/OWSContactsManager.h b/Signal/src/contact/OWSContactsManager.h index aafde01aa..134ec2156 100644 --- a/Signal/src/contact/OWSContactsManager.h +++ b/Signal/src/contact/OWSContactsManager.h @@ -34,6 +34,7 @@ extern NSString *const OWSContactsManagerSignalAccountsDidChangeNotification; @property (atomic, readonly) NSArray *signalAccounts; - (nullable SignalAccount *)signalAccountForRecipientId:(NSString *)recipientId; +- (void)loadSignalAccountsFromCache; #pragma mark - System Contact Fetching // Must call `requestSystemContactsOnce` before accessing this method diff --git a/Signal/src/contact/OWSContactsManager.m b/Signal/src/contact/OWSContactsManager.m index c9e95d8d7..29e531144 100644 --- a/Signal/src/contact/OWSContactsManager.m +++ b/Signal/src/contact/OWSContactsManager.m @@ -29,6 +29,8 @@ NSString *const OWSContactsManagerSignalAccountsDidChangeNotification @property (atomic) NSArray *signalAccounts; @property (atomic) NSDictionary *signalAccountMap; @property (nonatomic, readonly) SystemContactsFetcher *systemContactsFetcher; +@property (nonatomic, readonly) YapDatabaseConnection *dbReadConnection; +@property (nonatomic, readonly) YapDatabaseConnection *dbWriteConnection; @end @@ -42,18 +44,36 @@ NSString *const OWSContactsManagerSignalAccountsDidChangeNotification // 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 = @[]; _systemContactsFetcher = [SystemContactsFetcher new]; _systemContactsFetcher.delegate = self; - + OWSSingletonAssert(); return self; } +- (void)loadSignalAccountsFromCache +{ + __block NSMutableArray *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 // Request contacts access if you haven't asked recently. @@ -111,7 +131,7 @@ NSString *const OWSContactsManagerSignalAccountsDidChangeNotification { void (^success)(void) = ^{ DDLogInfo(@"%@ Successfully intersected contacts.", self.logTag); - [self updateSignalAccounts]; + [self buildSignalAccounts]; }; void (^failure)(NSError *error) = ^(NSError *error) { if ([error.domain isEqualToString:OWSSignalServiceKitErrorDomain] @@ -175,12 +195,12 @@ NSString *const OWSContactsManagerSignalAccountsDidChangeNotification [self intersectContacts]; - [self updateSignalAccounts]; + [self buildSignalAccounts]; }); }); } -- (void)updateSignalAccounts +- (void)buildSignalAccounts { dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ NSMutableDictionary *signalAccountMap = [NSMutableDictionary new]; @@ -191,7 +211,7 @@ NSString *const OWSContactsManagerSignalAccountsDidChangeNotification // in order to avoid database deadlock. NSMutableDictionary *> *contactIdToSignalRecipientsMap = [NSMutableDictionary new]; - [[TSStorageManager sharedManager].dbReadConnection readWithBlock:^(YapDatabaseReadTransaction *transaction) { + [self.dbReadConnection readWithBlock:^(YapDatabaseReadTransaction *transaction) { for (Contact *contact in contacts) { NSArray *signalRecipients = [contact signalRecipientsWithTransaction:transaction]; contactIdToSignalRecipientsMap[contact.uniqueId] = signalRecipients; @@ -212,31 +232,48 @@ NSString *const OWSContactsManagerSignalAccountsDidChangeNotification DDLogDebug(@"Ignoring duplicate contact: %@, %@", signalAccount.recipientId, contact.fullName); continue; } - signalAccountMap[signalAccount.recipientId] = signalAccount; [signalAccounts addObject:signalAccount]; } } - [TSStorageManager.sharedManager.newDatabaseConnection - readWriteWithBlock:^(YapDatabaseReadWriteTransaction *_Nonnull transaction) { - // TODO we can be more efficient here. - // - only save the ones that changed - // - only remove the ones which no longer exist - [transaction removeAllObjectsInCollection:[SignalAccount collection]]; - for (SignalAccount *signalAccount in signalAccounts) { - [signalAccount saveWithTransaction:transaction]; - } - }]; + // Update cached SignalAccounts on disk + [self.dbWriteConnection readWriteWithBlock:^(YapDatabaseReadWriteTransaction *_Nonnull transaction) { + NSArray *allKeys = [transaction allKeysInCollection:[SignalAccount collection]]; + NSMutableSet *orphanedKeys = [NSMutableSet setWithArray:allKeys]; + + 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.signalAccountMap = [signalAccountMap copy]; - self.signalAccounts = [signalAccounts copy]; - - [self.profileManager setContactRecipientIds:signalAccountMap.allKeys]; + [self updateSignalAccounts:signalAccounts]; }); }); } +- (void)updateSignalAccounts:(NSArray *)signalAccounts +{ + AssertIsOnMainThread(); + + NSMutableDictionary *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 { @@ -543,12 +580,14 @@ NSString *const OWSContactsManagerSignalAccountsDidChangeNotification { OWSAssert(recipientId.length > 0); - SignalAccount *signalAccount = 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) { - signalAccount = [SignalAccount fetchObjectWithUniqueID:recipientId]; + [self.dbReadConnection readWithBlock:^(YapDatabaseReadTransaction * _Nonnull transaction) { + signalAccount = [SignalAccount fetchObjectWithUniqueID:recipientId transaction: transaction]; + }]; } return signalAccount;