Fix gv2 left group locally bugs in backup and compose.

This commit is contained in:
Cody Henthorne 2025-01-10 13:51:16 -05:00 committed by Greyson Parrelli
parent 123c282610
commit a63fd5a584
6 changed files with 22 additions and 12 deletions

View file

@ -513,7 +513,8 @@ object BackupRepository {
eventTimer.emit("store-db-snapshot")
val exportState = ExportState(backupTime = currentTime, mediaBackupEnabled = mediaBackupEnabled)
val selfRecipientId = dbSnapshot.recipientTable.getByAci(signalStoreSnapshot.accountValues.aci!!).get().toLong().let { RecipientId.from(it) }
val selfAci = signalStoreSnapshot.accountValues.aci!!
val selfRecipientId = dbSnapshot.recipientTable.getByAci(selfAci).get().toLong().let { RecipientId.from(it) }
var frameCount = 0L
@ -542,7 +543,7 @@ object BackupRepository {
}
progressEmitter?.onRecipient()
RecipientArchiveProcessor.export(dbSnapshot, signalStoreSnapshot, exportState, selfRecipientId) {
RecipientArchiveProcessor.export(dbSnapshot, signalStoreSnapshot, exportState, selfRecipientId, selfAci) {
writer.write(it)
eventTimer.emit("recipient")
frameCount++

View file

@ -22,6 +22,7 @@ import org.thoughtcrime.securesms.database.model.databaseprotos.RecipientExtras
import org.thoughtcrime.securesms.keyvalue.SignalStore
import org.thoughtcrime.securesms.profiles.ProfileName
import org.thoughtcrime.securesms.recipients.RecipientId
import org.whispersystems.signalservice.api.push.ServiceId
/**
* Fetches all individual contacts for backups and returns the result as an iterator.
@ -76,7 +77,7 @@ fun RecipientTable.getContactsForBackup(selfId: Long): ContactArchiveExporter {
return ContactArchiveExporter(cursor, selfId)
}
fun RecipientTable.getGroupsForBackup(): GroupArchiveExporter {
fun RecipientTable.getGroupsForBackup(selfAci: ServiceId.ACI): GroupArchiveExporter {
val cursor = readableDatabase
.select(
"${RecipientTable.TABLE_NAME}.${RecipientTable.ID}",
@ -87,6 +88,7 @@ fun RecipientTable.getGroupsForBackup(): GroupArchiveExporter {
"${GroupTable.TABLE_NAME}.${GroupTable.V2_MASTER_KEY}",
"${GroupTable.TABLE_NAME}.${GroupTable.SHOW_AS_STORY_STATE}",
"${GroupTable.TABLE_NAME}.${GroupTable.TITLE}",
"${GroupTable.TABLE_NAME}.${GroupTable.ACTIVE}",
"${GroupTable.TABLE_NAME}.${GroupTable.V2_DECRYPTED_GROUP}"
)
.from(
@ -103,7 +105,7 @@ fun RecipientTable.getGroupsForBackup(): GroupArchiveExporter {
)
.run()
return GroupArchiveExporter(cursor)
return GroupArchiveExporter(selfAci, cursor)
}
/**

View file

@ -27,13 +27,14 @@ import org.thoughtcrime.securesms.database.GroupTable
import org.thoughtcrime.securesms.database.RecipientTable
import org.thoughtcrime.securesms.database.RecipientTableCursorUtil
import org.thoughtcrime.securesms.groups.v2.processing.GroupsV2StateProcessor
import org.whispersystems.signalservice.api.push.ServiceId
import java.io.Closeable
/**
* Provides a nice iterable interface over a [RecipientTable] cursor, converting rows to [ArchiveRecipient]s.
* Important: Because this is backed by a cursor, you must close it. It's recommended to use `.use()` or try-with-resources.
*/
class GroupArchiveExporter(private val cursor: Cursor) : Iterator<ArchiveRecipient>, Closeable {
class GroupArchiveExporter(private val selfAci: ServiceId.ACI, private val cursor: Cursor) : Iterator<ArchiveRecipient>, Closeable {
override fun hasNext(): Boolean {
return cursor.count > 0 && !cursor.isLast
@ -47,6 +48,7 @@ class GroupArchiveExporter(private val cursor: Cursor) : Iterator<ArchiveRecipie
val extras = RecipientTableCursorUtil.getExtras(cursor)
val showAsStoryState: GroupTable.ShowAsStoryState = GroupTable.ShowAsStoryState.deserialize(cursor.requireInt(GroupTable.SHOW_AS_STORY_STATE))
val isActive: Boolean = cursor.requireBoolean(GroupTable.ACTIVE)
val decryptedGroup: DecryptedGroup = DecryptedGroup.ADAPTER.decode(cursor.requireBlob(GroupTable.V2_DECRYPTED_GROUP)!!)
return ArchiveRecipient(
@ -56,7 +58,7 @@ class GroupArchiveExporter(private val cursor: Cursor) : Iterator<ArchiveRecipie
whitelisted = cursor.requireBoolean(RecipientTable.PROFILE_SHARING),
hideStory = extras?.hideStory() ?: false,
storySendMode = showAsStoryState.toRemote(),
snapshot = decryptedGroup.toRemote()
snapshot = decryptedGroup.toRemote(isActive, selfAci)
)
)
}
@ -74,18 +76,21 @@ private fun GroupTable.ShowAsStoryState.toRemote(): Group.StorySendMode {
}
}
private fun DecryptedGroup.toRemote(): Group.GroupSnapshot? {
private fun DecryptedGroup.toRemote(isActive: Boolean, selfAci: ServiceId.ACI): Group.GroupSnapshot? {
if (this.revision == GroupsV2StateProcessor.RESTORE_PLACEHOLDER_REVISION || this.revision == GroupsV2StateProcessor.PLACEHOLDER_REVISION) {
return null
}
val selfAciBytes = selfAci.toByteString()
val memberFilter = { m: DecryptedMember -> isActive || m.aciBytes != selfAciBytes }
return Group.GroupSnapshot(
title = Group.GroupAttributeBlob(title = this.title),
avatarUrl = this.avatar,
disappearingMessagesTimer = this.disappearingMessagesTimer?.takeIf { it.duration > 0 }?.let { Group.GroupAttributeBlob(disappearingMessagesDuration = it.duration) },
accessControl = this.accessControl?.toRemote(),
version = this.revision,
members = this.members.map { it.toRemote() },
members = this.members.filter(memberFilter).map { it.toRemote() },
membersPendingProfileKey = this.pendingMembers.map { it.toRemote() },
membersPendingAdminApproval = this.requestingMembers.map { it.toRemote() },
inviteLinkPassword = this.inviteLinkPassword,

View file

@ -25,6 +25,7 @@ import org.thoughtcrime.securesms.database.SignalDatabase
import org.thoughtcrime.securesms.keyvalue.SignalStore
import org.thoughtcrime.securesms.recipients.Recipient
import org.thoughtcrime.securesms.recipients.RecipientId
import org.whispersystems.signalservice.api.push.ServiceId
/**
* Handles importing/exporting [ArchiveRecipient] frames for an archive.
@ -33,7 +34,7 @@ object RecipientArchiveProcessor {
val TAG = Log.tag(RecipientArchiveProcessor::class.java)
fun export(db: SignalDatabase, signalStore: SignalStore, exportState: ExportState, selfRecipientId: RecipientId, emitter: BackupFrameEmitter) {
fun export(db: SignalDatabase, signalStore: SignalStore, exportState: ExportState, selfRecipientId: RecipientId, selfAci: ServiceId.ACI, emitter: BackupFrameEmitter) {
val releaseChannelId = signalStore.releaseChannelValues.releaseChannelRecipientId
if (releaseChannelId != null) {
exportState.recipientIds.add(releaseChannelId.toLong())
@ -58,7 +59,7 @@ object RecipientArchiveProcessor {
}
}
db.recipientTable.getGroupsForBackup().use { reader ->
db.recipientTable.getGroupsForBackup(selfAci).use { reader ->
for (recipient in reader) {
exportState.recipientIds.add(recipient.id)
emitter.emit(Frame(recipient = recipient))

View file

@ -21,7 +21,7 @@ class InputReadyState(
val isUnauthorized: Boolean,
val threadContainsSms: Boolean
) {
private val selfMemberLevel: GroupTable.MemberLevel? = groupRecord?.memberLevel(Recipient.self())
private val selfMemberLevel: GroupTable.MemberLevel? = groupRecord?.let { if (it.isActive) it.memberLevel(Recipient.self()) else GroupTable.MemberLevel.NOT_A_MEMBER }
val isAnnouncementGroup: Boolean? = groupRecord?.isAnnouncementGroup
val isActiveGroup: Boolean? = if (selfMemberLevel == null) null else selfMemberLevel != GroupTable.MemberLevel.NOT_A_MEMBER

View file

@ -119,13 +119,14 @@ public final class GroupV2UpdateSelfProfileKeyJob extends BaseJob {
}
ByteString selfUuidBytes = Recipient.self().requireAci().toByteString();
boolean isActive = group.get().isActive();
DecryptedMember selfMember = group.get().requireV2GroupProperties().getDecryptedGroup().members
.stream()
.filter(m -> m.aciBytes.equals(selfUuidBytes))
.findFirst()
.orElse(null);
if (selfMember != null && !selfMember.profileKey.equals(selfProfileKey)) {
if (isActive && selfMember != null && !selfMember.profileKey.equals(selfProfileKey)) {
Log.w(TAG, "Profile key mismatch for group " + id + " -- enqueueing job");
foundMismatch = true;
AppDependencies.getJobManager().add(GroupV2UpdateSelfProfileKeyJob.withQueueLimits(id));