]> nmode's Git Repositories - signal-cli/commitdiff
Store recipient unregistered state
authorAsamK <asamk@gmx.de>
Sat, 27 Jan 2024 16:22:21 +0000 (17:22 +0100)
committerAsamK <asamk@gmx.de>
Sun, 28 Jan 2024 21:38:41 +0000 (22:38 +0100)
lib/src/main/java/org/asamk/signal/manager/config/ServiceConfig.java
lib/src/main/java/org/asamk/signal/manager/helper/RecipientHelper.java
lib/src/main/java/org/asamk/signal/manager/helper/StorageHelper.java
lib/src/main/java/org/asamk/signal/manager/storage/AccountDatabase.java
lib/src/main/java/org/asamk/signal/manager/storage/recipients/RecipientStore.java

index d46b6988db432267c5e81b8e6ef9eaa3e48341fa..ea47b8a6797f3fde1f1a0a730efd1ae96461305d 100644 (file)
@@ -24,6 +24,7 @@ public class ServiceConfig {
     public static final boolean AUTOMATIC_NETWORK_RETRY = true;
     public static final int GROUP_MAX_SIZE = 1001;
     public static final int MAXIMUM_ONE_OFF_REQUEST_SIZE = 3;
+    public static final long UNREGISTERED_LIFESPAN = TimeUnit.DAYS.toMillis(30);
 
     public static AccountAttributes.Capabilities getCapabilities(boolean isPrimaryDevice) {
         final var giftBadges = !isPrimaryDevice;
index 4a0bc9c431df90107137f8e248575bddff982833..8364605235a228b5c04109a515eb0d4043d7d964 100644 (file)
@@ -164,6 +164,10 @@ public class RecipientHelper {
         registeredUsers.forEach((number, u) -> account.getRecipientTrustedResolver()
                 .resolveRecipientTrusted(u.aci, u.pni, Optional.of(number)));
 
+        final var unregisteredUsers = new HashSet<>(numbers);
+        unregisteredUsers.removeAll(registeredUsers.keySet());
+        account.getRecipientStore().markUnregistered(unregisteredUsers);
+
         return registeredUsers;
     }
 
index 8c8ac6fcbb894619e30112596225ecc5101a2083..5bccfd05d7cacbb368ac77dc99c3a38562d51e2d 100644 (file)
@@ -149,6 +149,17 @@ public class StorageHelper {
 
             logger.debug("Pre-Merge ID Difference :: " + idDifference);
 
+            if (!idDifference.localOnlyIds().isEmpty()) {
+                final var updated = account.getRecipientStore()
+                        .removeStorageIdsFromLocalOnlyUnregisteredRecipients(connection, idDifference.localOnlyIds());
+
+                if (updated > 0) {
+                    logger.warn(
+                            "Found {} records that were deleted remotely but only marked unregistered locally. Removed those from local store. Recalculating diff.",
+                            updated);
+                }
+            }
+
             if (!idDifference.isEmpty()) {
                 final var remoteOnlyRecords = getSignalStorageRecords(storageKey, idDifference.remoteOnlyIds());
 
index c49e56ab59b45598551d6b6e5c6dde1043b3e1df..9e9eebf8aabd3ca27ce6e0c68b08aec905b616a2 100644 (file)
@@ -32,7 +32,7 @@ import java.util.UUID;
 public class AccountDatabase extends Database {
 
     private static final Logger logger = LoggerFactory.getLogger(AccountDatabase.class);
-    private static final long DATABASE_VERSION = 20;
+    private static final long DATABASE_VERSION = 21;
 
     private AccountDatabase(final HikariDataSource dataSource) {
         super(logger, DATABASE_VERSION, dataSource);
@@ -558,5 +558,14 @@ public class AccountDatabase extends Database {
                                         """);
             }
         }
+        if (oldVersion < 21) {
+            logger.debug("Updating database: Create unregistered column");
+            try (final var statement = connection.createStatement()) {
+                statement.executeUpdate("""
+                                        ALTER TABLE recipient ADD unregistered_timestamp INTEGER;
+                                        UPDATE recipient SET pni = NULL WHERE uuid IS NOT NULL;
+                                        """);
+            }
+        }
     }
 }
index 926f09bd26959964f2f39c6a8d80ca094e6d9ad8..46f8652c0bc64f6c10a35ffa6f65537be47e9069 100644 (file)
@@ -37,6 +37,8 @@ import java.util.Set;
 import java.util.function.Supplier;
 import java.util.stream.Collectors;
 
+import static org.asamk.signal.manager.config.ServiceConfig.UNREGISTERED_LIFESPAN;
+
 public class RecipientStore implements RecipientIdCreator, RecipientResolver, RecipientTrustedResolver, ContactsStore, ProfileStore {
 
     private static final Logger logger = LoggerFactory.getLogger(RecipientStore.class);
@@ -65,6 +67,7 @@ public class RecipientStore implements RecipientIdCreator, RecipientResolver, Re
                                       username TEXT UNIQUE,
                                       uuid BLOB UNIQUE,
                                       pni BLOB UNIQUE,
+                                      unregistered_timestamp INTEGER,
                                       profile_key BLOB,
                                       profile_key_credential BLOB,
 
@@ -521,7 +524,7 @@ public class RecipientStore implements RecipientIdCreator, RecipientResolver, Re
                 """
                 SELECT r._id
                 FROM %s r
-                WHERE r.storage_id IS NULL
+                WHERE r.storage_id IS NULL AND (r.unregistered_timestamp IS NULL OR r.unregistered_timestamp > ?)
                 """
         ).formatted(TABLE_RECIPIENT);
         final var updateSql = (
@@ -534,6 +537,7 @@ public class RecipientStore implements RecipientIdCreator, RecipientResolver, Re
         try (final var connection = database.getConnection()) {
             connection.setAutoCommit(false);
             try (final var selectStmt = connection.prepareStatement(selectSql)) {
+                selectStmt.setLong(1, System.currentTimeMillis() - UNREGISTERED_LIFESPAN);
                 final var recipientIds = Utils.executeQueryForStream(selectStmt, this::getRecipientIdFromResultSet)
                         .toList();
                 try (final var updateStmt = connection.prepareStatement(updateSql)) {
@@ -835,6 +839,76 @@ public class RecipientStore implements RecipientIdCreator, RecipientResolver, Re
         rotateStorageId(connection, recipientId);
     }
 
+    public int removeStorageIdsFromLocalOnlyUnregisteredRecipients(
+            final Connection connection, final List<StorageId> storageIds
+    ) throws SQLException {
+        final var sql = (
+                """
+                UPDATE %s
+                SET storage_id = NULL
+                WHERE storage_id = ? AND storage_id IS NOT NULL AND unregistered_timestamp IS NOT NULL
+                """
+        ).formatted(TABLE_RECIPIENT);
+        var count = 0;
+        try (final var statement = connection.prepareStatement(sql)) {
+            for (final var storageId : storageIds) {
+                statement.setBytes(1, storageId.getRaw());
+                count += statement.executeUpdate();
+            }
+        }
+        return count;
+    }
+
+    public void markUnregistered(final Set<String> unregisteredUsers) {
+        logger.debug("Marking {} numbers as unregistered", unregisteredUsers.size());
+        try (final var connection = database.getConnection()) {
+            connection.setAutoCommit(false);
+            for (final var number : unregisteredUsers) {
+                final var recipient = findByNumber(connection, number);
+                if (recipient.isPresent()) {
+                    markUnregistered(connection, recipient.get().id());
+                }
+            }
+            connection.commit();
+        } catch (SQLException e) {
+            throw new RuntimeException("Failed update recipient store", e);
+        }
+    }
+
+    private void markRegistered(
+            final Connection connection, final RecipientId recipientId
+    ) throws SQLException {
+        final var sql = (
+                """
+                UPDATE %s
+                SET unregistered_timestamp = ?
+                WHERE _id = ?
+                """
+        ).formatted(TABLE_RECIPIENT);
+        try (final var statement = connection.prepareStatement(sql)) {
+            statement.setNull(1, Types.INTEGER);
+            statement.setLong(2, recipientId.id());
+            statement.executeUpdate();
+        }
+    }
+
+    private void markUnregistered(
+            final Connection connection, final RecipientId recipientId
+    ) throws SQLException {
+        final var sql = (
+                """
+                UPDATE %s
+                SET unregistered_timestamp = ?
+                WHERE _id = ? AND unregistered_timestamp IS NULL
+                """
+        ).formatted(TABLE_RECIPIENT);
+        try (final var statement = connection.prepareStatement(sql)) {
+            statement.setLong(1, System.currentTimeMillis());
+            statement.setLong(2, recipientId.id());
+            statement.executeUpdate();
+        }
+    }
+
     private void storeExpiringProfileKeyCredential(
             final Connection connection,
             final RecipientId recipientId,
@@ -948,6 +1022,7 @@ public class RecipientStore implements RecipientIdCreator, RecipientResolver, Re
             return new Pair<>(resolveRecipientLocked(connection, address), List.of());
         } else {
             final var pair = MergeRecipientHelper.resolveRecipientTrustedLocked(new HelperStore(connection), address);
+            markRegistered(connection, pair.first());
 
             for (final var toBeMergedRecipientId : pair.second()) {
                 mergeRecipientsLocked(connection, pair.first(), toBeMergedRecipientId);