From e508c664222d99535f7eb417dbbd1c9829ff917b Mon Sep 17 00:00:00 2001 From: Matthew Chen Date: Thu, 2 Jul 2020 16:24:53 -0300 Subject: [PATCH] Index signal recipients; re-index recipients and accounts when profile changes. --- SignalMessaging/utils/FullTextSearcher.swift | 43 +++++++++++++------ .../src/Contacts/SignalRecipient.m | 5 +++ .../Storage/Database/GRDBSchemaMigrator.swift | 30 ++++++++++--- .../src/Storage/FullTextSearchFinder.swift | 18 ++++++++ SignalServiceKit/src/Util/OWSUserProfile.m | 31 +++++++++++++ 5 files changed, 108 insertions(+), 19 deletions(-) diff --git a/SignalMessaging/utils/FullTextSearcher.swift b/SignalMessaging/utils/FullTextSearcher.swift index 69e567415e..970e4065d6 100644 --- a/SignalMessaging/utils/FullTextSearcher.swift +++ b/SignalMessaging/utils/FullTextSearcher.swift @@ -1,5 +1,5 @@ // -// Copyright (c) 2019 Open Whisper Systems. All rights reserved. +// Copyright (c) 2020 Open Whisper Systems. All rights reserved. // import Foundation @@ -257,7 +257,8 @@ public class FullTextSearcher: NSObject { maxResults: UInt = kDefaultMaxResults, transaction: SDSAnyReadTransaction) -> ComposeScreenSearchResultSet { - var signalContacts: [ContactSearchResult] = [] + var signalContactMap = [SignalServiceAddress: ContactSearchResult]() + var signalRecipentResults: [ContactSearchResult] = [] var groups: [GroupSearchResult] = [] var count: UInt = 0 @@ -272,7 +273,16 @@ public class FullTextSearcher: NSObject { switch match { case let signalAccount as SignalAccount: let searchResult = ContactSearchResult(signalAccount: signalAccount, transaction: transaction) - signalContacts.append(searchResult) + assert(signalContactMap[signalAccount.recipientAddress] == nil) + signalContactMap[signalAccount.recipientAddress] = searchResult + case let signalRecipient as SignalRecipient: + guard signalRecipient.devices.count > 0 else { + // Ignore unregistered recipients. + return + } + let signalAccount = SignalAccount(signalRecipient: signalRecipient, contact: nil, multipleAccountLabelText: nil) + let searchResult = ContactSearchResult(signalAccount: signalAccount, transaction: transaction) + signalRecipentResults.append(searchResult) case let groupThread as TSGroupThread: let sortKey = ConversationSortKey(isContactThread: false, creationDate: groupThread.creationDate, @@ -291,19 +301,28 @@ public class FullTextSearcher: NSObject { } } - if matchesNoteToSelf(searchText: searchText, transaction: transaction) { - if !signalContacts.contains(where: { $0.signalAccount.recipientAddress.isLocalAddress }) { - if let localAddress = TSAccountManager.localAddress { - let localAccount = SignalAccount(address: localAddress) - let localResult = ContactSearchResult(signalAccount: localAccount, transaction: transaction) - signalContacts.append(localResult) - } else { - owsFailDebug("localAddress was unexpectedly nil") - } + // Fill in user matches from SignalRecipients, but only if + // we don't already have a SignalAccount for the same user. + for signalRecipentResult in signalRecipentResults { + if signalContactMap[signalRecipentResult.recipientAddress] == nil { + signalContactMap[signalRecipentResult.recipientAddress] = signalRecipentResult } } + if let localAddress = TSAccountManager.localAddress { + if matchesNoteToSelf(searchText: searchText, transaction: transaction) { + if signalContactMap[localAddress] == nil { + let localAccount = SignalAccount(address: localAddress) + let localResult = ContactSearchResult(signalAccount: localAccount, transaction: transaction) + signalContactMap[localAddress] = localResult + } + } + } else { + owsFailDebug("localAddress was unexpectedly nil") + } + // Order contact results by display name. + var signalContacts: [ContactSearchResult] = Array(signalContactMap.values) signalContacts.sort() // Order the conversation and message results in reverse chronological order. diff --git a/SignalServiceKit/src/Contacts/SignalRecipient.m b/SignalServiceKit/src/Contacts/SignalRecipient.m index a476c67c2a..84113b0828 100644 --- a/SignalServiceKit/src/Contacts/SignalRecipient.m +++ b/SignalServiceKit/src/Contacts/SignalRecipient.m @@ -486,6 +486,11 @@ const NSUInteger SignalRecipientSchemaVersion = 1; [self.signalRecipientReadCache didRemoveSignalRecipient:self transaction:transaction]; } ++ (BOOL)shouldBeIndexedForFTS +{ + return YES; +} + @end NS_ASSUME_NONNULL_END diff --git a/SignalServiceKit/src/Storage/Database/GRDBSchemaMigrator.swift b/SignalServiceKit/src/Storage/Database/GRDBSchemaMigrator.swift index 36e89dfa62..387e1c4ba3 100644 --- a/SignalServiceKit/src/Storage/Database/GRDBSchemaMigrator.swift +++ b/SignalServiceKit/src/Storage/Database/GRDBSchemaMigrator.swift @@ -79,6 +79,7 @@ public class GRDBSchemaMigrator: NSObject { case addMarkedUnreadIndexToThread case fixIncorrectIndexes case resetThreadVisibility + case indexSignalRecipients // NOTE: Every time we add a migration id, consider // incrementing grdbSchemaVersionLatest. @@ -302,8 +303,9 @@ public class GRDBSchemaMigrator: NSObject { do { try autoreleasepool { let transaction = GRDBWriteTransaction(database: db) + defer { transaction.finalizeTransaction() } + try dedupeSignalRecipients(transaction: transaction.asAnyWrite) - transaction.finalizeTransaction() } try db.drop(index: "index_signal_recipients_on_recipientPhoneNumber") @@ -600,9 +602,10 @@ public class GRDBSchemaMigrator: NSObject { try db.drop(table: "model_OWSLinkedDeviceReadReceipt") let transaction = GRDBWriteTransaction(database: db) + defer { transaction.finalizeTransaction() } + let viewOnceStore = SDSKeyValueStore(collection: "viewOnceMessages") viewOnceStore.removeAll(transaction: transaction.asAnyWrite) - transaction.finalizeTransaction() } catch { owsFail("Error: \(error)") } @@ -715,6 +718,16 @@ public class GRDBSchemaMigrator: NSObject { } } + migrator.registerMigration(MigrationId.indexSignalRecipients.rawValue) { db in + let transaction = GRDBWriteTransaction(database: db) + defer { transaction.finalizeTransaction() } + + SignalRecipient.anyEnumerate(transaction: transaction.asAnyWrite) { (signalRecipient: SignalRecipient, + _: UnsafeMutablePointer) in + GRDBFullTextSearchFinder.modelWasInserted(model: signalRecipient, transaction: transaction) + } + } + // MARK: - Schema Migration Insertion Point } @@ -726,8 +739,9 @@ public class GRDBSchemaMigrator: NSObject { migrator.registerMigration(MigrationId.dataMigration_populateGalleryItems.rawValue) { db in do { let transaction = GRDBWriteTransaction(database: db) + defer { transaction.finalizeTransaction() } + try createInitialGalleryRecords(transaction: transaction) - transaction.finalizeTransaction() } catch { owsFail("Error: \(error)") } @@ -735,11 +749,12 @@ public class GRDBSchemaMigrator: NSObject { migrator.registerMigration(MigrationId.dataMigration_markOnboardedUsers_v2.rawValue) { db in let transaction = GRDBWriteTransaction(database: db) + defer { transaction.finalizeTransaction() } + if TSAccountManager.sharedInstance().isRegistered(transaction: transaction.asAnyWrite) { Logger.info("marking existing user as onboarded") TSAccountManager.sharedInstance().setIsOnboarded(true, transaction: transaction.asAnyWrite) } - transaction.finalizeTransaction() } migrator.registerMigration(MigrationId.dataMigration_clearLaunchScreenCache.rawValue) { _ in @@ -748,19 +763,20 @@ public class GRDBSchemaMigrator: NSObject { migrator.registerMigration(MigrationId.dataMigration_enableV2RegistrationLockIfNecessary.rawValue) { db in let transaction = GRDBWriteTransaction(database: db) + defer { transaction.finalizeTransaction() } + guard KeyBackupService.hasMasterKey(transaction: transaction.asAnyWrite) else { - transaction.finalizeTransaction() return } OWS2FAManager.keyValueStore().setBool(true, key: OWS2FAManager.isRegistrationLockV2EnabledKey, transaction: transaction.asAnyWrite) - transaction.finalizeTransaction() } migrator.registerMigration(MigrationId.dataMigration_resetStorageServiceData.rawValue) { db in let transaction = GRDBWriteTransaction(database: db) + defer { transaction.finalizeTransaction() } + SSKEnvironment.shared.storageServiceManager.resetLocalData(transaction: transaction.asAnyWrite) - transaction.finalizeTransaction() } migrator.registerMigration(MigrationId.dataMigration_markAllInteractionsAsNotDeleted.rawValue) { db in diff --git a/SignalServiceKit/src/Storage/FullTextSearchFinder.swift b/SignalServiceKit/src/Storage/FullTextSearchFinder.swift index 3ac7900064..6c40b58c59 100644 --- a/SignalServiceKit/src/Storage/FullTextSearchFinder.swift +++ b/SignalServiceKit/src/Storage/FullTextSearchFinder.swift @@ -28,6 +28,15 @@ public class FullTextSearchFinder: NSObject { } } + @objc + public func modelWasUpdatedObjc(model: TSYapDatabaseObject, transaction: SDSAnyWriteTransaction) { + guard let model = model as? SDSModel else { + owsFailDebug("Invalid model.") + return + } + modelWasUpdated(model: model, transaction: transaction) + } + public func modelWasUpdated(model: SDSModel, transaction: SDSAnyWriteTransaction) { assert(type(of: model).shouldBeIndexedForFTS) @@ -342,6 +351,13 @@ class GRDBFullTextSearchFinder: NSObject { return nil } return model + case SignalRecipient.collection(): + guard let model = SignalRecipient.anyFetch(uniqueId: uniqueId, + transaction: transaction.asAnyRead) else { + owsFailDebug("Couldn't load record: \(collection)") + return nil + } + return model default: owsFailDebug("Unexpected record type: \(collection)") return nil @@ -515,6 +531,8 @@ class AnySearchIndexer { return self.messageIndexer.index(message, transaction: transaction) } else if let signalAccount = object as? SignalAccount { return self.recipientIndexer.index(signalAccount.recipientAddress, transaction: transaction) + } else if let signalRecipient = object as? SignalRecipient { + return self.recipientIndexer.index(signalRecipient.address, transaction: transaction) } else { return nil } diff --git a/SignalServiceKit/src/Util/OWSUserProfile.m b/SignalServiceKit/src/Util/OWSUserProfile.m index 158b8ce5e5..34cc143d49 100644 --- a/SignalServiceKit/src/Util/OWSUserProfile.m +++ b/SignalServiceKit/src/Util/OWSUserProfile.m @@ -771,6 +771,8 @@ NSUInteger const kUserProfileSchemaVersion = 1; { [super anyDidInsertWithTransaction:transaction]; + [self reindexAssociatedModels:transaction]; + [self.userProfileReadCache didInsertOrUpdateUserProfile:self transaction:transaction]; } @@ -778,6 +780,8 @@ NSUInteger const kUserProfileSchemaVersion = 1; { [super anyDidUpdateWithTransaction:transaction]; + [self reindexAssociatedModels:transaction]; + [self.userProfileReadCache didInsertOrUpdateUserProfile:self transaction:transaction]; } @@ -788,6 +792,33 @@ NSUInteger const kUserProfileSchemaVersion = 1; [self.userProfileReadCache didRemoveUserProfile:self transaction:transaction]; } +- (void)reindexAssociatedModels:(SDSAnyWriteTransaction *)transaction +{ + // The profile can affect how accounts, recipients and contact threads are indexed, so we + // need to re-index them whenever the profile changes. + FullTextSearchFinder *fullTextSearchFinder = [FullTextSearchFinder new]; + + AnySignalAccountFinder *accountFinder = [AnySignalAccountFinder new]; + SignalAccount *_Nullable signalAccount = [accountFinder signalAccountForAddress:self.address + transaction:transaction]; + if (signalAccount != nil) { + [fullTextSearchFinder modelWasUpdatedObjcWithModel:signalAccount transaction:transaction]; + } + + AnySignalRecipientFinder *signalRecipientFinder = [AnySignalRecipientFinder new]; + SignalRecipient *_Nullable signalRecipient = [signalRecipientFinder signalRecipientForAddress:self.address + transaction:transaction]; + if (signalRecipient != nil) { + [fullTextSearchFinder modelWasUpdatedObjcWithModel:signalRecipient transaction:transaction]; + } + + TSContactThread *_Nullable contactThread = [TSContactThread getThreadWithContactAddress:self.address + transaction:transaction]; + if (contactThread != nil) { + [fullTextSearchFinder modelWasUpdatedObjcWithModel:contactThread transaction:transaction]; + } +} + + (void)mergeUserProfilesIfNecessaryForAddress:(SignalServiceAddress *)address transaction:(SDSAnyWriteTransaction *)transaction {