TM-SGNL-iOS/SignalServiceKit/Contacts/AuthorMergeObserver.swift
TeleMessage developers dde0620daf initial commit
2025-05-03 12:28:28 -07:00

97 lines
3.8 KiB
Swift

//
// Copyright 2023 Signal Messenger, LLC
// SPDX-License-Identifier: AGPL-3.0-only
//
import Foundation
import GRDB
import LibSignalClient
class AuthorMergeObserver: RecipientMergeObserver {
private let authorMergeHelper: AuthorMergeHelper
init(authorMergeHelper: AuthorMergeHelper) {
self.authorMergeHelper = authorMergeHelper
}
func willBreakAssociation(for recipient: SignalRecipient, mightReplaceNonnilPhoneNumber: Bool, tx: DBWriteTransaction) {
guard mightReplaceNonnilPhoneNumber else {
// This is only adding/removing a PNI, so there's nothing to do.
return
}
guard let aciString = recipient.aciString, let phoneNumber = recipient.phoneNumber?.stringValue else {
return
}
guard authorMergeHelper.shouldCleanUp(phoneNumber: phoneNumber, tx: tx) else {
return
}
populateMissingAcis(phoneNumber: phoneNumber, aciString: aciString, tx: tx)
authorMergeHelper.didCleanUp(phoneNumber: phoneNumber, tx: tx)
}
func didLearnAssociation(mergedRecipient: MergedRecipient, tx: DBWriteTransaction) {
// As an performance optimization, don't assign missing ACIs unless we
// must. When we learn an association, we don't need to assign ACIs because
// we can still fetch based on phone number. When we *break* an
// association, we must populate missing values because we'll no longer be
// able to fetch based on the phone number.
// As a further performance optimization, start a low-priority operation to
// assign this ACI now that we know it. Hopefully this will finish before
// the association is broken if and when some other ACI claims the number.
// If it doesn't finish, things won't break, but they will require a slower
// blocking migration instead.
guard
mergedRecipient.newRecipient.aciString != nil,
let phoneNumber = mergedRecipient.newRecipient.phoneNumber?.stringValue
else {
return
}
authorMergeHelper.maybeJustLearnedAci(for: phoneNumber, tx: tx)
}
private func populateMissingAcis(phoneNumber: String, aciString: String, tx: DBWriteTransaction) {
for table in AuthorDatabaseTable.all {
let sql = """
UPDATE "\(table.name)"
SET "\(table.aciColumn)" = ?
WHERE "\(table.phoneNumberColumn)" = ?
AND "\(table.aciColumn)" IS NULL
"""
let arguments: StatementArguments = [aciString, phoneNumber]
SDSDB.shimOnlyBridge(tx).unwrapGrdbWrite.execute(sql: sql, arguments: arguments)
}
SSKEnvironment.shared.modelReadCachesRef.evacuateAllCaches()
}
}
public struct AuthorDatabaseTable {
public let name: String
public let aciColumn: String
public let phoneNumberColumn: String
public static var all: [AuthorDatabaseTable] {
return [
AuthorDatabaseTable(
name: OWSReaction.databaseTableName,
aciColumn: OWSReaction.columnName(.reactorUUID),
phoneNumberColumn: OWSReaction.columnName(.reactorE164)
),
AuthorDatabaseTable(
name: InteractionRecord.databaseTableName,
aciColumn: InteractionRecord.columnName(.authorUUID),
phoneNumberColumn: InteractionRecord.columnName(.authorPhoneNumber)
),
AuthorDatabaseTable(
name: "pending_read_receipts",
aciColumn: "authorUuid",
phoneNumberColumn: "authorPhoneNumber"
),
AuthorDatabaseTable(
name: "pending_viewed_receipts",
aciColumn: "authorUuid",
phoneNumberColumn: "authorPhoneNumber"
)
]
}
}