Fix storage sync issues

This commit is contained in:
AsamK 2026-08-21 21:39:51 +02:00
parent 9605c94f09
commit 3c17e01c37
6 changed files with 106 additions and 13 deletions

View File

@ -452,7 +452,7 @@ public class StorageHelper {
final var stickerPacks = account.getStickerStore()
.getStickerPacks(connection)
.stream()
.filter(pack -> pack.isInstalled() || pack.deletedTimestamp() > 0)
.filter(pack -> pack.storageId() != null)
.toList();
newStickerPackStorageIds = generateStickerPackStorageIds(stickerPacks);
for (final var stickerPack : stickerPacks) {
@ -716,6 +716,13 @@ public class StorageHelper {
final var contactRecordProcessor = new ContactRecordProcessor(account, connection, context.getJobExecutor());
final var stickerPackRecordProcessor = new StickerPackRecordProcessor(account, connection);
final var contactRecords = records.stream()
.filter(record -> record.getProto().contact != null)
.map(record -> StorageRecordConvertersKt.toSignalContactRecord(record.getProto().contact,
record.getId()))
.toList();
contactRecordProcessor.prepare(contactRecords);
for (final var record : records) {
if (record.getProto().account != null) {
logger.debug("Reading record {} of type account", record.getId());

View File

@ -923,11 +923,34 @@ public class RecipientStore implements RecipientIdCreator, RecipientResolver, Re
statement.executeUpdate();
}
if (contact != null && contact.unregisteredTimestamp() != null) {
markUnregisteredAndSplitIfNecessary(connection, recipientId);
markUnregisteredAndSplitIfNecessary(connection, recipientId, contact.unregisteredTimestamp());
}
rotateStorageId(connection, recipientId);
}
public void splitForStorageSyncIfNecessary(final Connection connection, final ACI aci) throws SQLException {
final var recipient = findByServiceId(connection, aci);
if (recipient.isEmpty()) {
return;
}
final var recipientId = recipient.get().id();
final var address = recipient.get().address();
if (address.pni().isEmpty() && address.number().isEmpty()) {
return;
}
logger.debug("Splitting {} for storage sync", recipientId);
final var splitAddress = new RecipientAddress(Optional.empty(),
address.pni(),
address.number(),
Optional.empty());
updateRecipientAddress(connection,
recipientId,
new RecipientAddress(address.aci(), Optional.empty(), Optional.empty(), address.username()));
resolveRecipientTrusted(connection, splitAddress);
}
public int removeStorageIdsFromLocalOnlyUnregisteredRecipients(
final Connection connection,
final Collection<StorageId> storageIds
@ -1063,7 +1086,7 @@ public class RecipientStore implements RecipientIdCreator, RecipientResolver, Re
if (recipientAddress.get().address().aci().isEmpty() || (
contact != null && contact.unregisteredTimestamp() != null
)) {
markUnregisteredAndSplitIfNecessary(connection, recipientId);
markUnregisteredAndSplitIfNecessary(connection, recipientId, System.currentTimeMillis());
}
}
}
@ -1097,7 +1120,7 @@ public class RecipientStore implements RecipientIdCreator, RecipientResolver, Re
if (registered) {
markRegistered(connection, recipientId);
} else {
markUnregisteredAndSplitIfNecessary(connection, recipientId);
markUnregisteredAndSplitIfNecessary(connection, recipientId, System.currentTimeMillis());
}
connection.commit();
} catch (SQLException e) {
@ -1107,9 +1130,10 @@ public class RecipientStore implements RecipientIdCreator, RecipientResolver, Re
private void markUnregisteredAndSplitIfNecessary(
final Connection connection,
final RecipientId recipientId
final RecipientId recipientId,
final long unregisteredTimestamp
) throws SQLException {
markUnregistered(connection, recipientId);
markUnregistered(connection, recipientId, unregisteredTimestamp);
final var address = resolveRecipientAddress(connection, recipientId);
final var needSplit = address.aci().isPresent() && address.pni().isPresent();
logger.trace("Marking unregistered recipient {} as unregistered (and split={}): {}",
@ -1156,7 +1180,11 @@ public class RecipientStore implements RecipientIdCreator, RecipientResolver, Re
}
}
private void markUnregistered(final Connection connection, final RecipientId recipientId) throws SQLException {
private void markUnregistered(
final Connection connection,
final RecipientId recipientId,
final long unregisteredTimestamp
) throws SQLException {
final var sql = (
"""
UPDATE %s
@ -1165,7 +1193,7 @@ public class RecipientStore implements RecipientIdCreator, RecipientResolver, Re
"""
).formatted(TABLE_RECIPIENT);
try (final var statement = connection.prepareStatement(sql)) {
statement.setLong(1, System.currentTimeMillis());
statement.setLong(1, unregisteredTimestamp);
statement.setLong(2, recipientId.id());
statement.executeUpdate();
}

View File

@ -298,7 +298,7 @@ public class StickerStore {
"""
SELECT s.pack_id
FROM %s s
WHERE s.storage_id IS NULL AND (s.installed = TRUE OR s.deleted_timestamp > 0)
WHERE s.storage_id IS NULL AND s.installed = TRUE
"""
).formatted(TABLE_STICKER);
final var updateSql = (

View File

@ -24,6 +24,7 @@ import org.whispersystems.signalservice.internal.storage.protos.ContactRecord.Id
import java.sql.Connection;
import java.sql.SQLException;
import java.util.Arrays;
import java.util.Collection;
import java.util.Objects;
import java.util.Optional;
import java.util.regex.Pattern;
@ -56,6 +57,29 @@ public class ContactRecordProcessor extends DefaultStorageRecordProcessor<Signal
this.selfNumber = account.getNumber();
}
public void prepare(final Collection<SignalContactRecord> remoteRecords) throws SQLException {
for (final var remoteRecord : remoteRecords) {
if (isInvalid(remoteRecord)) {
continue;
}
final var remote = remoteRecord.getProto();
final var aci = ACI.parseOrNull(remote.aci, remote.aciBinary);
final var pni = PNI.parseOrNull(remote.pni, remote.pniBinary);
if (shouldSplitForStorageSync(remote.unregisteredAtTimestamp, aci, pni, remote.e164)) {
account.getRecipientStore().splitForStorageSyncIfNecessary(connection, aci);
}
}
}
static boolean shouldSplitForStorageSync(
final long unregisteredAtTimestamp,
final ACI aci,
final PNI pni,
final String e164
) {
return unregisteredAtTimestamp > 0 && aci != null && pni == null && e164.isEmpty();
}
/**
* Error cases:
* - You can't have a contact record without an ACI or PNI.

View File

@ -63,16 +63,17 @@ public class StickerPackRecordProcessor extends DefaultStorageRecordProcessor<Si
final var remote = remoteRecord.getProto();
final var local = localRecord.getProto();
final var isRemoteDeleted = remote.deletedAtTimestamp > 0;
final var isLocalDeleted = local.deletedAtTimestamp > 0;
if (isRemoteDeleted && isLocalDeleted && local.deletedAtTimestamp > remote.deletedAtTimestamp) {
if (shouldKeepLocalDeletion(remote.deletedAtTimestamp, local.deletedAtTimestamp)) {
return localRecord;
}
return remoteRecord;
}
static boolean shouldKeepLocalDeletion(final long remoteDeletedAt, final long localDeletedAt) {
return remoteDeletedAt > 0 && localDeletedAt > 0 && localDeletedAt < remoteDeletedAt;
}
@Override
protected void insertLocal(final SignalStickerPackRecord record) throws SQLException {
account.getStickerStore().upsertFromStorageSync(connection, record);

View File

@ -0,0 +1,33 @@
package org.asamk.signal.manager.syncStorage;
import org.junit.jupiter.api.Test;
import org.signal.core.models.ServiceId.ACI;
import org.signal.core.models.ServiceId.PNI;
import java.util.UUID;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertTrue;
class StorageRecordProcessorTest {
@Test
void splitsOnlyUnregisteredAciOnlyRecords() {
final var aci = ACI.from(UUID.randomUUID());
final var pni = PNI.from(UUID.randomUUID());
assertTrue(ContactRecordProcessor.shouldSplitForStorageSync(1, aci, null, ""));
assertFalse(ContactRecordProcessor.shouldSplitForStorageSync(0, aci, null, ""));
assertFalse(ContactRecordProcessor.shouldSplitForStorageSync(1, null, null, ""));
assertFalse(ContactRecordProcessor.shouldSplitForStorageSync(1, aci, pni, ""));
assertFalse(ContactRecordProcessor.shouldSplitForStorageSync(1, aci, null, "+12025550123"));
}
@Test
void keepsOlderLocalStickerDeletion() {
assertTrue(StickerPackRecordProcessor.shouldKeepLocalDeletion(200, 100));
assertFalse(StickerPackRecordProcessor.shouldKeepLocalDeletion(100, 200));
assertFalse(StickerPackRecordProcessor.shouldKeepLocalDeletion(200, 0));
assertFalse(StickerPackRecordProcessor.shouldKeepLocalDeletion(0, 100));
}
}