mirror of
https://github.com/signalapp/Signal-Android.git
synced 2025-12-20 11:08:31 +00:00
Do not include release notes in chat exports.
This commit is contained in:
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -66,11 +66,6 @@ class ArchiveImportExportTests {
|
||||
runTests { it.matches(Regex("^chat_%d%d.binproto$")) }
|
||||
}
|
||||
|
||||
// @Test
|
||||
fun chatReleaseNotes() {
|
||||
runTests { it.startsWith("chat_release_notes_") }
|
||||
}
|
||||
|
||||
// @Test
|
||||
fun chatFolders() {
|
||||
runTests { it.startsWith("chat_folder_") }
|
||||
|
||||
@@ -5,19 +5,17 @@
|
||||
|
||||
package org.thoughtcrime.securesms.backup.v2.database
|
||||
|
||||
import org.signal.core.util.SqlUtil
|
||||
import org.signal.core.util.forEach
|
||||
import org.signal.core.util.requireInt
|
||||
import org.signal.core.util.requireLong
|
||||
import org.signal.core.util.select
|
||||
import org.thoughtcrime.securesms.backup.v2.ExportState
|
||||
import org.thoughtcrime.securesms.backup.v2.exporters.ChatArchiveExporter
|
||||
import org.thoughtcrime.securesms.database.MessageTable
|
||||
import org.thoughtcrime.securesms.database.RecipientTable
|
||||
import org.thoughtcrime.securesms.database.SignalDatabase
|
||||
import org.thoughtcrime.securesms.database.ThreadTable
|
||||
|
||||
fun ThreadTable.getThreadsForBackup(db: SignalDatabase, exportState: ExportState, includeImageWallpapers: Boolean): ChatArchiveExporter {
|
||||
val notReleaseNoteClause = exportState.releaseNoteRecipientId?.let {
|
||||
"AND ${ThreadTable.TABLE_NAME}.${ThreadTable.RECIPIENT_ID} != $it"
|
||||
} ?: ""
|
||||
|
||||
//language=sql
|
||||
val query = """
|
||||
SELECT
|
||||
@@ -37,39 +35,9 @@ fun ThreadTable.getThreadsForBackup(db: SignalDatabase, exportState: ExportState
|
||||
LEFT OUTER JOIN ${RecipientTable.TABLE_NAME} ON ${ThreadTable.TABLE_NAME}.${ThreadTable.RECIPIENT_ID} = ${RecipientTable.TABLE_NAME}.${RecipientTable.ID}
|
||||
WHERE
|
||||
${RecipientTable.TABLE_NAME}.${RecipientTable.TYPE} NOT IN (${RecipientTable.RecipientType.DISTRIBUTION_LIST.id}, ${RecipientTable.RecipientType.CALL_LINK.id})
|
||||
$notReleaseNoteClause
|
||||
"""
|
||||
val cursor = readableDatabase.query(query)
|
||||
|
||||
return ChatArchiveExporter(cursor, db, exportState, includeImageWallpapers)
|
||||
}
|
||||
|
||||
fun ThreadTable.getThreadGroupStatus(messageIds: Collection<Long>): Map<Long, Boolean> {
|
||||
if (messageIds.isEmpty()) {
|
||||
return emptyMap()
|
||||
}
|
||||
|
||||
val out: MutableMap<Long, Boolean> = mutableMapOf()
|
||||
|
||||
val query = SqlUtil.buildFastCollectionQuery("${MessageTable.TABLE_NAME}.${MessageTable.ID}", messageIds)
|
||||
readableDatabase
|
||||
.select(
|
||||
"${MessageTable.TABLE_NAME}.${MessageTable.ID}",
|
||||
"${RecipientTable.TABLE_NAME}.${RecipientTable.TYPE}"
|
||||
)
|
||||
.from(
|
||||
"""
|
||||
${MessageTable.TABLE_NAME}
|
||||
INNER JOIN ${ThreadTable.TABLE_NAME} ON ${MessageTable.TABLE_NAME}.${MessageTable.THREAD_ID} = ${ThreadTable.TABLE_NAME}.${ThreadTable.ID}
|
||||
INNER JOIN ${RecipientTable.TABLE_NAME} ON ${ThreadTable.TABLE_NAME}.${ThreadTable.RECIPIENT_ID} = ${RecipientTable.TABLE_NAME}.${RecipientTable.ID}
|
||||
"""
|
||||
)
|
||||
.where(query.where, query.whereArgs)
|
||||
.run()
|
||||
.forEach { cursor ->
|
||||
val messageId = cursor.requireLong(MessageTable.ID)
|
||||
val type = cursor.requireInt(RecipientTable.TYPE)
|
||||
out[messageId] = type != RecipientTable.RecipientType.INDIVIDUAL.id
|
||||
}
|
||||
|
||||
return out
|
||||
}
|
||||
|
||||
@@ -182,7 +182,7 @@ class ChatItemArchiveExporter(
|
||||
val builder = record.toBasicChatItemBuilder(selfRecipientId, extraData.groupReceiptsById[id], exportState, backupStartTime)
|
||||
transformTimer.emit("basic")
|
||||
|
||||
if (builder == null) {
|
||||
if (builder == null || builder.authorId == exportState.releaseNoteRecipientId) {
|
||||
continue
|
||||
}
|
||||
|
||||
@@ -1639,7 +1639,7 @@ private fun ChatItem.validateChatItem(exportState: ExportState, selfRecipientId:
|
||||
return null
|
||||
}
|
||||
|
||||
if (this.incoming != null && this.authorId != exportState.releaseNoteRecipientId && exportState.recipientIdToAci[this.authorId] == null && exportState.recipientIdToE164[this.authorId] == null) {
|
||||
if (this.incoming != null && exportState.recipientIdToAci[this.authorId] == null && exportState.recipientIdToE164[this.authorId] == null) {
|
||||
Log.w(TAG, ExportSkips.incomingMessageAuthorDoesNotHaveAciOrE164(this.dateSent))
|
||||
return null
|
||||
}
|
||||
|
||||
@@ -3168,6 +3168,10 @@ class AttachmentTable(
|
||||
}
|
||||
|
||||
private fun buildAttachmentsThatNeedUploadQuery(transferStateFilter: String = "$ARCHIVE_TRANSFER_STATE IN (${ArchiveTransferState.NONE.value}, ${ArchiveTransferState.TEMPORARY_FAILURE.value})"): String {
|
||||
val notReleaseChannelClause = SignalStore.releaseChannel.releaseChannelRecipientId?.let {
|
||||
"(${MessageTable.TABLE_NAME}.${MessageTable.FROM_RECIPIENT_ID} != ${it.toLong()}) AND"
|
||||
} ?: ""
|
||||
|
||||
return """
|
||||
$transferStateFilter AND
|
||||
$DATA_FILE NOT NULL AND
|
||||
@@ -3176,6 +3180,7 @@ class AttachmentTable(
|
||||
$TRANSFER_STATE = $TRANSFER_PROGRESS_DONE AND
|
||||
(${MessageTable.STORY_TYPE} = 0 OR ${MessageTable.STORY_TYPE} IS NULL) AND
|
||||
(${MessageTable.TABLE_NAME}.${MessageTable.EXPIRES_IN} <= 0 OR ${MessageTable.TABLE_NAME}.${MessageTable.EXPIRES_IN} > ${ChatItemArchiveExporter.EXPIRATION_CUTOFF.inWholeMilliseconds}) AND
|
||||
$notReleaseChannelClause
|
||||
$CONTENT_TYPE != '${MediaUtil.LONG_TEXT}' AND
|
||||
${MessageTable.TABLE_NAME}.${MessageTable.VIEW_ONCE} = 0
|
||||
"""
|
||||
|
||||
@@ -151,6 +151,7 @@ import org.thoughtcrime.securesms.database.helpers.migration.V294_RemoveLastReso
|
||||
import org.thoughtcrime.securesms.database.helpers.migration.V295_AddLastRestoreKeyTypeTableIfMissingMigration
|
||||
import org.thoughtcrime.securesms.database.helpers.migration.V296_RemovePollVoteConstraint
|
||||
import org.thoughtcrime.securesms.database.helpers.migration.V297_AddPinnedMessageColumns
|
||||
import org.thoughtcrime.securesms.database.helpers.migration.V298_DoNotBackupReleaseNotes
|
||||
import org.thoughtcrime.securesms.database.SQLiteDatabase as SignalSqliteDatabase
|
||||
|
||||
/**
|
||||
@@ -308,10 +309,11 @@ object SignalDatabaseMigrations {
|
||||
294 to V294_RemoveLastResortKeyTupleColumnConstraintMigration,
|
||||
295 to V295_AddLastRestoreKeyTypeTableIfMissingMigration,
|
||||
296 to V296_RemovePollVoteConstraint,
|
||||
297 to V297_AddPinnedMessageColumns
|
||||
297 to V297_AddPinnedMessageColumns,
|
||||
298 to V298_DoNotBackupReleaseNotes
|
||||
)
|
||||
|
||||
const val DATABASE_VERSION = 297
|
||||
const val DATABASE_VERSION = 298
|
||||
|
||||
@JvmStatic
|
||||
fun migrate(context: Application, db: SignalSqliteDatabase, oldVersion: Int, newVersion: Int) {
|
||||
|
||||
@@ -0,0 +1,52 @@
|
||||
/*
|
||||
* Copyright 2025 Signal Messenger, LLC
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
package org.thoughtcrime.securesms.database.helpers.migration
|
||||
|
||||
import android.app.Application
|
||||
import org.signal.core.util.SqlUtil
|
||||
import org.signal.core.util.logging.Log
|
||||
import org.signal.core.util.requireLong
|
||||
import org.thoughtcrime.securesms.database.KeyValueDatabase
|
||||
import org.thoughtcrime.securesms.database.SQLiteDatabase
|
||||
|
||||
object V298_DoNotBackupReleaseNotes : SignalDatabaseMigration {
|
||||
|
||||
private val TAG = Log.tag(V298_DoNotBackupReleaseNotes::class.java)
|
||||
|
||||
override fun migrate(context: Application, db: SQLiteDatabase, oldVersion: Int, newVersion: Int) {
|
||||
val releaseNoteRecipientId = getReleaseNoteRecipientId(context) ?: return
|
||||
migrateWithRecipientId(db, releaseNoteRecipientId)
|
||||
}
|
||||
|
||||
fun migrateWithRecipientId(db: SQLiteDatabase, releaseNoteRecipientId: Long) {
|
||||
db.execSQL(
|
||||
"""
|
||||
UPDATE attachment
|
||||
SET archive_transfer_state = 0
|
||||
WHERE message_id IN (
|
||||
SELECT _id FROM message WHERE from_recipient_id = $releaseNoteRecipientId
|
||||
)
|
||||
"""
|
||||
)
|
||||
}
|
||||
|
||||
private fun getReleaseNoteRecipientId(context: Application): Long? {
|
||||
return if (KeyValueDatabase.exists(context)) {
|
||||
val keyValueDatabase = KeyValueDatabase.getInstance(context).readableDatabase
|
||||
keyValueDatabase.query("key_value", arrayOf("value"), "key = ?", SqlUtil.buildArgs("releasechannel.recipient_id"), null, null, null).use { cursor ->
|
||||
if (cursor.moveToFirst()) {
|
||||
cursor.requireLong("value")
|
||||
} else {
|
||||
Log.w(TAG, "Release note channel recipient ID not found in KV database!")
|
||||
null
|
||||
}
|
||||
}
|
||||
} else {
|
||||
Log.w(TAG, "Pre-KV database, not doing anything.")
|
||||
null
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,198 @@
|
||||
/*
|
||||
* Copyright 2025 Signal Messenger, LLC
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
package org.thoughtcrime.securesms.database.helpers.migration
|
||||
|
||||
import android.app.Application
|
||||
import android.content.ContentValues
|
||||
import assertk.assertThat
|
||||
import assertk.assertions.isEqualTo
|
||||
import org.junit.Rule
|
||||
import org.junit.Test
|
||||
import org.junit.runner.RunWith
|
||||
import org.robolectric.RobolectricTestRunner
|
||||
import org.robolectric.annotation.Config
|
||||
import org.thoughtcrime.securesms.database.AttachmentTable
|
||||
import org.thoughtcrime.securesms.testutil.SignalDatabaseMigrationRule
|
||||
|
||||
@Suppress("ClassName")
|
||||
@RunWith(RobolectricTestRunner::class)
|
||||
@Config(manifest = Config.NONE, application = Application::class)
|
||||
class V298_DoNotBackupReleaseNotesTest {
|
||||
|
||||
@get:Rule val signalDatabaseRule = SignalDatabaseMigrationRule(297)
|
||||
|
||||
private val releaseNoteRecipientId = 100L
|
||||
private val otherRecipientId = 200L
|
||||
|
||||
@Test
|
||||
fun `migrate - attachments from release note recipient - clears archive transfer state`() {
|
||||
val messageId = insertMessage(fromRecipientId = releaseNoteRecipientId)
|
||||
val attachmentId = insertAttachment(
|
||||
messageId = messageId,
|
||||
archiveTransferState = AttachmentTable.ArchiveTransferState.FINISHED.value,
|
||||
archiveCdn = 2
|
||||
)
|
||||
|
||||
runMigration()
|
||||
|
||||
assertArchiveState(attachmentId, expectedState = AttachmentTable.ArchiveTransferState.NONE.value)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `migrate - attachments from other recipient - no change`() {
|
||||
val messageId = insertMessage(fromRecipientId = otherRecipientId)
|
||||
val attachmentId = insertAttachment(
|
||||
messageId = messageId,
|
||||
archiveTransferState = AttachmentTable.ArchiveTransferState.FINISHED.value,
|
||||
archiveCdn = 2
|
||||
)
|
||||
|
||||
runMigration()
|
||||
|
||||
assertArchiveState(attachmentId, expectedState = AttachmentTable.ArchiveTransferState.FINISHED.value)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `migrate - multiple attachments from release note recipient - all cleared`() {
|
||||
val messageId1 = insertMessage(fromRecipientId = releaseNoteRecipientId)
|
||||
val messageId2 = insertMessage(fromRecipientId = releaseNoteRecipientId)
|
||||
|
||||
val attachmentId1 = insertAttachment(
|
||||
messageId = messageId1,
|
||||
archiveTransferState = AttachmentTable.ArchiveTransferState.FINISHED.value,
|
||||
archiveCdn = 2
|
||||
)
|
||||
val attachmentId2 = insertAttachment(
|
||||
messageId = messageId2,
|
||||
archiveTransferState = AttachmentTable.ArchiveTransferState.UPLOAD_IN_PROGRESS.value,
|
||||
archiveCdn = 3
|
||||
)
|
||||
|
||||
runMigration()
|
||||
|
||||
assertArchiveState(attachmentId1, expectedState = AttachmentTable.ArchiveTransferState.NONE.value)
|
||||
assertArchiveState(attachmentId2, expectedState = AttachmentTable.ArchiveTransferState.NONE.value)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `migrate - mixed recipients - only release note attachments cleared`() {
|
||||
val releaseNoteMessageId = insertMessage(fromRecipientId = releaseNoteRecipientId)
|
||||
val otherMessageId = insertMessage(fromRecipientId = otherRecipientId)
|
||||
|
||||
val releaseNoteAttachmentId = insertAttachment(
|
||||
messageId = releaseNoteMessageId,
|
||||
archiveTransferState = AttachmentTable.ArchiveTransferState.FINISHED.value,
|
||||
archiveCdn = 2
|
||||
)
|
||||
val otherAttachmentId = insertAttachment(
|
||||
messageId = otherMessageId,
|
||||
archiveTransferState = AttachmentTable.ArchiveTransferState.FINISHED.value,
|
||||
archiveCdn = 2
|
||||
)
|
||||
|
||||
runMigration()
|
||||
|
||||
assertArchiveState(releaseNoteAttachmentId, expectedState = AttachmentTable.ArchiveTransferState.NONE.value)
|
||||
assertArchiveState(otherAttachmentId, expectedState = AttachmentTable.ArchiveTransferState.FINISHED.value)
|
||||
}
|
||||
|
||||
private fun runMigration() {
|
||||
V298_DoNotBackupReleaseNotes.migrateWithRecipientId(
|
||||
db = signalDatabaseRule.database,
|
||||
releaseNoteRecipientId = releaseNoteRecipientId
|
||||
)
|
||||
}
|
||||
|
||||
private val insertedRecipients = mutableSetOf<Long>()
|
||||
private val insertedThreads = mutableMapOf<Long, Long>()
|
||||
private var dateSentCounter = System.currentTimeMillis()
|
||||
|
||||
private fun insertRecipient(recipientId: Long): Long {
|
||||
if (recipientId in insertedRecipients) {
|
||||
return recipientId
|
||||
}
|
||||
|
||||
val db = signalDatabaseRule.database
|
||||
|
||||
val values = ContentValues().apply {
|
||||
put("_id", recipientId)
|
||||
put("type", 0)
|
||||
}
|
||||
|
||||
db.insert("recipient", null, values)
|
||||
insertedRecipients.add(recipientId)
|
||||
return recipientId
|
||||
}
|
||||
|
||||
private fun insertThread(recipientId: Long): Long {
|
||||
insertedThreads[recipientId]?.let { return it }
|
||||
|
||||
val db = signalDatabaseRule.database
|
||||
|
||||
val values = ContentValues().apply {
|
||||
put("recipient_id", recipientId)
|
||||
put("date", System.currentTimeMillis())
|
||||
}
|
||||
|
||||
val threadId = db.insert("thread", null, values)
|
||||
insertedThreads[recipientId] = threadId
|
||||
return threadId
|
||||
}
|
||||
|
||||
private fun insertMessage(fromRecipientId: Long): Long {
|
||||
val db = signalDatabaseRule.database
|
||||
|
||||
insertRecipient(fromRecipientId)
|
||||
val threadId = insertThread(fromRecipientId)
|
||||
dateSentCounter++
|
||||
|
||||
val values = ContentValues().apply {
|
||||
put("date_sent", dateSentCounter)
|
||||
put("date_received", System.currentTimeMillis())
|
||||
put("thread_id", threadId)
|
||||
put("from_recipient_id", fromRecipientId)
|
||||
put("to_recipient_id", fromRecipientId)
|
||||
put("type", 0)
|
||||
}
|
||||
|
||||
return db.insert("message", null, values)
|
||||
}
|
||||
|
||||
private fun insertAttachment(messageId: Long, archiveTransferState: Int, archiveCdn: Int?): Long {
|
||||
val db = signalDatabaseRule.database
|
||||
|
||||
val values = ContentValues().apply {
|
||||
put("message_id", messageId)
|
||||
put("data_file", "/fake/path/attachment.jpg")
|
||||
put("data_random", "/fake/path/attachment.jpg".toByteArray())
|
||||
put("transfer_state", 0)
|
||||
put("archive_transfer_state", archiveTransferState)
|
||||
if (archiveCdn != null) {
|
||||
put("archive_cdn", archiveCdn)
|
||||
}
|
||||
}
|
||||
|
||||
return db.insert("attachment", null, values)
|
||||
}
|
||||
|
||||
private fun assertArchiveState(attachmentId: Long, expectedState: Int) {
|
||||
val db = signalDatabaseRule.database
|
||||
val cursor = db.query(
|
||||
"attachment",
|
||||
arrayOf("archive_transfer_state"),
|
||||
"_id = ?",
|
||||
arrayOf(attachmentId.toString()),
|
||||
null,
|
||||
null,
|
||||
null
|
||||
)
|
||||
|
||||
cursor.use {
|
||||
assertThat(it.moveToFirst()).isEqualTo(true)
|
||||
assertThat(it.getInt(it.getColumnIndexOrThrow("archive_transfer_state"))).isEqualTo(expectedState)
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user