Improve contact merging

This commit is contained in:
AsamK 2026-08-21 20:36:01 +02:00
parent d2f5696df1
commit 9605c94f09
2 changed files with 117 additions and 1 deletions

View File

@ -1490,9 +1490,14 @@ public class RecipientStore implements RecipientIdCreator, RecipientResolver, Re
RecipientId toBeMergedRecipientId
) throws SQLException {
final var contact = getContact(connection, recipientId);
final var toBeMergedContact = getContact(connection, toBeMergedRecipientId);
if (contact == null) {
final var toBeMergedContact = getContact(connection, toBeMergedRecipientId);
storeContact(connection, recipientId, toBeMergedContact);
} else if (toBeMergedContact != null) {
final var mergedContact = mergeContacts(contact, toBeMergedContact);
if (!contact.equals(mergedContact)) {
storeContact(connection, recipientId, mergedContact);
}
}
final var profileKey = getProfileKey(connection, recipientId);
@ -1517,6 +1522,24 @@ public class RecipientStore implements RecipientIdCreator, RecipientResolver, Re
recipientsMerged.put(toBeMergedRecipientId.id(), recipientId.id());
}
static Contact mergeContacts(final Contact primary, final Contact secondary) {
final var profileSharingEnabled = primary.isProfileSharingEnabled() || secondary.isProfileSharingEnabled();
return Contact.newBuilder(primary)
.withGivenName(secondary.givenName())
.withFamilyName(secondary.familyName())
.withMessageExpirationTime(primary.messageExpirationTime() > 0
? primary.messageExpirationTime()
: secondary.messageExpirationTime())
.withMessageExpirationTimeVersion(Math.max(primary.messageExpirationTimeVersion(),
secondary.messageExpirationTimeVersion()))
.withMuteUntil(primary.muteUntil() > 0 ? primary.muteUntil() : secondary.muteUntil())
.withIsBlocked(primary.isBlocked() || secondary.isBlocked())
.withBlockedAt(Math.max(primary.blockedAt(), secondary.blockedAt()))
.withIsProfileSharingEnabled(profileSharingEnabled)
.withIsHidden(profileSharingEnabled ? false : primary.isHidden())
.build();
}
private Optional<RecipientWithAddress> findByNumber(
final Connection connection,
final String number

View File

@ -0,0 +1,93 @@
package org.asamk.signal.manager.storage.recipients;
import org.asamk.signal.manager.api.Contact;
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertTrue;
class RecipientStoreTest {
@Test
void mergeContactsUsesAndroidMergePolicy() {
final var primary = Contact.newBuilder()
.withGivenName("Primary given")
.withFamilyName("Primary family")
.withNickName("Primary system nickname")
.withNickNameGivenName("Primary nickname given")
.withNickNameFamilyName("Primary nickname family")
.withNote("Primary note")
.withColor("Primary color")
.withMessageExpirationTimeVersion(2)
.withHideStory(true)
.withIsBlocked(false)
.withBlockedAt(100)
.withIsArchived(true)
.withIsHidden(true)
.withUnregisteredTimestamp(300L)
.build();
final var secondary = Contact.newBuilder()
.withGivenName("Secondary given")
.withFamilyName("Secondary family")
.withNickName("Secondary system nickname")
.withNickNameGivenName("Secondary nickname given")
.withNickNameFamilyName("Secondary nickname family")
.withNote("Secondary note")
.withColor("Secondary color")
.withMessageExpirationTime(60)
.withMessageExpirationTimeVersion(3)
.withMuteUntil(400)
.withIsBlocked(true)
.withBlockedAt(200)
.withIsProfileSharingEnabled(true)
.withIsHidden(true)
.withUnregisteredTimestamp(500L)
.build();
final var merged = RecipientStore.mergeContacts(primary, secondary);
assertEquals("Secondary given", merged.givenName());
assertEquals("Secondary family", merged.familyName());
assertEquals("Primary system nickname", merged.nickName());
assertEquals("Primary nickname given", merged.nickNameGivenName());
assertEquals("Primary nickname family", merged.nickNameFamilyName());
assertEquals("Primary note", merged.note());
assertEquals("Primary color", merged.color());
assertEquals(60, merged.messageExpirationTime());
assertEquals(3, merged.messageExpirationTimeVersion());
assertEquals(400, merged.muteUntil());
assertTrue(merged.hideStory());
assertTrue(merged.isBlocked());
assertEquals(200, merged.blockedAt());
assertTrue(merged.isArchived());
assertTrue(merged.isProfileSharingEnabled());
assertFalse(merged.isHidden());
assertEquals(300L, merged.unregisteredTimestamp());
}
@Test
void mergeContactsPrefersConfiguredPrimaryValues() {
final var primary = Contact.newBuilder()
.withColor("Primary color")
.withMessageExpirationTime(30)
.withMessageExpirationTimeVersion(4)
.withMuteUntil(100)
.withIsHidden(true)
.build();
final var secondary = Contact.newBuilder()
.withColor("Secondary color")
.withMessageExpirationTime(60)
.withMessageExpirationTimeVersion(3)
.withMuteUntil(200)
.build();
final var merged = RecipientStore.mergeContacts(primary, secondary);
assertEquals("Primary color", merged.color());
assertEquals(30, merged.messageExpirationTime());
assertEquals(4, merged.messageExpirationTimeVersion());
assertEquals(100, merged.muteUntil());
assertTrue(merged.isHidden());
}
}