]> nmode's Git Repositories - signal-cli/commitdiff
Implement message expiration timer version
authorAsamK <asamk@gmx.de>
Sat, 26 Oct 2024 11:08:21 +0000 (13:08 +0200)
committerAsamK <asamk@gmx.de>
Sat, 26 Oct 2024 11:08:21 +0000 (13:08 +0200)
Fixes #1605

lib/src/main/java/org/asamk/signal/manager/api/Contact.java
lib/src/main/java/org/asamk/signal/manager/config/ServiceConfig.java
lib/src/main/java/org/asamk/signal/manager/helper/ContactHelper.java
lib/src/main/java/org/asamk/signal/manager/helper/IncomingMessageHandler.java
lib/src/main/java/org/asamk/signal/manager/helper/SendHelper.java
lib/src/main/java/org/asamk/signal/manager/helper/SyncHelper.java
lib/src/main/java/org/asamk/signal/manager/storage/AccountDatabase.java
lib/src/main/java/org/asamk/signal/manager/storage/SignalAccount.java
lib/src/main/java/org/asamk/signal/manager/storage/recipients/LegacyRecipientStore2.java
lib/src/main/java/org/asamk/signal/manager/storage/recipients/RecipientStore.java
src/main/java/org/asamk/signal/dbus/DbusManagerImpl.java

index 3605b7c57a7a3fd9e2efb440c6410a8d28b4188e..91cf82df5e0b6e8b6cd0942ce66b51fa0bd27988 100644 (file)
@@ -11,6 +11,7 @@ public record Contact(
         String note,
         String color,
         int messageExpirationTime,
+        int messageExpirationTimeVersion,
         long muteUntil,
         boolean hideStory,
         boolean isBlocked,
@@ -29,6 +30,7 @@ public record Contact(
                 builder.note,
                 builder.color,
                 builder.messageExpirationTime,
+                builder.messageExpirationTimeVersion,
                 builder.muteUntil,
                 builder.hideStory,
                 builder.isBlocked,
@@ -84,6 +86,7 @@ public record Contact(
         private String note;
         private String color;
         private int messageExpirationTime;
+        private int messageExpirationTimeVersion = 1;
         private long muteUntil;
         private boolean hideStory;
         private boolean isBlocked;
@@ -139,6 +142,11 @@ public record Contact(
             return this;
         }
 
+        public Builder withMessageExpirationTimeVersion(final int val) {
+            messageExpirationTimeVersion = val;
+            return this;
+        }
+
         public Builder withMuteUntil(final long val) {
             muteUntil = val;
             return this;
index 52ee82e227f8f4b06e369e55e0bdc22cf6249122..7ee808156bb6bc3f32891161a4e3bf20ce024033 100644 (file)
@@ -29,8 +29,7 @@ public class ServiceConfig {
 
     public static AccountAttributes.Capabilities getCapabilities(boolean isPrimaryDevice) {
         final var deleteSync = !isPrimaryDevice;
-        final var versionedExpirationTimer = !isPrimaryDevice;
-        return new AccountAttributes.Capabilities(true, deleteSync, versionedExpirationTimer);
+        return new AccountAttributes.Capabilities(true, deleteSync, true);
     }
 
     public static ServiceEnvironmentConfig getServiceEnvironmentConfig(
index f9af27f18cf964d2edf077e47168125a88a414d8..56ef89ed434e7189a9c06d6da6952127f2a0ac31 100644 (file)
@@ -36,8 +36,34 @@ public class ContactHelper {
             return;
         }
         final var builder = contact == null ? Contact.newBuilder() : Contact.newBuilder(contact);
+        final var version = contact == null
+                ? 1
+                : contact.messageExpirationTimeVersion() == Integer.MAX_VALUE
+                        ? Integer.MAX_VALUE
+                        : contact.messageExpirationTimeVersion() + 1;
         account.getContactStore()
-                .storeContact(recipientId, builder.withMessageExpirationTime(messageExpirationTimer).build());
+                .storeContact(recipientId,
+                        builder.withMessageExpirationTime(messageExpirationTimer)
+                                .withMessageExpirationTimeVersion(version)
+                                .build());
+    }
+
+    public void setExpirationTimer(
+            RecipientId recipientId, int messageExpirationTimer, int messageExpirationTimerVersion
+    ) {
+        var contact = account.getContactStore().getContact(recipientId);
+        if (contact != null && (
+                contact.messageExpirationTime() == messageExpirationTimer
+                        || contact.messageExpirationTimeVersion() >= messageExpirationTimerVersion
+        )) {
+            return;
+        }
+        final var builder = contact == null ? Contact.newBuilder() : Contact.newBuilder(contact);
+        account.getContactStore()
+                .storeContact(recipientId,
+                        builder.withMessageExpirationTime(messageExpirationTimer)
+                                .withMessageExpirationTimeVersion(messageExpirationTimerVersion)
+                                .build());
     }
 
     public void setContactBlocked(RecipientId recipientId, boolean blocked) {
index 5e04f1357351eb2499e49229d8ab149991e92527..294b17f6120d0d2b4ddc8bf3fe66cc4185c7166a 100644 (file)
@@ -802,7 +802,9 @@ public final class IncomingMessageHandler {
                 }
             } else if (conversationPartnerAddress != null) {
                 context.getContactHelper()
-                        .setExpirationTimer(conversationPartnerAddress.recipientId(), message.getExpiresInSeconds());
+                        .setExpirationTimer(conversationPartnerAddress.recipientId(),
+                                message.getExpiresInSeconds(),
+                                message.getExpireTimerVersion());
             }
         }
         if (!ignoreAttachments) {
index 84b9f0e654c681afc95760a90ab91bd1e13dd995..0622b4a8972d6402795487d253e8206c69222786 100644 (file)
@@ -86,8 +86,8 @@ public class SendHelper {
             account.getContactStore().storeContact(recipientId, contact);
         }
 
-        final var expirationTime = contact.messageExpirationTime();
-        messageBuilder.withExpiration(expirationTime);
+        messageBuilder.withExpiration(contact.messageExpirationTime());
+        messageBuilder.withExpireTimerVersion(contact.messageExpirationTimeVersion());
 
         if (!contact.isBlocked()) {
             final var profileKey = account.getProfileKey().serialize();
@@ -187,8 +187,8 @@ public class SendHelper {
     ) {
         final var recipientId = account.getSelfRecipientId();
         final var contact = account.getContactStore().getContact(recipientId);
-        final var expirationTime = contact != null ? contact.messageExpirationTime() : 0;
-        messageBuilder.withExpiration(expirationTime);
+        messageBuilder.withExpiration(contact != null ? contact.messageExpirationTime() : 0);
+        messageBuilder.withExpireTimerVersion(contact != null ? contact.messageExpirationTimeVersion() : 1);
 
         var message = messageBuilder.build();
         return sendSelfMessage(message, editTargetTimestamp);
index ecaf76058beca397ebd8b40c228da93554abdbab..56665fda870c73edbac42287d21ba437312b4330 100644 (file)
@@ -239,7 +239,7 @@ public class SyncHelper {
                 Optional.ofNullable(verifiedMessage),
                 Optional.ofNullable(profileKey),
                 Optional.ofNullable(contact == null ? null : contact.messageExpirationTime()),
-                Optional.empty(),
+                Optional.ofNullable(contact == null ? null : contact.messageExpirationTimeVersion()),
                 Optional.empty(),
                 contact != null && contact.isArchived());
     }
@@ -392,7 +392,18 @@ public class SyncHelper {
                                 TrustLevel.fromVerifiedState(verifiedMessage.getVerified()));
             }
             if (c.getExpirationTimer().isPresent()) {
-                builder.withMessageExpirationTime(c.getExpirationTimer().get());
+                if (c.getExpirationTimerVersion().isPresent() && (
+                        contact == null || c.getExpirationTimerVersion().get() > contact.messageExpirationTimeVersion()
+                )) {
+                    builder.withMessageExpirationTime(c.getExpirationTimer().get());
+                    builder.withMessageExpirationTimeVersion(c.getExpirationTimerVersion().get());
+                } else {
+                    logger.debug(
+                            "[ContactSync] {} was synced with an old expiration timer. Ignoring. Received: {} Current: ${}",
+                            recipientId,
+                            c.getExpirationTimerVersion(),
+                            contact == null ? 1 : contact.messageExpirationTimeVersion());
+                }
             }
             builder.withIsArchived(c.isArchived());
             account.getContactStore().storeContact(recipientId, builder.build());
index 3d9fad9c90a7cad9b53cb76cf32d75eeed317299..be9e7af183a225ea877cf24b77928f5c0ea8032a 100644 (file)
@@ -33,7 +33,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 = 26;
+    private static final long DATABASE_VERSION = 27;
 
     private AccountDatabase(final HikariDataSource dataSource) {
         super(logger, DATABASE_VERSION, dataSource);
@@ -600,6 +600,14 @@ public class AccountDatabase extends Database {
                                         """);
             }
         }
+        if (oldVersion < 27) {
+            logger.debug("Updating database: Create expiration_time_version column");
+            try (final var statement = connection.createStatement()) {
+                statement.executeUpdate("""
+                                        ALTER TABLE recipient ADD expiration_time_version INTEGER DEFAULT 1 NOT NULL;
+                                        """);
+            }
+        }
     }
 
     private static void createUuidMappingTable(
index 0ca76e9dfffa63b54e38d6f7695b67396a799b95..87cf3aa39cdc5c1ee099917fdad987f44c53222c 100644 (file)
@@ -864,6 +864,7 @@ public class SignalAccount implements Closeable {
                                 null,
                                 contact.color,
                                 contact.messageExpirationTime,
+                                1,
                                 0,
                                 false,
                                 contact.blocked,
@@ -939,6 +940,7 @@ public class SignalAccount implements Closeable {
                             getContactStore().storeContact(recipientId,
                                     Contact.newBuilder(contact)
                                             .withMessageExpirationTime(thread.messageExpirationTime)
+                                            .withMessageExpirationTimeVersion(1)
                                             .build());
                         }
                     } else {
index e7aff6405ebd52fdebeda33bad7a5ec898352d09..e8d44ab946dfcaf5af057a1af5ff829f67914fd8 100644 (file)
@@ -46,6 +46,7 @@ public class LegacyRecipientStore2 {
                             null,
                             r.contact.color,
                             r.contact.messageExpirationTime,
+                            1,
                             0,
                             false,
                             r.contact.blocked,
index 6a67200884e66e74a7ee0628dbe0d819a640e199..54383eb0b92e042d74ad9645e57f2bd210489805 100644 (file)
@@ -79,6 +79,7 @@ public class RecipientStore implements RecipientIdCreator, RecipientResolver, Re
                                       color TEXT,
 
                                       expiration_time INTEGER NOT NULL DEFAULT 0,
+                                      expiration_time_version INTEGER DEFAULT 1 NOT NULL,
                                       mute_until INTEGER NOT NULL DEFAULT 0,
                                       blocked INTEGER NOT NULL DEFAULT FALSE,
                                       archived INTEGER NOT NULL DEFAULT FALSE,
@@ -332,7 +333,7 @@ public class RecipientStore implements RecipientIdCreator, RecipientResolver, Re
     public List<Pair<RecipientId, Contact>> getContacts() {
         final var sql = (
                 """
-                SELECT r._id, r.given_name, r.family_name, r.nick_name, r.nick_name_given_name, r.nick_name_family_name, r.note, r.expiration_time, r.mute_until, r.hide_story, r.profile_sharing, r.color, r.blocked, r.archived, r.hidden, r.unregistered_timestamp
+                SELECT r._id, r.given_name, r.family_name, r.nick_name, r.nick_name_given_name, r.nick_name_family_name, r.note, r.expiration_time, r.expiration_time_version, r.mute_until, r.hide_story, r.profile_sharing, r.color, r.blocked, r.archived, r.hidden, r.unregistered_timestamp
                 FROM %s r
                 WHERE (r.number IS NOT NULL OR r.aci IS NOT NULL) AND %s AND r.hidden = FALSE
                 """
@@ -356,7 +357,7 @@ public class RecipientStore implements RecipientIdCreator, RecipientResolver, Re
                 SELECT r._id,
                        r.number, r.aci, r.pni, r.username,
                        r.profile_key, r.profile_key_credential,
-                       r.given_name, r.family_name, r.nick_name, r.nick_name_given_name, r.nick_name_family_name, r.note, r.expiration_time, r.mute_until, r.hide_story, r.profile_sharing, r.color, r.blocked, r.archived, r.hidden, r.unregistered_timestamp,
+                       r.given_name, r.family_name, r.nick_name, r.nick_name_given_name, r.nick_name_family_name, r.note, r.expiration_time, r.expiration_time_version, r.mute_until, r.hide_story, r.profile_sharing, r.color, r.blocked, r.archived, r.hidden, r.unregistered_timestamp,
                        r.profile_last_update_timestamp, r.profile_given_name, r.profile_family_name, r.profile_about, r.profile_about_emoji, r.profile_avatar_url_path, r.profile_mobile_coin_address, r.profile_unidentified_access_mode, r.profile_capabilities, r.profile_phone_number_sharing,
                        r.discoverable,
                        r.storage_record
@@ -376,7 +377,7 @@ public class RecipientStore implements RecipientIdCreator, RecipientResolver, Re
                 SELECT r._id,
                        r.number, r.aci, r.pni, r.username,
                        r.profile_key, r.profile_key_credential,
-                       r.given_name, r.family_name, r.nick_name, r.nick_name_given_name, r.nick_name_family_name, r.note, r.expiration_time, r.mute_until, r.hide_story, r.profile_sharing, r.color, r.blocked, r.archived, r.hidden, r.unregistered_timestamp,
+                       r.given_name, r.family_name, r.nick_name, r.nick_name_given_name, r.nick_name_family_name, r.note, r.expiration_time, r.expiration_time_version, r.mute_until, r.hide_story, r.profile_sharing, r.color, r.blocked, r.archived, r.hidden, r.unregistered_timestamp,
                        r.profile_last_update_timestamp, r.profile_given_name, r.profile_family_name, r.profile_about, r.profile_about_emoji, r.profile_avatar_url_path, r.profile_mobile_coin_address, r.profile_unidentified_access_mode, r.profile_capabilities, r.profile_phone_number_sharing,
                        r.discoverable,
                        r.storage_record
@@ -413,7 +414,7 @@ public class RecipientStore implements RecipientIdCreator, RecipientResolver, Re
                 SELECT r._id,
                        r.number, r.aci, r.pni, r.username,
                        r.profile_key, r.profile_key_credential,
-                       r.given_name, r.family_name, r.nick_name, r.nick_name_given_name, r.nick_name_family_name, r.note, r.expiration_time, r.mute_until, r.hide_story, r.profile_sharing, r.color, r.blocked, r.archived, r.hidden, r.unregistered_timestamp,
+                       r.given_name, r.family_name, r.nick_name, r.nick_name_given_name, r.nick_name_family_name, r.note, r.expiration_time, r.expiration_time_version, r.mute_until, r.hide_story, r.profile_sharing, r.color, r.blocked, r.archived, r.hidden, r.unregistered_timestamp,
                        r.profile_last_update_timestamp, r.profile_given_name, r.profile_family_name, r.profile_about, r.profile_about_emoji, r.profile_avatar_url_path, r.profile_mobile_coin_address, r.profile_unidentified_access_mode, r.profile_capabilities, r.profile_phone_number_sharing,
                        r.discoverable,
                        r.storage_record
@@ -817,7 +818,7 @@ public class RecipientStore implements RecipientIdCreator, RecipientResolver, Re
         final var sql = (
                 """
                 UPDATE %s
-                SET given_name = ?, family_name = ?, nick_name = ?, expiration_time = ?, mute_until = ?, hide_story = ?, profile_sharing = ?, color = ?, blocked = ?, archived = ?, unregistered_timestamp = ?, nick_name_given_name = ?, nick_name_family_name = ?, note = ?
+                SET given_name = ?, family_name = ?, nick_name = ?, expiration_time = ?, expiration_time_version = ?, mute_until = ?, hide_story = ?, profile_sharing = ?, color = ?, blocked = ?, archived = ?, unregistered_timestamp = ?, nick_name_given_name = ?, nick_name_family_name = ?, note = ?
                 WHERE _id = ?
                 """
         ).formatted(TABLE_RECIPIENT);
@@ -826,21 +827,22 @@ public class RecipientStore implements RecipientIdCreator, RecipientResolver, Re
             statement.setString(2, contact == null ? null : contact.familyName());
             statement.setString(3, contact == null ? null : contact.nickName());
             statement.setInt(4, contact == null ? 0 : contact.messageExpirationTime());
-            statement.setLong(5, contact == null ? 0 : contact.muteUntil());
-            statement.setBoolean(6, contact != null && contact.hideStory());
-            statement.setBoolean(7, contact != null && contact.isProfileSharingEnabled());
-            statement.setString(8, contact == null ? null : contact.color());
-            statement.setBoolean(9, contact != null && contact.isBlocked());
-            statement.setBoolean(10, contact != null && contact.isArchived());
+            statement.setInt(5, contact == null ? 0 : Math.max(1, contact.messageExpirationTimeVersion()));
+            statement.setLong(6, contact == null ? 0 : contact.muteUntil());
+            statement.setBoolean(7, contact != null && contact.hideStory());
+            statement.setBoolean(8, contact != null && contact.isProfileSharingEnabled());
+            statement.setString(9, contact == null ? null : contact.color());
+            statement.setBoolean(10, contact != null && contact.isBlocked());
+            statement.setBoolean(11, contact != null && contact.isArchived());
             if (contact == null || contact.unregisteredTimestamp() == null) {
-                statement.setNull(11, Types.INTEGER);
+                statement.setNull(12, Types.INTEGER);
             } else {
-                statement.setLong(11, contact.unregisteredTimestamp());
+                statement.setLong(12, contact.unregisteredTimestamp());
             }
-            statement.setString(12, contact == null ? null : contact.nickNameGivenName());
-            statement.setString(13, contact == null ? null : contact.nickNameFamilyName());
-            statement.setString(14, contact == null ? null : contact.note());
-            statement.setLong(15, recipientId.id());
+            statement.setString(13, contact == null ? null : contact.nickNameGivenName());
+            statement.setString(14, contact == null ? null : contact.nickNameFamilyName());
+            statement.setString(15, contact == null ? null : contact.note());
+            statement.setLong(16, recipientId.id());
             statement.executeUpdate();
         }
         if (contact != null && contact.unregisteredTimestamp() != null) {
@@ -918,8 +920,7 @@ public class RecipientStore implements RecipientIdCreator, RecipientResolver, Re
                     markDiscoverable(connection, recipientId, false);
                     final var contact = getContact(connection, recipientId);
                     if (recipientAddress.get().address().aci().isEmpty() || (
-                            contact != null
-                                    && contact.unregisteredTimestamp() != null
+                            contact != null && contact.unregisteredTimestamp() != null
                     )) {
                         markUnregisteredAndSplitIfNecessary(connection, recipientId);
                     }
@@ -1416,7 +1417,7 @@ public class RecipientStore implements RecipientIdCreator, RecipientResolver, Re
     private Contact getContact(final Connection connection, final RecipientId recipientId) throws SQLException {
         final var sql = (
                 """
-                SELECT r.given_name, r.family_name, r.nick_name, r.nick_name_given_name, r.nick_name_family_name, r.note, r.expiration_time, r.mute_until, r.hide_story, r.profile_sharing, r.color, r.blocked, r.archived, r.hidden, r.unregistered_timestamp
+                SELECT r.given_name, r.family_name, r.nick_name, r.nick_name_given_name, r.nick_name_family_name, r.note, r.expiration_time, r.expiration_time_version, r.mute_until, r.hide_story, r.profile_sharing, r.color, r.blocked, r.archived, r.hidden, r.unregistered_timestamp
                 FROM %s r
                 WHERE r._id = ? AND (%s)
                 """
@@ -1514,6 +1515,7 @@ public class RecipientStore implements RecipientIdCreator, RecipientResolver, Re
                 resultSet.getString("note"),
                 resultSet.getString("color"),
                 resultSet.getInt("expiration_time"),
+                resultSet.getInt("expiration_time_version"),
                 resultSet.getLong("mute_until"),
                 resultSet.getBoolean("hide_story"),
                 resultSet.getBoolean("blocked"),
index 9ae3001a6cf55975d7adff3c5e175e51b7709a21..bd5bdfb2b1835457a329358d6fbfa07b707b9b04 100644 (file)
@@ -699,6 +699,7 @@ public class DbusManagerImpl implements Manager {
                             null,
                             null,
                             0,
+                            1,
                             0,
                             false,
                             contactBlocked,