Fix release channel recipient ID surviving failed backup imports.

This commit is contained in:
Cody Henthorne
2026-03-05 11:46:38 -05:00
committed by jeffrey-signal
parent 2356bb6da2
commit 5b543c5212
6 changed files with 79 additions and 16 deletions

View File

@@ -1218,6 +1218,7 @@ object BackupRepository {
}
SignalStore.backup.hasInvalidBackupVersion = false
var transactionSuccessful = false
try {
// Removing all the data from the various tables is *very* expensive (i.e. can take *several* minutes) if we don't do some pre-work.
// SQLite optimizes deletes if there's no foreign keys, triggers, or WHERE clause, so that's the environment we're gonna create.
@@ -1406,11 +1407,17 @@ object BackupRepository {
stopwatch.split("fk-check")
SignalDatabase.rawDatabase.setTransactionSuccessful()
transactionSuccessful = true
} finally {
if (SignalDatabase.rawDatabase.inTransaction()) {
SignalDatabase.rawDatabase.endTransaction()
}
if (!transactionSuccessful) {
Log.w(TAG, "[import] Transaction failed, clearing release channel recipient ID from key-value store.")
SignalStore.releaseChannel.clearReleaseChannelRecipientId()
}
Log.d(TAG, "[import] Re-enabling foreign keys...")
SignalDatabase.rawDatabase.forceForeignKeyConstraintsEnabled(true)
}

View File

@@ -51,24 +51,31 @@ class CreateReleaseChannelJob private constructor(parameters: Parameters) : Base
}
if (SignalStore.releaseChannel.releaseChannelRecipientId != null) {
Log.i(TAG, "Already created Release Channel recipient ${SignalStore.releaseChannel.releaseChannelRecipientId}")
val existingId = SignalStore.releaseChannel.releaseChannelRecipientId!!
val recipient = Recipient.resolved(existingId)
val recipient = Recipient.resolved(SignalStore.releaseChannel.releaseChannelRecipientId!!)
if (recipient.profileAvatar.isNullOrEmpty() || !SignalStore.releaseChannel.hasUpdatedAvatar) {
SignalStore.releaseChannel.hasUpdatedAvatar = true
setAvatar(recipient.id)
if (recipient.hasServiceId || recipient.hasE164 || recipient.isGroup || recipient.isDistributionList || recipient.isCallLink) {
Log.w(TAG, "Release channel recipient $existingId is not a valid release channel recipient (hasServiceId: ${recipient.hasServiceId}, hasE164: ${recipient.hasE164}, isGroup: ${recipient.isGroup}, isDistributionList: ${recipient.isDistributionList}, isCallLink: ${recipient.isCallLink}). Clearing and recreating.")
SignalStore.releaseChannel.clearReleaseChannelRecipientId()
} else {
Log.i(TAG, "Already created Release Channel recipient $existingId")
if (recipient.profileAvatar.isNullOrEmpty() || !SignalStore.releaseChannel.hasUpdatedAvatar) {
SignalStore.releaseChannel.hasUpdatedAvatar = true
setAvatar(recipient.id)
}
return
}
} else {
val recipients = SignalDatabase.recipients
val releaseChannelId: RecipientId = recipients.insertReleaseChannelRecipient()
SignalStore.releaseChannel.setReleaseChannelRecipientId(releaseChannelId)
SignalStore.releaseChannel.hasUpdatedAvatar = true
recipients.setProfileName(releaseChannelId, ProfileName.asGiven("Signal"))
recipients.setMuted(releaseChannelId, Long.MAX_VALUE)
setAvatar(releaseChannelId)
}
val recipients = SignalDatabase.recipients
val releaseChannelId: RecipientId = recipients.insertReleaseChannelRecipient()
SignalStore.releaseChannel.setReleaseChannelRecipientId(releaseChannelId)
SignalStore.releaseChannel.hasUpdatedAvatar = true
recipients.setProfileName(releaseChannelId, ProfileName.asGiven("Signal"))
recipients.setMuted(releaseChannelId, Long.MAX_VALUE)
setAvatar(releaseChannelId)
}
private fun setAvatar(id: RecipientId) {

View File

@@ -92,6 +92,7 @@ import org.thoughtcrime.securesms.migrations.ProfileSharingUpdateMigrationJob;
import org.thoughtcrime.securesms.migrations.QuoteThumbnailBackfillMigrationJob;
import org.thoughtcrime.securesms.migrations.RebuildMessageSearchIndexMigrationJob;
import org.thoughtcrime.securesms.migrations.RecheckPaymentsMigrationJob;
import org.thoughtcrime.securesms.migrations.ReleaseChannelRecipientFixMigrationJob;
import org.thoughtcrime.securesms.migrations.RecipientSearchMigrationJob;
import org.thoughtcrime.securesms.migrations.ResetArchiveTierMigrationJob;
import org.thoughtcrime.securesms.migrations.SelfRegisteredStateMigrationJob;
@@ -344,6 +345,7 @@ public final class JobManagerFactories {
put(QuoteThumbnailBackfillMigrationJob.KEY, new QuoteThumbnailBackfillMigrationJob.Factory());
put(RebuildMessageSearchIndexMigrationJob.KEY, new RebuildMessageSearchIndexMigrationJob.Factory());
put(RecheckPaymentsMigrationJob.KEY, new RecheckPaymentsMigrationJob.Factory());
put(ReleaseChannelRecipientFixMigrationJob.KEY, new ReleaseChannelRecipientFixMigrationJob.Factory());
put(RecipientSearchMigrationJob.KEY, new RecipientSearchMigrationJob.Factory());
put(ResetArchiveTierMigrationJob.KEY, new ResetArchiveTierMigrationJob.Factory());
put(SelfRegisteredStateMigrationJob.KEY, new SelfRegisteredStateMigrationJob.Factory());

View File

@@ -33,6 +33,10 @@ class ReleaseChannelValues(store: KeyValueStore) : SignalStoreValues(store) {
putString(KEY_RELEASE_CHANNEL_RECIPIENT_ID, id.serialize())
}
fun clearReleaseChannelRecipientId() {
store.beginWrite().remove(KEY_RELEASE_CHANNEL_RECIPIENT_ID).apply()
}
var nextScheduledCheck by longValue(KEY_NEXT_SCHEDULED_CHECK, 0)
var previousManifestMd5 by blobValue(KEY_PREVIOUS_MANIFEST_MD5, ByteArray(0))
var highestVersionNoteReceived by integerValue(KEY_HIGHEST_VERSION_NOTE_RECEIVED, 0)

View File

@@ -194,9 +194,10 @@ public class ApplicationMigrations {
static final int SVR2_ENCLAVE_UPDATE_5 = 150;
static final int STICKER_PACK_ADDITION_2 = 151;
static final int DELETED_BY_DB_MIGRATION = 152;
static final int RELEASE_CHANNEL_RECIPIENT_FIX = 153;
}
public static final int CURRENT_VERSION = 152;
public static final int CURRENT_VERSION = 153;
/**
* This *must* be called after the {@link JobManager} has been instantiated, but *before* the call
@@ -899,6 +900,10 @@ public class ApplicationMigrations {
jobs.put(Version.DELETED_BY_DB_MIGRATION, new DatabaseMigrationJob());
}
if (lastSeenVersion < Version.RELEASE_CHANNEL_RECIPIENT_FIX) {
jobs.put(Version.RELEASE_CHANNEL_RECIPIENT_FIX, new ReleaseChannelRecipientFixMigrationJob());
}
return jobs;
}

View File

@@ -0,0 +1,38 @@
/*
* Copyright 2025 Signal Messenger, LLC
* SPDX-License-Identifier: AGPL-3.0-only
*/
package org.thoughtcrime.securesms.migrations
import org.thoughtcrime.securesms.dependencies.AppDependencies
import org.thoughtcrime.securesms.jobmanager.Job
import org.thoughtcrime.securesms.jobs.CreateReleaseChannelJob
/**
* In a failed backup flow, the release channel recipient can be incorrectly set. Fix it if that's the case.
*/
internal class ReleaseChannelRecipientFixMigrationJob private constructor(parameters: Parameters) : MigrationJob(parameters) {
companion object {
const val KEY = "ReleaseChannelRecipientFixMigrationJob"
}
constructor() : this(Parameters.Builder().build())
override fun isUiBlocking(): Boolean = false
override fun getFactoryKey(): String = KEY
override fun performMigration() {
AppDependencies.jobManager.add(CreateReleaseChannelJob.create())
}
override fun shouldRetry(e: Exception): Boolean = false
class Factory : Job.Factory<ReleaseChannelRecipientFixMigrationJob> {
override fun create(parameters: Parameters, serializedData: ByteArray?): ReleaseChannelRecipientFixMigrationJob {
return ReleaseChannelRecipientFixMigrationJob(parameters)
}
}
}