189 lines
6 KiB
Swift
189 lines
6 KiB
Swift
//
|
|
// Copyright 2019 Signal Messenger, LLC
|
|
// SPDX-License-Identifier: AGPL-3.0-only
|
|
//
|
|
|
|
import GRDB
|
|
import LibSignalClient
|
|
|
|
public class SignalAccountFinder: NSObject {
|
|
func signalAccount(
|
|
for address: SignalServiceAddress,
|
|
tx: SDSAnyReadTransaction
|
|
) -> SignalAccount? {
|
|
if
|
|
let serviceId = address.serviceId,
|
|
let serviceIdMatch = signalAccountWhere(
|
|
column: SignalAccount.columnName(.recipientServiceId),
|
|
matches: serviceId.serviceIdUppercaseString,
|
|
tx: tx
|
|
)
|
|
{
|
|
return serviceIdMatch
|
|
}
|
|
if
|
|
let phoneNumber = address.phoneNumber,
|
|
let phoneNumberMatch = signalAccount(for: phoneNumber, tx: tx)
|
|
{
|
|
return phoneNumberMatch
|
|
}
|
|
return nil
|
|
}
|
|
|
|
public func signalAccount(
|
|
for e164: E164,
|
|
tx: SDSAnyReadTransaction
|
|
) -> SignalAccount? {
|
|
return signalAccount(for: e164.stringValue, tx: tx)
|
|
}
|
|
|
|
func signalAccount(
|
|
for phoneNumber: String,
|
|
tx: SDSAnyReadTransaction
|
|
) -> SignalAccount? {
|
|
return signalAccountWhere(
|
|
column: SignalAccount.columnName(.recipientPhoneNumber),
|
|
matches: phoneNumber,
|
|
tx: tx
|
|
)
|
|
}
|
|
|
|
func signalAccounts(
|
|
for addresses: [SignalServiceAddress],
|
|
tx: SDSAnyReadTransaction
|
|
) -> [SignalAccount?] {
|
|
return Refinery<SignalServiceAddress, SignalAccount>(addresses).refine { addresses in
|
|
return signalAccountsForServiceIds(
|
|
addresses.map { $0.serviceId },
|
|
tx: tx
|
|
)
|
|
}.refine { addresses in
|
|
return signalAccountsForPhoneNumbers(
|
|
addresses.map { $0.phoneNumber },
|
|
tx: tx
|
|
)
|
|
}.values
|
|
}
|
|
|
|
func signalAccounts(
|
|
for phoneNumbers: [String],
|
|
tx: SDSAnyReadTransaction
|
|
) -> [SignalAccount?] {
|
|
return signalAccountsForPhoneNumbers(phoneNumbers, tx: tx)
|
|
}
|
|
|
|
private func signalAccountsForServiceIds(
|
|
_ serviceIds: [ServiceId?],
|
|
tx: SDSAnyReadTransaction
|
|
) -> [SignalAccount?] {
|
|
let accounts = signalAccountsWhere(
|
|
column: SignalAccount.columnName(.recipientServiceId),
|
|
anyValueIn: serviceIds.compactMap { $0?.serviceIdUppercaseString },
|
|
tx: tx
|
|
)
|
|
|
|
let index: [ServiceId?: [SignalAccount?]] = Dictionary(grouping: accounts) { $0?.recipientServiceId }
|
|
return serviceIds.map { maybeServiceId -> SignalAccount? in
|
|
guard
|
|
let serviceId = maybeServiceId,
|
|
let accountsForServiceId = index[serviceId],
|
|
let firstAccountForServiceId = accountsForServiceId.first
|
|
else {
|
|
return nil
|
|
}
|
|
|
|
return firstAccountForServiceId
|
|
}
|
|
}
|
|
|
|
private func signalAccountsForPhoneNumbers(
|
|
_ phoneNumbers: [String?],
|
|
tx: SDSAnyReadTransaction
|
|
) -> [SignalAccount?] {
|
|
let accounts = signalAccountsWhere(
|
|
column: SignalAccount.columnName(.recipientPhoneNumber),
|
|
anyValueIn: phoneNumbers.compacted(),
|
|
tx: tx
|
|
)
|
|
|
|
let index: [String?: [SignalAccount?]] = Dictionary(grouping: accounts) { $0?.recipientPhoneNumber }
|
|
return phoneNumbers.map { maybePhoneNumber -> SignalAccount? in
|
|
guard
|
|
let phoneNumber = maybePhoneNumber,
|
|
let accountsForPhoneNumber = index[phoneNumber],
|
|
let firstAccountForPhoneNumber = accountsForPhoneNumber.first
|
|
else {
|
|
return nil
|
|
}
|
|
|
|
return firstAccountForPhoneNumber
|
|
}
|
|
}
|
|
|
|
private func signalAccountsWhere(
|
|
column: String,
|
|
anyValueIn values: [String],
|
|
tx: SDSAnyReadTransaction
|
|
) -> [SignalAccount?] {
|
|
guard !values.isEmpty else {
|
|
return []
|
|
}
|
|
let qms = Array(repeating: "?", count: values.count).joined(separator: ", ")
|
|
let sql = "SELECT * FROM \(SignalAccount.databaseTableName) WHERE \(column) in (\(qms))"
|
|
|
|
/// Why did we use `allSignalAccounts` instead of `SignalAccount.anyFetchAll`?
|
|
/// The reason is that the `SignalAccountReadCache` needs to have
|
|
/// `didReadSignalAccount` called on it for each record we enumerate, and
|
|
/// `SignalAccount.anyEnumerate` has this built in.
|
|
return allSignalAccounts(
|
|
tx: tx,
|
|
sql: sql,
|
|
arguments: StatementArguments(values)
|
|
)
|
|
}
|
|
|
|
private func signalAccountWhere(
|
|
column: String,
|
|
matches matchString: String,
|
|
tx: SDSAnyReadTransaction
|
|
) -> SignalAccount? {
|
|
let sql = "SELECT * FROM \(SignalAccount.databaseTableName) WHERE \(column) = ? LIMIT 1"
|
|
|
|
/// Why did we use `allSignalAccounts` instead of `SignalAccount.anyFetchAll`?
|
|
/// The reason is that the `SignalAccountReadCache` needs to have
|
|
/// `didReadSignalAccount` called on it for each record we enumerate, and
|
|
/// `SignalAccount.anyEnumerate` has this built in.
|
|
return allSignalAccounts(
|
|
tx: tx,
|
|
sql: sql,
|
|
arguments: [matchString]
|
|
).first
|
|
}
|
|
|
|
private func allSignalAccounts(
|
|
tx: SDSAnyReadTransaction,
|
|
sql: String,
|
|
arguments: StatementArguments
|
|
) -> [SignalAccount] {
|
|
var result = [SignalAccount]()
|
|
SignalAccount.anyEnumerate(
|
|
transaction: tx,
|
|
sql: sql,
|
|
arguments: arguments
|
|
) { account, _ in
|
|
result.append(account)
|
|
}
|
|
return result
|
|
}
|
|
|
|
func fetchPhoneNumbers(tx: SDSAnyReadTransaction) throws -> [String] {
|
|
let sql = """
|
|
SELECT \(SignalAccount.columnName(.recipientPhoneNumber)) FROM \(SignalAccount.databaseTableName)
|
|
"""
|
|
do {
|
|
return try String?.fetchAll(tx.unwrapGrdbRead.database, sql: sql).compacted()
|
|
} catch {
|
|
throw error.grdbErrorForLogging
|
|
}
|
|
}
|
|
}
|