diff --git a/app/src/main/java/org/thoughtcrime/securesms/jobs/StorageSyncJob.kt b/app/src/main/java/org/thoughtcrime/securesms/jobs/StorageSyncJob.kt index d5ea6f15dd..081b704f64 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/jobs/StorageSyncJob.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/jobs/StorageSyncJob.kt @@ -368,7 +368,7 @@ class StorageSyncJob private constructor(parameters: Parameters) : BaseJob(param @Throws(IOException::class) private fun processKnownRecords(context: Context, records: StorageRecordCollection) { ContactRecordProcessor().process(records.contacts, StorageSyncHelper.KEY_GENERATOR) - GroupV1RecordProcessor(context).process(records.gv1, StorageSyncHelper.KEY_GENERATOR) + GroupV1RecordProcessor().process(records.gv1, StorageSyncHelper.KEY_GENERATOR) GroupV2RecordProcessor(context).process(records.gv2, StorageSyncHelper.KEY_GENERATOR) AccountRecordProcessor(context, freshSelf()).process(records.account, StorageSyncHelper.KEY_GENERATOR) StoryDistributionListRecordProcessor().process(records.storyDistributionLists, StorageSyncHelper.KEY_GENERATOR) diff --git a/app/src/main/java/org/thoughtcrime/securesms/storage/GroupV1RecordProcessor.java b/app/src/main/java/org/thoughtcrime/securesms/storage/GroupV1RecordProcessor.java deleted file mode 100644 index 577ea3c361..0000000000 --- a/app/src/main/java/org/thoughtcrime/securesms/storage/GroupV1RecordProcessor.java +++ /dev/null @@ -1,137 +0,0 @@ -package org.thoughtcrime.securesms.storage; - -import android.content.Context; - -import androidx.annotation.NonNull; -import androidx.annotation.Nullable; - -import org.signal.core.util.logging.Log; -import org.thoughtcrime.securesms.database.GroupTable; -import org.thoughtcrime.securesms.database.RecipientTable; -import org.thoughtcrime.securesms.database.SignalDatabase; -import org.thoughtcrime.securesms.database.model.GroupRecord; -import org.thoughtcrime.securesms.groups.BadGroupIdException; -import org.thoughtcrime.securesms.groups.GroupId; -import org.thoughtcrime.securesms.recipients.RecipientId; -import org.whispersystems.signalservice.api.storage.SignalGroupV1Record; - -import java.util.Arrays; -import java.util.Optional; - -/** - * Handles merging remote storage updates into local group v1 state. - */ -public final class GroupV1RecordProcessor extends DefaultStorageRecordProcessor { - - private static final String TAG = Log.tag(GroupV1RecordProcessor.class); - - private final GroupTable groupDatabase; - private final RecipientTable recipientTable; - - public GroupV1RecordProcessor(@NonNull Context context) { - this(SignalDatabase.groups(), SignalDatabase.recipients()); - } - - GroupV1RecordProcessor(@NonNull GroupTable groupDatabase, @NonNull RecipientTable recipientTable) { - this.groupDatabase = groupDatabase; - this.recipientTable = recipientTable; - } - - /** - * We want to catch: - * - Invalid group ID's - * - GV1 ID's that map to GV2 ID's, meaning we've already migrated them. - * - * Note: This method could be written more succinctly, but the logs are useful :) - */ - @Override - public boolean isInvalid(@NonNull SignalGroupV1Record remote) { - try { - GroupId.V1 id = GroupId.v1(remote.getGroupId()); - Optional v2Record = groupDatabase.getGroup(id.deriveV2MigrationGroupId()); - - if (v2Record.isPresent()) { - Log.w(TAG, "We already have an upgraded V2 group for this V1 group -- marking as invalid."); - return true; - } else { - return false; - } - } catch (BadGroupIdException e) { - Log.w(TAG, "Bad Group ID -- marking as invalid."); - return true; - } - } - - @Override - public @NonNull Optional getMatching(@NonNull SignalGroupV1Record record, @NonNull StorageKeyGenerator keyGenerator) { - GroupId.V1 groupId = GroupId.v1orThrow(record.getGroupId()); - - Optional recipientId = recipientTable.getByGroupId(groupId); - - return recipientId.map(recipientTable::getRecordForSync) - .map(StorageSyncModels::localToRemoteRecord) - .map(r -> r.getGroupV1().get()); - } - - @Override - public @NonNull SignalGroupV1Record merge(@NonNull SignalGroupV1Record remote, @NonNull SignalGroupV1Record local, @NonNull StorageKeyGenerator keyGenerator) { - byte[] unknownFields = remote.serializeUnknownFields(); - boolean blocked = remote.isBlocked(); - boolean profileSharing = remote.isProfileSharingEnabled(); - boolean archived = remote.isArchived(); - boolean forcedUnread = remote.isForcedUnread(); - long muteUntil = remote.getMuteUntil(); - - boolean matchesRemote = doParamsMatch(remote, unknownFields, blocked, profileSharing, archived, forcedUnread, muteUntil); - boolean matchesLocal = doParamsMatch(local, unknownFields, blocked, profileSharing, archived, forcedUnread, muteUntil); - - if (matchesRemote) { - return remote; - } else if (matchesLocal) { - return local; - } else { - return new SignalGroupV1Record.Builder(keyGenerator.generate(), remote.getGroupId(), unknownFields) - .setBlocked(blocked) - .setProfileSharingEnabled(profileSharing) - .setArchived(archived) - .setForcedUnread(forcedUnread) - .setMuteUntil(muteUntil) - .build(); - } - } - - @Override - public void insertLocal(@NonNull SignalGroupV1Record record) { - recipientTable.applyStorageSyncGroupV1Insert(record); - } - - @Override - public void updateLocal(@NonNull StorageRecordUpdate update) { - recipientTable.applyStorageSyncGroupV1Update(update); - } - - @Override - public int compare(@NonNull SignalGroupV1Record lhs, @NonNull SignalGroupV1Record rhs) { - if (Arrays.equals(lhs.getGroupId(), rhs.getGroupId())) { - return 0; - } else { - return 1; - } - } - - private boolean doParamsMatch(@NonNull SignalGroupV1Record group, - @Nullable byte[] unknownFields, - boolean blocked, - boolean profileSharing, - boolean archived, - boolean forcedUnread, - long muteUntil) - { - return Arrays.equals(unknownFields, group.serializeUnknownFields()) && - blocked == group.isBlocked() && - profileSharing == group.isProfileSharingEnabled() && - archived == group.isArchived() && - forcedUnread == group.isForcedUnread() && - muteUntil == group.getMuteUntil(); - } -} diff --git a/app/src/main/java/org/thoughtcrime/securesms/storage/GroupV1RecordProcessor.kt b/app/src/main/java/org/thoughtcrime/securesms/storage/GroupV1RecordProcessor.kt new file mode 100644 index 0000000000..91a2d341c4 --- /dev/null +++ b/app/src/main/java/org/thoughtcrime/securesms/storage/GroupV1RecordProcessor.kt @@ -0,0 +1,117 @@ +package org.thoughtcrime.securesms.storage + +import org.signal.core.util.logging.Log +import org.thoughtcrime.securesms.database.GroupTable +import org.thoughtcrime.securesms.database.RecipientTable +import org.thoughtcrime.securesms.database.SignalDatabase +import org.thoughtcrime.securesms.database.model.RecipientRecord +import org.thoughtcrime.securesms.groups.BadGroupIdException +import org.thoughtcrime.securesms.groups.GroupId +import org.whispersystems.signalservice.api.storage.SignalGroupV1Record +import org.whispersystems.signalservice.api.storage.SignalStorageRecord +import java.util.Optional + +/** + * Handles merging remote storage updates into local group v1 state. + */ +class GroupV1RecordProcessor(private val groupDatabase: GroupTable, private val recipientTable: RecipientTable) : DefaultStorageRecordProcessor() { + companion object { + private val TAG = Log.tag(GroupV1RecordProcessor::class.java) + } + + constructor() : this(SignalDatabase.groups, SignalDatabase.recipients) + + /** + * We want to catch: + * - Invalid group ID's + * - GV1 ID's that map to GV2 ID's, meaning we've already migrated them. + * + * Note: This method could be written more succinctly, but the logs are useful :) + */ + override fun isInvalid(remote: SignalGroupV1Record): Boolean { + try { + val id = GroupId.v1(remote.groupId) + val v2Record = groupDatabase.getGroup(id.deriveV2MigrationGroupId()) + + if (v2Record.isPresent) { + Log.w(TAG, "We already have an upgraded V2 group for this V1 group -- marking as invalid.") + return true + } else { + return false + } + } catch (e: BadGroupIdException) { + Log.w(TAG, "Bad Group ID -- marking as invalid.") + return true + } + } + + override fun getMatching(remote: SignalGroupV1Record, keyGenerator: StorageKeyGenerator): Optional { + val groupId = GroupId.v1orThrow(remote.groupId) + + val recipientId = recipientTable.getByGroupId(groupId) + + return recipientId + .map { recipientTable.getRecordForSync(it)!! } + .map { settings: RecipientRecord -> StorageSyncModels.localToRemoteRecord(settings) } + .map { record: SignalStorageRecord -> record.groupV1.get() } + } + + override fun merge(remote: SignalGroupV1Record, local: SignalGroupV1Record, keyGenerator: StorageKeyGenerator): SignalGroupV1Record { + val unknownFields = remote.serializeUnknownFields() + val blocked = remote.isBlocked + val profileSharing = remote.isProfileSharingEnabled + val archived = remote.isArchived + val forcedUnread = remote.isForcedUnread + val muteUntil = remote.muteUntil + + val matchesRemote = doParamsMatch(group = remote, unknownFields = unknownFields, blocked = blocked, profileSharing = profileSharing, archived = archived, forcedUnread = forcedUnread, muteUntil = muteUntil) + val matchesLocal = doParamsMatch(group = local, unknownFields = unknownFields, blocked = blocked, profileSharing = profileSharing, archived = archived, forcedUnread = forcedUnread, muteUntil = muteUntil) + + return if (matchesRemote) { + remote + } else if (matchesLocal) { + local + } else { + SignalGroupV1Record.Builder(keyGenerator.generate(), remote.groupId, unknownFields) + .setBlocked(blocked) + .setProfileSharingEnabled(profileSharing) + .setArchived(archived) + .setForcedUnread(forcedUnread) + .setMuteUntil(muteUntil) + .build() + } + } + + override fun insertLocal(record: SignalGroupV1Record) { + recipientTable.applyStorageSyncGroupV1Insert(record) + } + + override fun updateLocal(update: StorageRecordUpdate) { + recipientTable.applyStorageSyncGroupV1Update(update) + } + + override fun compare(lhs: SignalGroupV1Record, rhs: SignalGroupV1Record): Int { + return if (lhs.groupId.contentEquals(rhs.groupId)) { + 0 + } else { + 1 + } + } + + private fun doParamsMatch( + group: SignalGroupV1Record, + unknownFields: ByteArray?, + blocked: Boolean, + profileSharing: Boolean, + archived: Boolean, + forcedUnread: Boolean, + muteUntil: Long + ): Boolean { + return unknownFields.contentEquals(group.serializeUnknownFields()) && + blocked == group.isBlocked && + profileSharing == group.isProfileSharingEnabled && + archived == group.isArchived && + forcedUnread == group.isForcedUnread && + muteUntil == group.muteUntil + } +}