mirror of
https://github.com/AsamK/signal-cli
synced 2025-08-29 18:40:39 +00:00
Implement full CDSI refresh
This commit is contained in:
parent
7cd24a74af
commit
5c39344cff
6 changed files with 278 additions and 11 deletions
|
@ -14,6 +14,7 @@ import org.whispersystems.signalservice.api.push.ServiceId;
|
||||||
import org.whispersystems.signalservice.api.push.ServiceId.ACI;
|
import org.whispersystems.signalservice.api.push.ServiceId.ACI;
|
||||||
import org.whispersystems.signalservice.api.push.ServiceId.PNI;
|
import org.whispersystems.signalservice.api.push.ServiceId.PNI;
|
||||||
import org.whispersystems.signalservice.api.push.SignalServiceAddress;
|
import org.whispersystems.signalservice.api.push.SignalServiceAddress;
|
||||||
|
import org.whispersystems.signalservice.api.push.exceptions.CdsiInvalidTokenException;
|
||||||
import org.whispersystems.signalservice.api.services.CdsiV2Service;
|
import org.whispersystems.signalservice.api.services.CdsiV2Service;
|
||||||
import org.whispersystems.util.Base64UrlSafe;
|
import org.whispersystems.util.Base64UrlSafe;
|
||||||
|
|
||||||
|
@ -115,6 +116,10 @@ public class RecipientHelper {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void refreshUsers() throws IOException {
|
||||||
|
getRegisteredUsers(account.getRecipientStore().getAllNumbers(), false);
|
||||||
|
}
|
||||||
|
|
||||||
public RecipientId refreshRegisteredUser(RecipientId recipientId) throws IOException, UnregisteredRecipientException {
|
public RecipientId refreshRegisteredUser(RecipientId recipientId) throws IOException, UnregisteredRecipientException {
|
||||||
final var address = resolveSignalServiceAddress(recipientId);
|
final var address = resolveSignalServiceAddress(recipientId);
|
||||||
if (address.getNumber().isEmpty()) {
|
if (address.getNumber().isEmpty()) {
|
||||||
|
@ -126,8 +131,16 @@ public class RecipientHelper {
|
||||||
.resolveRecipientTrusted(new SignalServiceAddress(serviceId, number));
|
.resolveRecipientTrusted(new SignalServiceAddress(serviceId, number));
|
||||||
}
|
}
|
||||||
|
|
||||||
public Map<String, RegisteredUser> getRegisteredUsers(final Set<String> numbers) throws IOException {
|
public Map<String, RegisteredUser> getRegisteredUsers(
|
||||||
Map<String, RegisteredUser> registeredUsers = getRegisteredUsersV2(numbers, true);
|
final Set<String> numbers
|
||||||
|
) throws IOException {
|
||||||
|
return getRegisteredUsers(numbers, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
private Map<String, RegisteredUser> getRegisteredUsers(
|
||||||
|
final Set<String> numbers, final boolean isPartialRefresh
|
||||||
|
) throws IOException {
|
||||||
|
Map<String, RegisteredUser> registeredUsers = getRegisteredUsersV2(numbers, isPartialRefresh, true);
|
||||||
|
|
||||||
// Store numbers as recipients, so we have the number/uuid association
|
// Store numbers as recipients, so we have the number/uuid association
|
||||||
registeredUsers.forEach((number, u) -> account.getRecipientTrustedResolver()
|
registeredUsers.forEach((number, u) -> account.getRecipientTrustedResolver()
|
||||||
|
@ -139,7 +152,7 @@ public class RecipientHelper {
|
||||||
private ServiceId getRegisteredUserByNumber(final String number) throws IOException, UnregisteredRecipientException {
|
private ServiceId getRegisteredUserByNumber(final String number) throws IOException, UnregisteredRecipientException {
|
||||||
final Map<String, RegisteredUser> aciMap;
|
final Map<String, RegisteredUser> aciMap;
|
||||||
try {
|
try {
|
||||||
aciMap = getRegisteredUsers(Set.of(number));
|
aciMap = getRegisteredUsers(Set.of(number), true);
|
||||||
} catch (NumberFormatException e) {
|
} catch (NumberFormatException e) {
|
||||||
throw new UnregisteredRecipientException(new org.asamk.signal.manager.api.RecipientAddress(null, number));
|
throw new UnregisteredRecipientException(new org.asamk.signal.manager.api.RecipientAddress(null, number));
|
||||||
}
|
}
|
||||||
|
@ -151,22 +164,50 @@ public class RecipientHelper {
|
||||||
}
|
}
|
||||||
|
|
||||||
private Map<String, RegisteredUser> getRegisteredUsersV2(
|
private Map<String, RegisteredUser> getRegisteredUsersV2(
|
||||||
final Set<String> numbers, boolean useCompat
|
final Set<String> numbers, boolean isPartialRefresh, boolean useCompat
|
||||||
) throws IOException {
|
) throws IOException {
|
||||||
// Only partial refresh is implemented here
|
final var previousNumbers = isPartialRefresh ? Set.<String>of() : account.getCdsiStore().getAllNumbers();
|
||||||
|
final var newNumbers = new HashSet<>(numbers) {{
|
||||||
|
removeAll(previousNumbers);
|
||||||
|
}};
|
||||||
|
if (newNumbers.isEmpty() && previousNumbers.isEmpty()) {
|
||||||
|
logger.debug("No new numbers to query.");
|
||||||
|
return Map.of();
|
||||||
|
}
|
||||||
|
logger.trace("Querying CDSI for {} new numbers ({} previous)", newNumbers.size(), previousNumbers.size());
|
||||||
|
final var token = previousNumbers.isEmpty()
|
||||||
|
? Optional.<byte[]>empty()
|
||||||
|
: Optional.ofNullable(account.getCdsiToken());
|
||||||
|
|
||||||
final CdsiV2Service.Response response;
|
final CdsiV2Service.Response response;
|
||||||
try {
|
try {
|
||||||
response = dependencies.getAccountManager()
|
response = dependencies.getAccountManager()
|
||||||
.getRegisteredUsersWithCdsi(Set.of(),
|
.getRegisteredUsersWithCdsi(previousNumbers,
|
||||||
numbers,
|
newNumbers,
|
||||||
account.getRecipientStore().getServiceIdToProfileKeyMap(),
|
account.getRecipientStore().getServiceIdToProfileKeyMap(),
|
||||||
useCompat,
|
useCompat,
|
||||||
Optional.empty(),
|
token,
|
||||||
serviceEnvironmentConfig.cdsiMrenclave(),
|
serviceEnvironmentConfig.cdsiMrenclave(),
|
||||||
null,
|
null,
|
||||||
token -> {
|
newToken -> {
|
||||||
// Not storing for partial refresh
|
if (isPartialRefresh) {
|
||||||
|
account.getCdsiStore().updateAfterPartialCdsQuery(newNumbers);
|
||||||
|
// Not storing newToken for partial refresh
|
||||||
|
} else {
|
||||||
|
final var fullNumbers = new HashSet<>(previousNumbers) {{
|
||||||
|
addAll(newNumbers);
|
||||||
|
}};
|
||||||
|
final var seenNumbers = new HashSet<>(numbers) {{
|
||||||
|
addAll(newNumbers);
|
||||||
|
}};
|
||||||
|
account.getCdsiStore().updateAfterFullCdsQuery(fullNumbers, seenNumbers);
|
||||||
|
account.setCdsiToken(newToken);
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
} catch (CdsiInvalidTokenException e) {
|
||||||
|
account.setCdsiToken(null);
|
||||||
|
account.getCdsiStore().clearAll();
|
||||||
|
throw e;
|
||||||
} catch (NumberFormatException e) {
|
} catch (NumberFormatException e) {
|
||||||
throw new IOException(e);
|
throw new IOException(e);
|
||||||
}
|
}
|
||||||
|
|
|
@ -9,6 +9,7 @@ import org.asamk.signal.manager.storage.keyValue.KeyValueStore;
|
||||||
import org.asamk.signal.manager.storage.prekeys.KyberPreKeyStore;
|
import org.asamk.signal.manager.storage.prekeys.KyberPreKeyStore;
|
||||||
import org.asamk.signal.manager.storage.prekeys.PreKeyStore;
|
import org.asamk.signal.manager.storage.prekeys.PreKeyStore;
|
||||||
import org.asamk.signal.manager.storage.prekeys.SignedPreKeyStore;
|
import org.asamk.signal.manager.storage.prekeys.SignedPreKeyStore;
|
||||||
|
import org.asamk.signal.manager.storage.recipients.CdsiStore;
|
||||||
import org.asamk.signal.manager.storage.recipients.RecipientStore;
|
import org.asamk.signal.manager.storage.recipients.RecipientStore;
|
||||||
import org.asamk.signal.manager.storage.sendLog.MessageSendLogStore;
|
import org.asamk.signal.manager.storage.sendLog.MessageSendLogStore;
|
||||||
import org.asamk.signal.manager.storage.senderKeys.SenderKeyRecordStore;
|
import org.asamk.signal.manager.storage.senderKeys.SenderKeyRecordStore;
|
||||||
|
@ -31,7 +32,7 @@ import java.util.UUID;
|
||||||
public class AccountDatabase extends Database {
|
public class AccountDatabase extends Database {
|
||||||
|
|
||||||
private final static Logger logger = LoggerFactory.getLogger(AccountDatabase.class);
|
private final static Logger logger = LoggerFactory.getLogger(AccountDatabase.class);
|
||||||
private static final long DATABASE_VERSION = 17;
|
private static final long DATABASE_VERSION = 18;
|
||||||
|
|
||||||
private AccountDatabase(final HikariDataSource dataSource) {
|
private AccountDatabase(final HikariDataSource dataSource) {
|
||||||
super(logger, DATABASE_VERSION, dataSource);
|
super(logger, DATABASE_VERSION, dataSource);
|
||||||
|
@ -55,6 +56,7 @@ public class AccountDatabase extends Database {
|
||||||
SenderKeyRecordStore.createSql(connection);
|
SenderKeyRecordStore.createSql(connection);
|
||||||
SenderKeySharedStore.createSql(connection);
|
SenderKeySharedStore.createSql(connection);
|
||||||
KeyValueStore.createSql(connection);
|
KeyValueStore.createSql(connection);
|
||||||
|
CdsiStore.createSql(connection);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -517,5 +519,17 @@ public class AccountDatabase extends Database {
|
||||||
""");
|
""");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if (oldVersion < 18) {
|
||||||
|
logger.debug("Updating database: Adding cdsi table");
|
||||||
|
try (final var statement = connection.createStatement()) {
|
||||||
|
statement.executeUpdate("""
|
||||||
|
CREATE TABLE cdsi (
|
||||||
|
_id INTEGER PRIMARY KEY,
|
||||||
|
number TEXT NOT NULL UNIQUE,
|
||||||
|
last_seen_at INTEGER NOT NULL
|
||||||
|
) STRICT;
|
||||||
|
""");
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -33,6 +33,7 @@ import org.asamk.signal.manager.storage.profiles.LegacyProfileStore;
|
||||||
import org.asamk.signal.manager.storage.profiles.ProfileStore;
|
import org.asamk.signal.manager.storage.profiles.ProfileStore;
|
||||||
import org.asamk.signal.manager.storage.protocol.LegacyJsonSignalProtocolStore;
|
import org.asamk.signal.manager.storage.protocol.LegacyJsonSignalProtocolStore;
|
||||||
import org.asamk.signal.manager.storage.protocol.SignalProtocolStore;
|
import org.asamk.signal.manager.storage.protocol.SignalProtocolStore;
|
||||||
|
import org.asamk.signal.manager.storage.recipients.CdsiStore;
|
||||||
import org.asamk.signal.manager.storage.recipients.LegacyRecipientStore;
|
import org.asamk.signal.manager.storage.recipients.LegacyRecipientStore;
|
||||||
import org.asamk.signal.manager.storage.recipients.LegacyRecipientStore2;
|
import org.asamk.signal.manager.storage.recipients.LegacyRecipientStore2;
|
||||||
import org.asamk.signal.manager.storage.recipients.RecipientAddress;
|
import org.asamk.signal.manager.storage.recipients.RecipientAddress;
|
||||||
|
@ -145,6 +146,7 @@ public class SignalAccount implements Closeable {
|
||||||
private final KeyValueEntry<Long> lastReceiveTimestamp = new KeyValueEntry<>("last-receive-timestamp",
|
private final KeyValueEntry<Long> lastReceiveTimestamp = new KeyValueEntry<>("last-receive-timestamp",
|
||||||
long.class,
|
long.class,
|
||||||
0L);
|
0L);
|
||||||
|
private final KeyValueEntry<byte[]> cdsiToken = new KeyValueEntry<>("cdsi-token", byte[].class);
|
||||||
private final KeyValueEntry<Long> storageManifestVersion = new KeyValueEntry<>("storage-manifest-version",
|
private final KeyValueEntry<Long> storageManifestVersion = new KeyValueEntry<>("storage-manifest-version",
|
||||||
long.class,
|
long.class,
|
||||||
-1L);
|
-1L);
|
||||||
|
@ -160,6 +162,7 @@ public class SignalAccount implements Closeable {
|
||||||
private StickerStore stickerStore;
|
private StickerStore stickerStore;
|
||||||
private ConfigurationStore configurationStore;
|
private ConfigurationStore configurationStore;
|
||||||
private KeyValueStore keyValueStore;
|
private KeyValueStore keyValueStore;
|
||||||
|
private CdsiStore cdsiStore;
|
||||||
|
|
||||||
private MessageCache messageCache;
|
private MessageCache messageCache;
|
||||||
private MessageSendLogStore messageSendLogStore;
|
private MessageSendLogStore messageSendLogStore;
|
||||||
|
@ -1220,6 +1223,10 @@ public class SignalAccount implements Closeable {
|
||||||
return getRecipientStore();
|
return getRecipientStore();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public CdsiStore getCdsiStore() {
|
||||||
|
return getOrCreate(() -> cdsiStore, () -> cdsiStore = new CdsiStore(getAccountDatabase()));
|
||||||
|
}
|
||||||
|
|
||||||
private RecipientIdCreator getRecipientIdCreator() {
|
private RecipientIdCreator getRecipientIdCreator() {
|
||||||
return recipientId -> getRecipientStore().create(recipientId);
|
return recipientId -> getRecipientStore().create(recipientId);
|
||||||
}
|
}
|
||||||
|
@ -1571,6 +1578,14 @@ public class SignalAccount implements Closeable {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public byte[] getCdsiToken() {
|
||||||
|
return getKeyValueStore().getEntry(cdsiToken);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setCdsiToken(final byte[] value) {
|
||||||
|
getKeyValueStore().storeEntry(cdsiToken, value);
|
||||||
|
}
|
||||||
|
|
||||||
public ProfileKey getProfileKey() {
|
public ProfileKey getProfileKey() {
|
||||||
return profileKey;
|
return profileKey;
|
||||||
}
|
}
|
||||||
|
|
|
@ -90,6 +90,8 @@ public class KeyValueStore {
|
||||||
value = resultSet.getLong("value");
|
value = resultSet.getLong("value");
|
||||||
} else if (clazz == boolean.class || clazz == Boolean.class) {
|
} else if (clazz == boolean.class || clazz == Boolean.class) {
|
||||||
value = resultSet.getBoolean("value");
|
value = resultSet.getBoolean("value");
|
||||||
|
} else if (clazz == byte[].class || clazz == Byte[].class) {
|
||||||
|
value = resultSet.getBytes("value");
|
||||||
} else if (clazz == String.class) {
|
} else if (clazz == String.class) {
|
||||||
value = resultSet.getString("value");
|
value = resultSet.getString("value");
|
||||||
} else if (Enum.class.isAssignableFrom(clazz)) {
|
} else if (Enum.class.isAssignableFrom(clazz)) {
|
||||||
|
@ -134,6 +136,12 @@ public class KeyValueStore {
|
||||||
} else {
|
} else {
|
||||||
statement.setBoolean(parameterIndex, (boolean) value);
|
statement.setBoolean(parameterIndex, (boolean) value);
|
||||||
}
|
}
|
||||||
|
} else if (clazz == byte[].class || clazz == Byte[].class) {
|
||||||
|
if (value == null) {
|
||||||
|
statement.setNull(parameterIndex, Types.BLOB);
|
||||||
|
} else {
|
||||||
|
statement.setBytes(parameterIndex, (byte[]) value);
|
||||||
|
}
|
||||||
} else if (clazz == String.class) {
|
} else if (clazz == String.class) {
|
||||||
if (value == null) {
|
if (value == null) {
|
||||||
statement.setNull(parameterIndex, Types.VARCHAR);
|
statement.setNull(parameterIndex, Types.VARCHAR);
|
||||||
|
|
|
@ -0,0 +1,170 @@
|
||||||
|
package org.asamk.signal.manager.storage.recipients;
|
||||||
|
|
||||||
|
import org.asamk.signal.manager.storage.Database;
|
||||||
|
import org.asamk.signal.manager.storage.Utils;
|
||||||
|
|
||||||
|
import java.sql.Connection;
|
||||||
|
import java.sql.SQLException;
|
||||||
|
import java.util.HashSet;
|
||||||
|
import java.util.Set;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
|
public class CdsiStore {
|
||||||
|
|
||||||
|
private static final String TABLE_CDSI = "cdsi";
|
||||||
|
|
||||||
|
private final Database database;
|
||||||
|
|
||||||
|
public static void createSql(Connection connection) throws SQLException {
|
||||||
|
// When modifying the CREATE statement here, also add a migration in AccountDatabase.java
|
||||||
|
try (final var statement = connection.createStatement()) {
|
||||||
|
statement.executeUpdate("""
|
||||||
|
CREATE TABLE cdsi (
|
||||||
|
_id INTEGER PRIMARY KEY,
|
||||||
|
number TEXT NOT NULL UNIQUE,
|
||||||
|
last_seen_at INTEGER NOT NULL
|
||||||
|
) STRICT;
|
||||||
|
""");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public CdsiStore(final Database database) {
|
||||||
|
this.database = database;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Set<String> getAllNumbers() {
|
||||||
|
try (final var connection = database.getConnection()) {
|
||||||
|
return getAllNumbers(connection);
|
||||||
|
} catch (SQLException e) {
|
||||||
|
throw new RuntimeException("Failed read from cdsi store", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Saves the set of e164 numbers used after a full refresh.
|
||||||
|
*
|
||||||
|
* @param fullNumbers All the e164 numbers used in the last CDS query (previous and new).
|
||||||
|
* @param seenNumbers The E164 numbers that were seen in either the system contacts or recipients table. This is different from fullNumbers in that fullNumbers
|
||||||
|
* includes every number we've ever seen, even if it's not in our contacts anymore.
|
||||||
|
*/
|
||||||
|
public void updateAfterFullCdsQuery(Set<String> fullNumbers, Set<String> seenNumbers) {
|
||||||
|
final var lastSeen = System.currentTimeMillis();
|
||||||
|
try (final var connection = database.getConnection()) {
|
||||||
|
final var existingNumbers = getAllNumbers(connection);
|
||||||
|
|
||||||
|
final var removedNumbers = new HashSet<>(existingNumbers) {{
|
||||||
|
removeAll(fullNumbers);
|
||||||
|
}};
|
||||||
|
removeNumbers(connection, removedNumbers);
|
||||||
|
|
||||||
|
final var addedNumbers = new HashSet<>(fullNumbers) {{
|
||||||
|
removeAll(existingNumbers);
|
||||||
|
}};
|
||||||
|
addNumbers(connection, addedNumbers, lastSeen);
|
||||||
|
|
||||||
|
updateLastSeen(connection, seenNumbers, lastSeen);
|
||||||
|
} catch (SQLException e) {
|
||||||
|
throw new RuntimeException("Failed update cdsi store", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Updates after a partial CDS query. Will not insert new entries.
|
||||||
|
* Instead, this will simply update the lastSeen timestamp of any entry we already have.
|
||||||
|
*
|
||||||
|
* @param seenNumbers The newly-added E164 numbers that we hadn't previously queried for.
|
||||||
|
*/
|
||||||
|
public void updateAfterPartialCdsQuery(Set<String> seenNumbers) {
|
||||||
|
final var lastSeen = System.currentTimeMillis();
|
||||||
|
|
||||||
|
try (final var connection = database.getConnection()) {
|
||||||
|
updateLastSeen(connection, seenNumbers, lastSeen);
|
||||||
|
} catch (SQLException e) {
|
||||||
|
throw new RuntimeException("Failed update cdsi store", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static Set<String> getAllNumbers(final Connection connection) throws SQLException {
|
||||||
|
final var sql = (
|
||||||
|
"""
|
||||||
|
SELECT c.number
|
||||||
|
FROM %s c
|
||||||
|
"""
|
||||||
|
).formatted(TABLE_CDSI);
|
||||||
|
try (final var statement = connection.prepareStatement(sql)) {
|
||||||
|
try (var result = Utils.executeQueryForStream(statement, r -> r.getString("number"))) {
|
||||||
|
return result.collect(Collectors.toSet());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void removeNumbers(
|
||||||
|
final Connection connection, final Set<String> numbers
|
||||||
|
) throws SQLException {
|
||||||
|
final var sql = (
|
||||||
|
"""
|
||||||
|
DELETE FROM %s
|
||||||
|
WHERE number = ?
|
||||||
|
"""
|
||||||
|
).formatted(TABLE_CDSI);
|
||||||
|
try (final var statement = connection.prepareStatement(sql)) {
|
||||||
|
for (final var number : numbers) {
|
||||||
|
statement.setString(1, number);
|
||||||
|
statement.executeUpdate();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void addNumbers(
|
||||||
|
final Connection connection, final Set<String> numbers, final long lastSeen
|
||||||
|
) throws SQLException {
|
||||||
|
final var sql = (
|
||||||
|
"""
|
||||||
|
INSERT INTO %s (number, last_seen_at)
|
||||||
|
VALUES (?, ?)
|
||||||
|
ON CONFLICT (number) DO UPDATE SET last_seen_at = excluded.last_seen_at
|
||||||
|
"""
|
||||||
|
).formatted(TABLE_CDSI);
|
||||||
|
try (final var statement = connection.prepareStatement(sql)) {
|
||||||
|
for (final var number : numbers) {
|
||||||
|
statement.setString(1, number);
|
||||||
|
statement.setLong(2, lastSeen);
|
||||||
|
statement.executeUpdate();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void updateLastSeen(
|
||||||
|
final Connection connection, final Set<String> numbers, final long lastSeen
|
||||||
|
) throws SQLException {
|
||||||
|
final var sql = (
|
||||||
|
"""
|
||||||
|
UPDATE %s
|
||||||
|
SET last_seen_at = ?
|
||||||
|
WHERE number = ?
|
||||||
|
"""
|
||||||
|
).formatted(TABLE_CDSI);
|
||||||
|
try (final var statement = connection.prepareStatement(sql)) {
|
||||||
|
for (final var number : numbers) {
|
||||||
|
statement.setLong(1, lastSeen);
|
||||||
|
statement.setString(2, number);
|
||||||
|
statement.executeUpdate();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void clearAll() {
|
||||||
|
final var sql = (
|
||||||
|
"""
|
||||||
|
TRUNCATE %s
|
||||||
|
"""
|
||||||
|
).formatted(TABLE_CDSI);
|
||||||
|
try (final var connection = database.getConnection()) {
|
||||||
|
try (final var statement = connection.prepareStatement(sql)) {
|
||||||
|
statement.executeUpdate();
|
||||||
|
}
|
||||||
|
} catch (SQLException e) {
|
||||||
|
throw new RuntimeException("Failed update cdsi store", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -383,6 +383,25 @@ public class RecipientStore implements RecipientIdCreator, RecipientResolver, Re
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public Set<String> getAllNumbers() {
|
||||||
|
final var sql = (
|
||||||
|
"""
|
||||||
|
SELECT r.number
|
||||||
|
FROM %s r
|
||||||
|
WHERE r.number IS NOT NULL
|
||||||
|
"""
|
||||||
|
).formatted(TABLE_RECIPIENT);
|
||||||
|
try (final var connection = database.getConnection()) {
|
||||||
|
try (final var statement = connection.prepareStatement(sql)) {
|
||||||
|
return Utils.executeQueryForStream(statement, resultSet -> resultSet.getString("number"))
|
||||||
|
.filter(Objects::nonNull)
|
||||||
|
.collect(Collectors.toSet());
|
||||||
|
}
|
||||||
|
} catch (SQLException e) {
|
||||||
|
throw new RuntimeException("Failed read from recipient store", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public Map<ServiceId, ProfileKey> getServiceIdToProfileKeyMap() {
|
public Map<ServiceId, ProfileKey> getServiceIdToProfileKeyMap() {
|
||||||
final var sql = (
|
final var sql = (
|
||||||
"""
|
"""
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue