Search in sources :

Example 1 with IdDifferenceResult

use of org.thoughtcrime.securesms.storage.StorageSyncHelper.IdDifferenceResult in project Signal-Android by WhisperSystems.

the class StorageSyncHelperTest method findIdDifference_allOverlap.

@Test
public void findIdDifference_allOverlap() {
    IdDifferenceResult result = StorageSyncHelper.findIdDifference(keyListOf(1, 2, 3), keyListOf(1, 2, 3));
    assertTrue(result.getLocalOnlyIds().isEmpty());
    assertTrue(result.getRemoteOnlyIds().isEmpty());
    assertFalse(result.hasTypeMismatches());
}
Also used : IdDifferenceResult(org.thoughtcrime.securesms.storage.StorageSyncHelper.IdDifferenceResult) PrepareForTest(org.powermock.core.classloader.annotations.PrepareForTest) Test(org.junit.Test)

Example 2 with IdDifferenceResult

use of org.thoughtcrime.securesms.storage.StorageSyncHelper.IdDifferenceResult in project Signal-Android by WhisperSystems.

the class StorageSyncHelperTest method findIdDifference_typeMismatch_someOverlap.

@Test
public void findIdDifference_typeMismatch_someOverlap() {
    IdDifferenceResult result = StorageSyncHelper.findIdDifference(keyListOf(new HashMap<Integer, Integer>() {

        {
            put(100, 1);
            put(200, 2);
            put(300, 1);
        }
    }), keyListOf(new HashMap<Integer, Integer>() {

        {
            put(100, 1);
            put(200, 1);
            put(400, 1);
        }
    }));
    assertContentsEqual(Arrays.asList(StorageId.forType(byteArray(300), 1)), result.getRemoteOnlyIds());
    assertContentsEqual(Arrays.asList(StorageId.forType(byteArray(400), 1)), result.getLocalOnlyIds());
    assertTrue(result.hasTypeMismatches());
}
Also used : IdDifferenceResult(org.thoughtcrime.securesms.storage.StorageSyncHelper.IdDifferenceResult) HashMap(java.util.HashMap) PrepareForTest(org.powermock.core.classloader.annotations.PrepareForTest) Test(org.junit.Test)

Example 3 with IdDifferenceResult

use of org.thoughtcrime.securesms.storage.StorageSyncHelper.IdDifferenceResult in project Signal-Android by WhisperSystems.

the class StorageSyncHelperTest method findIdDifference_noOverlap.

@Test
public void findIdDifference_noOverlap() {
    IdDifferenceResult result = StorageSyncHelper.findIdDifference(keyListOf(1, 2, 3), keyListOf(4, 5, 6));
    assertContentsEqual(keyListOf(1, 2, 3), result.getRemoteOnlyIds());
    assertContentsEqual(keyListOf(4, 5, 6), result.getLocalOnlyIds());
    assertFalse(result.hasTypeMismatches());
}
Also used : IdDifferenceResult(org.thoughtcrime.securesms.storage.StorageSyncHelper.IdDifferenceResult) PrepareForTest(org.powermock.core.classloader.annotations.PrepareForTest) Test(org.junit.Test)

Example 4 with IdDifferenceResult

use of org.thoughtcrime.securesms.storage.StorageSyncHelper.IdDifferenceResult in project Signal-Android by WhisperSystems.

the class StorageSyncJob method performSync.

private boolean performSync() throws IOException, RetryLaterException, InvalidKeyException {
    final Stopwatch stopwatch = new Stopwatch("StorageSync");
    final SQLiteDatabase db = SignalDatabase.getRawDatabase();
    final SignalServiceAccountManager accountManager = ApplicationDependencies.getSignalServiceAccountManager();
    final UnknownStorageIdDatabase storageIdDatabase = SignalDatabase.unknownStorageIds();
    final StorageKey storageServiceKey = SignalStore.storageService().getOrCreateStorageKey();
    final SignalStorageManifest localManifest = SignalStore.storageService().getManifest();
    final SignalStorageManifest remoteManifest = accountManager.getStorageManifestIfDifferentVersion(storageServiceKey, localManifest.getVersion()).or(localManifest);
    stopwatch.split("remote-manifest");
    Recipient self = freshSelf();
    boolean needsMultiDeviceSync = false;
    boolean needsForcePush = false;
    if (self.getStorageServiceId() == null) {
        Log.w(TAG, "No storageId for self. Generating.");
        SignalDatabase.recipients().updateStorageId(self.getId(), StorageSyncHelper.generateKey());
        self = freshSelf();
    }
    Log.i(TAG, "Our version: " + localManifest.getVersion() + ", their version: " + remoteManifest.getVersion());
    if (remoteManifest.getVersion() > localManifest.getVersion()) {
        Log.i(TAG, "[Remote Sync] Newer manifest version found!");
        List<StorageId> localStorageIdsBeforeMerge = getAllLocalStorageIds(context, self);
        IdDifferenceResult idDifference = StorageSyncHelper.findIdDifference(remoteManifest.getStorageIds(), localStorageIdsBeforeMerge);
        if (idDifference.hasTypeMismatches() && SignalStore.account().isPrimaryDevice()) {
            Log.w(TAG, "[Remote Sync] Found type mismatches in the ID sets! Scheduling a force push after this sync completes.");
            needsForcePush = true;
        }
        Log.i(TAG, "[Remote Sync] Pre-Merge ID Difference :: " + idDifference);
        stopwatch.split("remote-id-diff");
        if (!idDifference.isEmpty()) {
            Log.i(TAG, "[Remote Sync] Retrieving records for key difference.");
            List<SignalStorageRecord> remoteOnly = accountManager.readStorageRecords(storageServiceKey, idDifference.getRemoteOnlyIds());
            stopwatch.split("remote-records");
            if (remoteOnly.size() != idDifference.getRemoteOnlyIds().size()) {
                Log.w(TAG, "[Remote Sync] Could not find all remote-only records! Requested: " + idDifference.getRemoteOnlyIds().size() + ", Found: " + remoteOnly.size() + ". These stragglers should naturally get deleted during the sync.");
            }
            List<SignalContactRecord> remoteContacts = new LinkedList<>();
            List<SignalGroupV1Record> remoteGv1 = new LinkedList<>();
            List<SignalGroupV2Record> remoteGv2 = new LinkedList<>();
            List<SignalAccountRecord> remoteAccount = new LinkedList<>();
            List<SignalStorageRecord> remoteUnknown = new LinkedList<>();
            for (SignalStorageRecord remote : remoteOnly) {
                if (remote.getContact().isPresent()) {
                    remoteContacts.add(remote.getContact().get());
                } else if (remote.getGroupV1().isPresent()) {
                    remoteGv1.add(remote.getGroupV1().get());
                } else if (remote.getGroupV2().isPresent()) {
                    remoteGv2.add(remote.getGroupV2().get());
                } else if (remote.getAccount().isPresent()) {
                    remoteAccount.add(remote.getAccount().get());
                } else if (remote.getId().isUnknown()) {
                    remoteUnknown.add(remote);
                } else {
                    Log.w(TAG, "Bad record! Type is a known value (" + remote.getId().getType() + "), but doesn't have a matching inner record. Dropping it.");
                }
            }
            db.beginTransaction();
            try {
                self = freshSelf();
                Log.i(TAG, "[Remote Sync] Remote-Only :: Contacts: " + remoteContacts.size() + ", GV1: " + remoteGv1.size() + ", GV2: " + remoteGv2.size() + ", Account: " + remoteAccount.size());
                new ContactRecordProcessor(context, self).process(remoteContacts, StorageSyncHelper.KEY_GENERATOR);
                new GroupV1RecordProcessor(context).process(remoteGv1, StorageSyncHelper.KEY_GENERATOR);
                new GroupV2RecordProcessor(context).process(remoteGv2, StorageSyncHelper.KEY_GENERATOR);
                self = freshSelf();
                new AccountRecordProcessor(context, self).process(remoteAccount, StorageSyncHelper.KEY_GENERATOR);
                List<SignalStorageRecord> unknownInserts = remoteUnknown;
                List<StorageId> unknownDeletes = Stream.of(idDifference.getLocalOnlyIds()).filter(StorageId::isUnknown).toList();
                Log.i(TAG, "[Remote Sync] Unknowns :: " + unknownInserts.size() + " inserts, " + unknownDeletes.size() + " deletes");
                storageIdDatabase.insert(unknownInserts);
                storageIdDatabase.delete(unknownDeletes);
                db.setTransactionSuccessful();
            } finally {
                db.endTransaction();
                ApplicationDependencies.getDatabaseObserver().notifyConversationListListeners();
                stopwatch.split("remote-merge-transaction");
            }
        } else {
            Log.i(TAG, "[Remote Sync] Remote version was newer, but there were no remote-only IDs.");
        }
    } else if (remoteManifest.getVersion() < localManifest.getVersion()) {
        Log.w(TAG, "[Remote Sync] Remote version was older. User might have switched accounts.");
    }
    if (remoteManifest != localManifest) {
        Log.i(TAG, "[Remote Sync] Saved new manifest. Now at version: " + remoteManifest.getVersion());
        SignalStore.storageService().setManifest(remoteManifest);
    }
    Log.i(TAG, "We are up-to-date with the remote storage state.");
    final WriteOperationResult remoteWriteOperation;
    db.beginTransaction();
    try {
        self = freshSelf();
        List<StorageId> localStorageIds = getAllLocalStorageIds(context, self);
        IdDifferenceResult idDifference = StorageSyncHelper.findIdDifference(remoteManifest.getStorageIds(), localStorageIds);
        List<SignalStorageRecord> remoteInserts = buildLocalStorageRecords(context, self, idDifference.getLocalOnlyIds());
        List<byte[]> remoteDeletes = Stream.of(idDifference.getRemoteOnlyIds()).map(StorageId::getRaw).toList();
        Log.i(TAG, "ID Difference :: " + idDifference);
        remoteWriteOperation = new WriteOperationResult(new SignalStorageManifest(remoteManifest.getVersion() + 1, localStorageIds), remoteInserts, remoteDeletes);
        db.setTransactionSuccessful();
    } finally {
        db.endTransaction();
        stopwatch.split("local-data-transaction");
    }
    if (!remoteWriteOperation.isEmpty()) {
        Log.i(TAG, "We have something to write remotely.");
        Log.i(TAG, "WriteOperationResult :: " + remoteWriteOperation);
        StorageSyncValidations.validate(remoteWriteOperation, remoteManifest, needsForcePush, self);
        Optional<SignalStorageManifest> conflict = accountManager.writeStorageRecords(storageServiceKey, remoteWriteOperation.getManifest(), remoteWriteOperation.getInserts(), remoteWriteOperation.getDeletes());
        if (conflict.isPresent()) {
            Log.w(TAG, "Hit a conflict when trying to resolve the conflict! Retrying.");
            throw new RetryLaterException();
        }
        Log.i(TAG, "Saved new manifest. Now at version: " + remoteWriteOperation.getManifest().getVersion());
        SignalStore.storageService().setManifest(remoteWriteOperation.getManifest());
        stopwatch.split("remote-write");
        needsMultiDeviceSync = true;
    } else {
        Log.i(TAG, "No remote writes needed. Still at version: " + remoteManifest.getVersion());
    }
    if (needsForcePush && SignalStore.account().isPrimaryDevice()) {
        Log.w(TAG, "Scheduling a force push.");
        ApplicationDependencies.getJobManager().add(new StorageForcePushJob());
    }
    stopwatch.stop(TAG);
    return needsMultiDeviceSync;
}
Also used : SignalContactRecord(org.whispersystems.signalservice.api.storage.SignalContactRecord) Stopwatch(org.thoughtcrime.securesms.util.Stopwatch) StorageId(org.whispersystems.signalservice.api.storage.StorageId) GroupV2RecordProcessor(org.thoughtcrime.securesms.storage.GroupV2RecordProcessor) SignalGroupV2Record(org.whispersystems.signalservice.api.storage.SignalGroupV2Record) UnknownStorageIdDatabase(org.thoughtcrime.securesms.database.UnknownStorageIdDatabase) StorageKey(org.whispersystems.signalservice.api.storage.StorageKey) SignalAccountRecord(org.whispersystems.signalservice.api.storage.SignalAccountRecord) SignalStorageManifest(org.whispersystems.signalservice.api.storage.SignalStorageManifest) IdDifferenceResult(org.thoughtcrime.securesms.storage.StorageSyncHelper.IdDifferenceResult) GroupV1RecordProcessor(org.thoughtcrime.securesms.storage.GroupV1RecordProcessor) SignalServiceAccountManager(org.whispersystems.signalservice.api.SignalServiceAccountManager) SignalStorageRecord(org.whispersystems.signalservice.api.storage.SignalStorageRecord) Recipient(org.thoughtcrime.securesms.recipients.Recipient) WriteOperationResult(org.thoughtcrime.securesms.storage.StorageSyncHelper.WriteOperationResult) LinkedList(java.util.LinkedList) SignalGroupV1Record(org.whispersystems.signalservice.api.storage.SignalGroupV1Record) AccountRecordProcessor(org.thoughtcrime.securesms.storage.AccountRecordProcessor) SQLiteDatabase(net.zetetic.database.sqlcipher.SQLiteDatabase) ContactRecordProcessor(org.thoughtcrime.securesms.storage.ContactRecordProcessor) RetryLaterException(org.thoughtcrime.securesms.transport.RetryLaterException)

Example 5 with IdDifferenceResult

use of org.thoughtcrime.securesms.storage.StorageSyncHelper.IdDifferenceResult in project Signal-Android by WhisperSystems.

the class StorageSyncHelperTest method findIdDifference_someOverlap.

@Test
public void findIdDifference_someOverlap() {
    IdDifferenceResult result = StorageSyncHelper.findIdDifference(keyListOf(1, 2, 3), keyListOf(2, 3, 4));
    assertContentsEqual(keyListOf(1), result.getRemoteOnlyIds());
    assertContentsEqual(keyListOf(4), result.getLocalOnlyIds());
    assertFalse(result.hasTypeMismatches());
}
Also used : IdDifferenceResult(org.thoughtcrime.securesms.storage.StorageSyncHelper.IdDifferenceResult) PrepareForTest(org.powermock.core.classloader.annotations.PrepareForTest) Test(org.junit.Test)

Aggregations

IdDifferenceResult (org.thoughtcrime.securesms.storage.StorageSyncHelper.IdDifferenceResult)6 Test (org.junit.Test)5 PrepareForTest (org.powermock.core.classloader.annotations.PrepareForTest)5 HashMap (java.util.HashMap)2 LinkedList (java.util.LinkedList)1 SQLiteDatabase (net.zetetic.database.sqlcipher.SQLiteDatabase)1 UnknownStorageIdDatabase (org.thoughtcrime.securesms.database.UnknownStorageIdDatabase)1 Recipient (org.thoughtcrime.securesms.recipients.Recipient)1 AccountRecordProcessor (org.thoughtcrime.securesms.storage.AccountRecordProcessor)1 ContactRecordProcessor (org.thoughtcrime.securesms.storage.ContactRecordProcessor)1 GroupV1RecordProcessor (org.thoughtcrime.securesms.storage.GroupV1RecordProcessor)1 GroupV2RecordProcessor (org.thoughtcrime.securesms.storage.GroupV2RecordProcessor)1 WriteOperationResult (org.thoughtcrime.securesms.storage.StorageSyncHelper.WriteOperationResult)1 RetryLaterException (org.thoughtcrime.securesms.transport.RetryLaterException)1 Stopwatch (org.thoughtcrime.securesms.util.Stopwatch)1 SignalServiceAccountManager (org.whispersystems.signalservice.api.SignalServiceAccountManager)1 SignalAccountRecord (org.whispersystems.signalservice.api.storage.SignalAccountRecord)1 SignalContactRecord (org.whispersystems.signalservice.api.storage.SignalContactRecord)1 SignalGroupV1Record (org.whispersystems.signalservice.api.storage.SignalGroupV1Record)1 SignalGroupV2Record (org.whispersystems.signalservice.api.storage.SignalGroupV2Record)1