mirror of
https://github.com/signalapp/Signal-Android.git
synced 2026-02-27 21:24:42 +00:00
Migrate away from placeholder revision to support exporting pending approval groups.
This commit is contained in:
committed by
Alex Hart
parent
dc8e93a9d3
commit
5ce5326721
@@ -29,7 +29,6 @@ import org.thoughtcrime.securesms.conversation.colors.AvatarColor
|
||||
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
|
||||
|
||||
@@ -82,10 +81,6 @@ private fun GroupTable.ShowAsStoryState.toRemote(): Group.StorySendMode {
|
||||
}
|
||||
|
||||
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 }
|
||||
|
||||
|
||||
@@ -30,6 +30,7 @@ import org.thoughtcrime.securesms.database.model.databaseprotos.RecipientExtras
|
||||
import org.thoughtcrime.securesms.dependencies.AppDependencies
|
||||
import org.thoughtcrime.securesms.groups.GroupId
|
||||
import org.thoughtcrime.securesms.groups.v2.processing.GroupsV2StateProcessor
|
||||
import org.thoughtcrime.securesms.keyvalue.SignalStore
|
||||
import org.thoughtcrime.securesms.recipients.RecipientId
|
||||
import org.thoughtcrime.securesms.storage.StorageSyncHelper
|
||||
import org.whispersystems.signalservice.api.groupsv2.GroupsV2Operations
|
||||
@@ -133,6 +134,10 @@ private fun Group.MemberBanned.toLocal(): DecryptedBannedMember {
|
||||
}
|
||||
|
||||
private fun Group.GroupSnapshot.toLocal(operations: GroupsV2Operations.GroupOperations): DecryptedGroup {
|
||||
val selfAciBytes = SignalStore.account.aci?.toByteString()
|
||||
val requestingMembers = this.membersPendingAdminApproval.map { requesting -> requesting.toLocal() }
|
||||
val isPlaceholder = requestingMembers.any { it.aciBytes == selfAciBytes }
|
||||
|
||||
return DecryptedGroup(
|
||||
title = this.title?.title ?: "",
|
||||
avatar = this.avatarUrl,
|
||||
@@ -141,10 +146,11 @@ private fun Group.GroupSnapshot.toLocal(operations: GroupsV2Operations.GroupOper
|
||||
revision = this.version,
|
||||
members = this.members.map { member -> member.toLocal() },
|
||||
pendingMembers = this.membersPendingProfileKey.map { pending -> pending.toLocal(operations) },
|
||||
requestingMembers = this.membersPendingAdminApproval.map { requesting -> requesting.toLocal() },
|
||||
requestingMembers = requestingMembers,
|
||||
inviteLinkPassword = this.inviteLinkPassword,
|
||||
description = this.description?.descriptionText ?: "",
|
||||
isAnnouncementGroup = if (this.announcements_only) EnabledState.ENABLED else EnabledState.DISABLED,
|
||||
bannedMembers = this.members_banned.map { it.toLocal() }
|
||||
bannedMembers = this.members_banned.map { it.toLocal() },
|
||||
isPlaceholderGroup = isPlaceholder
|
||||
)
|
||||
}
|
||||
|
||||
@@ -138,6 +138,7 @@ import org.thoughtcrime.securesms.database.helpers.migration.V280_RemoveAttachme
|
||||
import org.thoughtcrime.securesms.database.helpers.migration.V281_RemoveArchiveTransferFile
|
||||
import org.thoughtcrime.securesms.database.helpers.migration.V282_AddSnippetMessageIdColumnToThreadTable
|
||||
import org.thoughtcrime.securesms.database.helpers.migration.V283_ViewOnceRemoteDataCleanup
|
||||
import org.thoughtcrime.securesms.database.helpers.migration.V284_SetPlaceholderGroupFlag
|
||||
import org.thoughtcrime.securesms.database.SQLiteDatabase as SignalSqliteDatabase
|
||||
|
||||
/**
|
||||
@@ -281,10 +282,11 @@ object SignalDatabaseMigrations {
|
||||
280 to V280_RemoveAttachmentIv,
|
||||
281 to V281_RemoveArchiveTransferFile,
|
||||
282 to V282_AddSnippetMessageIdColumnToThreadTable,
|
||||
283 to V283_ViewOnceRemoteDataCleanup
|
||||
283 to V283_ViewOnceRemoteDataCleanup,
|
||||
284 to V284_SetPlaceholderGroupFlag
|
||||
)
|
||||
|
||||
const val DATABASE_VERSION = 283
|
||||
const val DATABASE_VERSION = 284
|
||||
|
||||
@JvmStatic
|
||||
fun migrate(context: Application, db: SignalSqliteDatabase, oldVersion: Int, newVersion: Int) {
|
||||
|
||||
@@ -0,0 +1,45 @@
|
||||
/*
|
||||
* Copyright 2025 Signal Messenger, LLC
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
package org.thoughtcrime.securesms.database.helpers.migration
|
||||
|
||||
import android.app.Application
|
||||
import androidx.core.content.contentValuesOf
|
||||
import org.signal.core.util.logging.Log
|
||||
import org.signal.core.util.requireLong
|
||||
import org.signal.core.util.requireNonNullBlob
|
||||
import org.signal.storageservice.protos.groups.local.DecryptedGroup
|
||||
import org.thoughtcrime.securesms.database.SQLiteDatabase
|
||||
|
||||
/**
|
||||
* For all of time, we used the revision of -1 to indicate a placeholder group (i.e., pending invite approval). With
|
||||
* backups we want to be able to export those groups which require a non-negative revision. Migrates groups with a
|
||||
* revision of -1 to a group dummy revision of 0 but with the placeholder group state flag set.
|
||||
*/
|
||||
object V284_SetPlaceholderGroupFlag : SignalDatabaseMigration {
|
||||
private val TAG = Log.tag(V284_SetPlaceholderGroupFlag::class)
|
||||
|
||||
override fun migrate(context: Application, db: SQLiteDatabase, oldVersion: Int, newVersion: Int) {
|
||||
val updates = mutableListOf<Pair<Long, ByteArray>>()
|
||||
|
||||
db.query("groups", arrayOf("_id", "decrypted_group"), "revision = -1 AND decrypted_group IS NOT NULL", null, null, null, null).use { cursor ->
|
||||
while (cursor.moveToNext()) {
|
||||
val decryptedGroup = try {
|
||||
DecryptedGroup.ADAPTER.decode(cursor.requireNonNullBlob("decrypted_group"))
|
||||
} catch (e: Exception) {
|
||||
Log.w(TAG, "Unable to parse group state", e)
|
||||
continue
|
||||
}
|
||||
|
||||
updates += cursor.requireLong("_id") to decryptedGroup.newBuilder().revision(0).isPlaceholderGroup(true).build().encode()
|
||||
}
|
||||
}
|
||||
|
||||
updates.forEach { (id, groupState) ->
|
||||
val values = contentValuesOf("decrypted_group" to groupState, "revision" to 0)
|
||||
db.update("groups", values, "_id = $id", null)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1021,15 +1021,16 @@ final class GroupManagerV2 {
|
||||
/**
|
||||
* Creates a local group from what we know before joining.
|
||||
* <p>
|
||||
* Creates as a {@link GroupsV2StateProcessor#PLACEHOLDER_REVISION} so that we know not do do a
|
||||
* full diff against this group once we learn more about this group as that would create a large
|
||||
* update message.
|
||||
* Creates as a placeholder group so that we know not do do a full diff against this group once we learn more about this
|
||||
* group as that would create a large update message.
|
||||
*/
|
||||
private DecryptedGroup createPlaceholderGroup(@NonNull DecryptedGroupJoinInfo joinInfo, boolean requestToJoin) {
|
||||
DecryptedGroup.Builder group = new DecryptedGroup.Builder()
|
||||
.title(joinInfo.title)
|
||||
.avatar(joinInfo.avatar)
|
||||
.revision(GroupsV2StateProcessor.PLACEHOLDER_REVISION);
|
||||
.description(joinInfo.description)
|
||||
.revision(joinInfo.revision)
|
||||
.isPlaceholderGroup(true);
|
||||
|
||||
Recipient self = Recipient.self();
|
||||
ByteString selfAciBytes = selfAci.toByteString();
|
||||
|
||||
@@ -23,7 +23,6 @@ final class GroupStatePatcher {
|
||||
private static final String TAG = Log.tag(GroupStatePatcher.class);
|
||||
|
||||
static final int LATEST = Integer.MAX_VALUE;
|
||||
static final int PLACEHOLDER_REVISION = -1;
|
||||
static final int RESTORE_PLACEHOLDER_REVISION = -2;
|
||||
|
||||
private static final Comparator<DecryptedGroupChangeLog> BY_REVISION = (o1, o2) -> Integer.compare(o1.getRevision(), o2.getRevision());
|
||||
@@ -71,7 +70,7 @@ final class GroupStatePatcher {
|
||||
final int from = Math.max(0, inputState.getEarliestRevisionNumber());
|
||||
final int to = Math.min(inputState.getLatestRevisionNumber(), maximumRevisionToApply);
|
||||
|
||||
if (current != null && current.revision == PLACEHOLDER_REVISION) {
|
||||
if (current != null && current.isPlaceholderGroup) {
|
||||
Log.i(TAG, "Ignoring place holder group state");
|
||||
} else {
|
||||
stateChain.push(current, null);
|
||||
@@ -84,10 +83,11 @@ final class GroupStatePatcher {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (stateChain.getLatestState() == null && entry.getGroup() != null && current != null && current.revision == PLACEHOLDER_REVISION) {
|
||||
if (stateChain.getLatestState() == null && entry.getGroup() != null && current != null && current.isPlaceholderGroup) {
|
||||
DecryptedGroup previousState = entry.getGroup().newBuilder()
|
||||
.title(current.title)
|
||||
.avatar(current.avatar)
|
||||
.description(current.description)
|
||||
.build();
|
||||
|
||||
stateChain.push(previousState, null);
|
||||
|
||||
@@ -70,12 +70,6 @@ class GroupsV2StateProcessor private constructor(
|
||||
|
||||
const val LATEST = GroupStatePatcher.LATEST
|
||||
|
||||
/**
|
||||
* Used to mark a group state as a placeholder when there is partial knowledge (title and avater)
|
||||
* gathered from a group join link.
|
||||
*/
|
||||
const val PLACEHOLDER_REVISION = GroupStatePatcher.PLACEHOLDER_REVISION
|
||||
|
||||
/**
|
||||
* Used to mark a group state as a placeholder when you have no knowledge at all of the group
|
||||
* e.g. from a group master key from a storage service restore.
|
||||
|
||||
Reference in New Issue
Block a user