Update to latest Backup.proto and fix various backup bugs.

This commit is contained in:
Greyson Parrelli
2024-08-09 16:04:47 -04:00
committed by mtang-signal
parent e2e6a73e8d
commit 5ffb7b07da
31 changed files with 761 additions and 2182 deletions

View File

@@ -49,13 +49,14 @@ class ArchivedAttachment : Attachment {
stickerLocator: StickerLocator?,
gif: Boolean,
quote: Boolean,
uuid: UUID?
uuid: UUID?,
fileName: String?
) : super(
contentType = contentType ?: "",
quote = quote,
transferState = AttachmentTable.TRANSFER_NEEDS_RESTORE,
size = size,
fileName = null,
fileName = fileName,
cdn = Cdn.fromCdnNumber(cdn),
remoteLocation = cdnKey,
remoteKey = Base64.encodeWithoutPadding(key),

View File

@@ -46,7 +46,7 @@ enum class Cdn(private val value: Int) {
0 -> CDN_0
2 -> CDN_2
3 -> CDN_3
else -> throw UnsupportedOperationException()
else -> throw UnsupportedOperationException("Invalid CDN number: $cdnNumber")
}
}
}

View File

@@ -83,7 +83,7 @@ class DatabaseAttachment : Attachment {
thumbnailRestoreState: AttachmentTable.ThumbnailRestoreState,
uuid: UUID?
) : super(
contentType = contentType!!,
contentType = contentType,
transferState = transferProgress,
size = size,
fileName = fileName,

View File

@@ -13,7 +13,7 @@ import java.util.UUID
* quote them and know their contentType even though the media has been deleted.
*/
class TombstoneAttachment : Attachment {
constructor(contentType: String, quote: Boolean) : super(
constructor(contentType: String?, quote: Boolean) : super(
contentType = contentType,
quote = quote,
transferState = AttachmentTable.TRANSFER_PROGRESS_DONE,

View File

@@ -14,7 +14,7 @@ class UriAttachment : Attachment {
constructor(
uri: Uri,
contentType: String,
contentType: String?,
transferState: Int,
size: Long,
fileName: String?,
@@ -50,7 +50,7 @@ class UriAttachment : Attachment {
@JvmOverloads
constructor(
dataUri: Uri,
contentType: String,
contentType: String?,
transferState: Int,
size: Long,
width: Int,

View File

@@ -211,7 +211,7 @@ object BackupRepository {
)
}
val exportState = ExportState(backupTime = currentTime, allowMediaBackup = SignalStore.backup.backsUpMedia)
val exportState = ExportState(backupTime = currentTime, mediaBackupEnabled = SignalStore.backup.backsUpMedia)
writer.use {
writer.write(
@@ -302,13 +302,15 @@ object BackupRepository {
// Note: Without a transaction, bad imports could lead to lost data. But because we have a transaction,
// writes from other threads are blocked. This is something to think more about.
SignalDatabase.rawDatabase.withinTransaction {
SignalStore.clearAllDataForBackupRestore()
SignalDatabase.recipients.clearAllDataForBackupRestore()
SignalDatabase.distributionLists.clearAllDataForBackupRestore()
SignalDatabase.threads.clearAllDataForBackupRestore()
SignalDatabase.messages.clearAllDataForBackupRestore()
SignalDatabase.attachments.clearAllDataForBackupRestore()
SignalDatabase.stickers.clearAllDataForBackupRestore()
SignalDatabase.reactions.clearAllDataForBackupRestore()
SignalDatabase.inAppPayments.clearAllDataForBackupRestore()
SignalDatabase.chatColors.clearAllDataForBackupRestore()
// Add back self after clearing data
val selfId: RecipientId = SignalDatabase.recipients.getAndPossiblyMerge(selfData.aci, selfData.pni, selfData.e164, pniVerified = true, changeSelf = true)
@@ -953,7 +955,7 @@ data class ArchivedMediaObject(val mediaId: String, val cdn: Int)
data class BackupDirectories(val backupDir: String, val mediaDir: String)
class ExportState(val backupTime: Long, val allowMediaBackup: Boolean) {
class ExportState(val backupTime: Long, val mediaBackupEnabled: Boolean) {
val recipientIds: MutableSet<Long> = hashSetOf()
val threadIds: MutableSet<Long> = hashSetOf()
val localToRemoteCustomChatColors: MutableMap<Long, Int> = hashMapOf()

View File

@@ -0,0 +1,13 @@
/*
* Copyright 2024 Signal Messenger, LLC
* SPDX-License-Identifier: AGPL-3.0-only
*/
package org.thoughtcrime.securesms.backup.v2.database
import org.signal.core.util.deleteAll
import org.thoughtcrime.securesms.database.ChatColorsTable
fun ChatColorsTable.clearAllDataForBackupRestore() {
writableDatabase.deleteAll(ChatColorsTable.TABLE_NAME)
}

View File

@@ -131,7 +131,7 @@ class ChatItemExportIterator(private val cursor: Cursor, private val batchSize:
}
}
val reactionsById: Map<Long, List<ReactionRecord>> = SignalDatabase.reactions.getReactionsForMessages(records.keys)
val reactionsById: Map<Long, List<ReactionRecord>> = SignalDatabase.reactions.getReactionsForMessages(records.keys).map { entry -> entry.key to entry.value.sortedBy { it.dateReceived } }.toMap()
val mentionsById: Map<Long, List<Mention>> = SignalDatabase.mentions.getMentionsForMessages(records.keys)
val attachmentsById: Map<Long, List<DatabaseAttachment>> = SignalDatabase.attachments.getAttachmentsForMessages(records.keys)
val groupReceiptsById: Map<Long, List<GroupReceiptTable.GroupReceiptInfo>> = SignalDatabase.groupReceipts.getGroupReceiptInfoForMessages(records.keys)
@@ -757,28 +757,28 @@ class ChatItemExportIterator(private val cursor: Cursor, private val batchSize:
private fun DatabaseAttachment.toBackupAttachment(): MessageAttachment {
val builder = FilePointer.Builder()
builder.contentType = contentType
builder.incrementalMac = incrementalDigest?.toByteString()
builder.incrementalMacChunkSize = incrementalMacChunkSize
builder.fileName = fileName
builder.width = width
builder.height = height
builder.caption = caption
builder.blurHash = blurHash?.hash
builder.contentType = this.contentType?.takeUnless { it.isBlank() }
builder.incrementalMac = this.incrementalDigest?.toByteString()
builder.incrementalMacChunkSize = this.incrementalMacChunkSize.takeIf { it > 0 }
builder.fileName = this.fileName
builder.width = this.width.takeUnless { it == 0 }
builder.height = this.height.takeUnless { it == 0 }
builder.caption = this.caption
builder.blurHash = this.blurHash?.hash
if (remoteKey.isNullOrBlank() || remoteDigest == null || size == 0L) {
if (this.remoteKey.isNullOrBlank() || this.remoteDigest == null || this.size == 0L) {
builder.invalidAttachmentLocator = FilePointer.InvalidAttachmentLocator()
} else {
if (archiveMedia) {
builder.backupLocator = FilePointer.BackupLocator(
mediaName = archiveMediaName ?: this.getMediaName().toString(),
cdnNumber = if (archiveMediaName != null) archiveCdn else Cdn.CDN_3.cdnNumber, // TODO (clark): Update when new proto with optional cdn is landed
mediaName = this.archiveMediaName ?: this.getMediaName().toString(),
cdnNumber = if (this.archiveMediaName != null) this.archiveCdn else Cdn.CDN_3.cdnNumber, // TODO (clark): Update when new proto with optional cdn is landed
key = Base64.decode(remoteKey).toByteString(),
size = this.size,
digest = remoteDigest.toByteString()
size = this.size.toInt(),
digest = this.remoteDigest.toByteString()
)
} else {
if (remoteLocation.isNullOrBlank()) {
if (this.remoteLocation.isNullOrBlank()) {
builder.invalidAttachmentLocator = FilePointer.InvalidAttachmentLocator()
} else {
builder.attachmentLocator = FilePointer.AttachmentLocator(
@@ -787,7 +787,7 @@ class ChatItemExportIterator(private val cursor: Cursor, private val batchSize:
uploadTimestamp = this.uploadTimestamp,
key = Base64.decode(remoteKey).toByteString(),
size = this.size.toInt(),
digest = remoteDigest.toByteString()
digest = this.remoteDigest.toByteString()
)
}
}
@@ -795,16 +795,16 @@ class ChatItemExportIterator(private val cursor: Cursor, private val batchSize:
return MessageAttachment(
pointer = builder.build(),
wasDownloaded = this.transferState == AttachmentTable.TRANSFER_PROGRESS_DONE || this.transferState == AttachmentTable.TRANSFER_NEEDS_RESTORE,
flag = if (voiceNote) {
flag = if (this.voiceNote) {
MessageAttachment.Flag.VOICE_MESSAGE
} else if (videoGif) {
} else if (this.videoGif) {
MessageAttachment.Flag.GIF
} else if (borderless) {
} else if (this.borderless) {
MessageAttachment.Flag.BORDERLESS
} else {
MessageAttachment.Flag.NONE
},
clientUuid = uuid?.let { UuidUtil.toByteString(uuid) }
clientUuid = this.uuid?.let { UuidUtil.toByteString(uuid) }
)
}
@@ -873,11 +873,18 @@ class ChatItemExportIterator(private val cursor: Cursor, private val batchSize:
}
return decoded.ranges.map {
val mention = it.mentionUuid?.let { uuid -> UuidUtil.parseOrThrow(uuid) }?.toByteArray()?.toByteString()
val style = if (mention == null) {
it.style?.toBackupBodyRangeStyle() ?: BackupBodyRange.Style.NONE
} else {
null
}
BackupBodyRange(
start = it.start,
length = it.length,
mentionAci = it.mentionUuid?.let { uuid -> UuidUtil.parseOrThrow(uuid) }?.toByteArray()?.toByteString(),
style = it.style?.toBackupBodyRangeStyle()
mentionAci = mention,
style = style
)
}
}
@@ -899,7 +906,8 @@ class ChatItemExportIterator(private val cursor: Cursor, private val batchSize:
emoji = it.emoji,
authorId = it.author.toLong(),
sentTimestamp = it.dateSent,
receivedTimestamp = it.dateReceived
receivedTimestamp = it.dateReceived,
sortOrder = 0 // TODO [backup] make this it.dateReceived once comparator support is added
)
} ?: emptyList()
}
@@ -913,48 +921,98 @@ class ChatItemExportIterator(private val cursor: Cursor, private val batchSize:
return groupReceipts.toBackupSendStatus(this.networkFailureRecipientIds, this.identityMismatchRecipientIds)
}
val status: SendStatus.Status = when {
this.viewed -> SendStatus.Status.VIEWED
this.hasReadReceipt -> SendStatus.Status.READ
this.hasDeliveryReceipt -> SendStatus.Status.DELIVERED
this.baseType == MessageTypes.BASE_SENT_TYPE -> SendStatus.Status.SENT
MessageTypes.isFailedMessageType(this.type) -> SendStatus.Status.FAILED
else -> SendStatus.Status.PENDING
val statusBuilder = SendStatus.Builder()
.recipientId(this.toRecipientId)
.timestamp(this.receiptTimestamp)
when {
this.identityMismatchRecipientIds.contains(this.toRecipientId) -> {
statusBuilder.failed = SendStatus.Failed(
identityKeyMismatch = true
)
}
this.networkFailureRecipientIds.contains(this.toRecipientId) -> {
statusBuilder.failed = SendStatus.Failed(
network = true
)
}
this.baseType == MessageTypes.BASE_SENT_TYPE -> {
statusBuilder.sent = SendStatus.Sent(
sealedSender = this.sealedSender
)
}
this.hasDeliveryReceipt -> {
statusBuilder.delivered = SendStatus.Delivered(
sealedSender = this.sealedSender
)
}
this.hasReadReceipt -> {
statusBuilder.read = SendStatus.Read(
sealedSender = this.sealedSender
)
}
this.viewed -> {
statusBuilder.viewed = SendStatus.Viewed(
sealedSender = this.sealedSender
)
}
else -> {
statusBuilder.pending = SendStatus.Pending()
}
}
return listOf(
SendStatus(
recipientId = this.toRecipientId,
deliveryStatus = status,
lastStatusUpdateTimestamp = this.receiptTimestamp,
sealedSender = this.sealedSender,
networkFailure = this.networkFailureRecipientIds.contains(this.toRecipientId),
identityKeyMismatch = this.identityMismatchRecipientIds.contains(this.toRecipientId)
)
)
return listOf(statusBuilder.build())
}
private fun List<GroupReceiptTable.GroupReceiptInfo>.toBackupSendStatus(networkFailureRecipientIds: Set<Long>, identityMismatchRecipientIds: Set<Long>): List<SendStatus> {
return this.map {
SendStatus(
recipientId = it.recipientId.toLong(),
deliveryStatus = it.status.toBackupDeliveryStatus(),
sealedSender = it.isUnidentified,
lastStatusUpdateTimestamp = it.timestamp,
networkFailure = networkFailureRecipientIds.contains(it.recipientId.toLong()),
identityKeyMismatch = identityMismatchRecipientIds.contains(it.recipientId.toLong())
)
}
}
val statusBuilder = SendStatus.Builder()
.recipientId(it.recipientId.toLong())
.timestamp(it.timestamp)
private fun Int.toBackupDeliveryStatus(): SendStatus.Status {
return when (this) {
GroupReceiptTable.STATUS_UNDELIVERED -> SendStatus.Status.PENDING
GroupReceiptTable.STATUS_DELIVERED -> SendStatus.Status.DELIVERED
GroupReceiptTable.STATUS_READ -> SendStatus.Status.READ
GroupReceiptTable.STATUS_VIEWED -> SendStatus.Status.VIEWED
GroupReceiptTable.STATUS_SKIPPED -> SendStatus.Status.SKIPPED
else -> SendStatus.Status.SKIPPED
when {
identityMismatchRecipientIds.contains(it.recipientId.toLong()) -> {
statusBuilder.failed = SendStatus.Failed(
identityKeyMismatch = true
)
}
networkFailureRecipientIds.contains(it.recipientId.toLong()) -> {
statusBuilder.failed = SendStatus.Failed(
network = true
)
}
it.status == GroupReceiptTable.STATUS_UNKNOWN -> {
statusBuilder.pending = SendStatus.Pending()
}
it.status == GroupReceiptTable.STATUS_UNDELIVERED -> {
statusBuilder.sent = SendStatus.Sent(
sealedSender = it.isUnidentified
)
}
it.status == GroupReceiptTable.STATUS_DELIVERED -> {
statusBuilder.delivered = SendStatus.Delivered(
sealedSender = it.isUnidentified
)
}
it.status == GroupReceiptTable.STATUS_READ -> {
statusBuilder.read = SendStatus.Read(
sealedSender = it.isUnidentified
)
}
it.status == GroupReceiptTable.STATUS_VIEWED -> {
statusBuilder.viewed = SendStatus.Viewed(
sealedSender = it.isUnidentified
)
}
it.status == GroupReceiptTable.STATUS_SKIPPED -> {
statusBuilder.skipped = SendStatus.Skipped()
}
else -> {
statusBuilder.pending = SendStatus.Pending()
}
}
statusBuilder.build()
}
}

View File

@@ -11,10 +11,12 @@ import okio.ByteString
import org.signal.core.util.Base64
import org.signal.core.util.Hex
import org.signal.core.util.SqlUtil
import org.signal.core.util.forEach
import org.signal.core.util.logging.Log
import org.signal.core.util.orNull
import org.signal.core.util.requireLong
import org.signal.core.util.toInt
import org.signal.core.util.update
import org.thoughtcrime.securesms.attachments.ArchivedAttachment
import org.thoughtcrime.securesms.attachments.Attachment
import org.thoughtcrime.securesms.attachments.Cdn
@@ -211,17 +213,13 @@ class ChatItemImportInserter(
if (buffer.size == 0) {
return false
}
buildBulkInsert(MessageTable.TABLE_NAME, MESSAGE_COLUMNS, buffer.messages).forEach {
db.rawQuery("${it.query.where} RETURNING ${MessageTable.ID}", it.query.whereArgs).use { cursor ->
var index = 0
while (cursor.moveToNext()) {
val rowId = cursor.requireLong(MessageTable.ID)
val followup = it.inserts[index].followUp
if (followup != null) {
followup(rowId)
}
index++
}
var messageInsertIndex = 0
SqlUtil.buildBulkInsert(MessageTable.TABLE_NAME, MESSAGE_COLUMNS, buffer.messages.map { it.contentValues }).forEach { query ->
db.rawQuery("${query.where} RETURNING ${MessageTable.ID}", query.whereArgs).forEach { cursor ->
val finalMessageId = cursor.requireLong(MessageTable.ID)
val relatedInsert = buffer.messages[messageInsertIndex++]
relatedInsert.followUp?.invoke(finalMessageId)
}
}
@@ -240,15 +238,6 @@ class ChatItemImportInserter(
return true
}
private fun buildBulkInsert(tableName: String, columns: Array<String>, messageInserts: List<MessageInsert>, maxQueryArgs: Int = 999): List<BatchInsert> {
val batchSize = maxQueryArgs / columns.size
return messageInserts
.chunked(batchSize)
.map { batch: List<MessageInsert> -> BatchInsert(batch, SqlUtil.buildSingleBulkInsert(tableName, columns, batch.map { it.contentValues })) }
.toList()
}
private fun ChatItem.toMessageInsert(fromRecipientId: RecipientId, chatRecipientId: RecipientId, threadId: Long): MessageInsert {
val contentValues = this.toMessageContentValues(fromRecipientId, chatRecipientId, threadId)
@@ -304,22 +293,22 @@ class ChatItemImportInserter(
}
}
}
if (this.paymentNotification != null) {
followUp = { messageRowId ->
val uuid = tryRestorePayment(this, chatRecipientId)
if (uuid != null) {
db.update(
MessageTable.TABLE_NAME,
contentValuesOf(
db.update(MessageTable.TABLE_NAME)
.values(
MessageTable.BODY to uuid.toString(),
MessageTable.TYPE to ((contentValues.getAsLong(MessageTable.TYPE) and MessageTypes.SPECIAL_TYPES_MASK.inv()) or MessageTypes.SPECIAL_TYPE_PAYMENTS_NOTIFICATION)
),
"${MessageTable.ID}=?",
SqlUtil.buildArgs(messageRowId)
)
)
.where("${MessageTable.ID} = ?", messageRowId)
.run()
}
}
}
if (this.contactMessage != null) {
val contacts = this.contactMessage.contact.map { backupContact ->
Contact(
@@ -352,9 +341,10 @@ class ChatItemImportInserter(
address.country
)
},
Contact.Avatar(null, backupContact.avatar.toLocalAttachment(voiceNote = false, borderless = false, gif = false, wasDownloaded = true), true)
Contact.Avatar(null, backupContact.avatar.toLocalAttachment(), true)
)
}
val contactAttachments = contacts.mapNotNull { it.avatarAttachment }
if (contacts.isNotEmpty()) {
followUp = { messageRowId ->
@@ -374,6 +364,7 @@ class ChatItemImportInserter(
}
}
}
if (this.standardMessage != null) {
val bodyRanges = this.standardMessage.text?.bodyRanges
if (!bodyRanges.isNullOrEmpty()) {
@@ -399,9 +390,11 @@ class ChatItemImportInserter(
val attachments = this.standardMessage.attachments.mapNotNull { attachment ->
attachment.toLocalAttachment()
}
val quoteAttachments = this.standardMessage.quote?.attachments?.mapNotNull {
it.toLocalAttachment()
} ?: emptyList()
if (attachments.isNotEmpty() || linkPreviewAttachments.isNotEmpty() || quoteAttachments.isNotEmpty()) {
followUp = { messageRowId ->
val attachmentMap = SignalDatabase.attachments.insertAttachmentsForMessage(messageRowId, attachments + linkPreviewAttachments, quoteAttachments)
@@ -418,6 +411,7 @@ class ChatItemImportInserter(
}
}
}
if (this.stickerMessage != null) {
val sticker = this.stickerMessage.sticker
val attachment = sticker.toLocalAttachment()
@@ -427,6 +421,7 @@ class ChatItemImportInserter(
}
}
}
return MessageInsert(contentValues, followUp)
}
@@ -442,7 +437,7 @@ class ChatItemImportInserter(
contentValues.put(MessageTable.TO_RECIPIENT_ID, (if (this.outgoing != null) chatRecipientId else selfId).serialize())
contentValues.put(MessageTable.THREAD_ID, threadId)
contentValues.put(MessageTable.DATE_RECEIVED, this.incoming?.dateReceived ?: this.dateSent)
contentValues.put(MessageTable.RECEIPT_TIMESTAMP, this.outgoing?.sendStatus?.maxOfOrNull { it.lastStatusUpdateTimestamp } ?: 0)
contentValues.put(MessageTable.RECEIPT_TIMESTAMP, this.outgoing?.sendStatus?.maxOfOrNull { it.timestamp } ?: 0)
contentValues.putNull(MessageTable.LATEST_REVISION_ID)
contentValues.putNull(MessageTable.ORIGINAL_MESSAGE_ID)
contentValues.put(MessageTable.REVISION_NUMBER, 0)
@@ -450,9 +445,9 @@ class ChatItemImportInserter(
contentValues.put(MessageTable.EXPIRE_STARTED, this.expireStartDate ?: 0)
if (this.outgoing != null) {
val viewed = this.outgoing.sendStatus.any { it.deliveryStatus == SendStatus.Status.VIEWED }
val hasReadReceipt = viewed || this.outgoing.sendStatus.any { it.deliveryStatus == SendStatus.Status.READ }
val hasDeliveryReceipt = viewed || hasReadReceipt || this.outgoing.sendStatus.any { it.deliveryStatus == SendStatus.Status.DELIVERED }
val viewed = this.outgoing.sendStatus.any { it.viewed != null }
val hasReadReceipt = viewed || this.outgoing.sendStatus.any { it.read != null }
val hasDeliveryReceipt = viewed || hasReadReceipt || this.outgoing.sendStatus.any { it.delivered != null }
contentValues.put(MessageTable.VIEWED_COLUMN, viewed.toInt())
contentValues.put(MessageTable.HAS_READ_RECEIPT, hasReadReceipt.toInt())
@@ -536,7 +531,7 @@ class ChatItemImportInserter(
ReactionTable.MESSAGE_ID to messageId,
ReactionTable.AUTHOR_ID to authorId,
ReactionTable.DATE_SENT to it.sentTimestamp,
ReactionTable.DATE_RECEIVED to it.receivedTimestamp,
ReactionTable.DATE_RECEIVED to (it.receivedTimestamp ?: it.sortOrder),
ReactionTable.EMOJI to it.emoji
)
} else {
@@ -551,7 +546,7 @@ class ChatItemImportInserter(
return emptyList()
}
// TODO This seems like an indirect/bad way to detect if this is a 1:1 or group convo
// TODO [backup] This seems like an indirect/bad way to detect if this is a 1:1 or group convo
if (this.outgoing.sendStatus.size == 1 && this.outgoing.sendStatus[0].recipientId == chatBackupRecipientId) {
return emptyList()
}
@@ -563,8 +558,8 @@ class ChatItemImportInserter(
contentValuesOf(
GroupReceiptTable.MMS_ID to messageId,
GroupReceiptTable.RECIPIENT_ID to recipientId.serialize(),
GroupReceiptTable.STATUS to sendStatus.deliveryStatus.toLocalSendStatus(),
GroupReceiptTable.TIMESTAMP to sendStatus.lastStatusUpdateTimestamp,
GroupReceiptTable.STATUS to sendStatus.toLocalSendStatus(),
GroupReceiptTable.TIMESTAMP to sendStatus.timestamp,
GroupReceiptTable.UNIDENTIFIED to sendStatus.sealedSender
)
} else {
@@ -576,9 +571,9 @@ class ChatItemImportInserter(
private fun ChatItem.getMessageType(): Long {
var type: Long = if (this.outgoing != null) {
if (this.outgoing.sendStatus.count { it.identityKeyMismatch } > 0) {
if (this.outgoing.sendStatus.count { it.failed?.identityKeyMismatch == true } > 0) {
MessageTypes.BASE_SENT_FAILED_TYPE
} else if (this.outgoing.sendStatus.count { it.networkFailure } > 0) {
} else if (this.outgoing.sendStatus.count { it.failed?.network == true } > 0) {
MessageTypes.BASE_SENDING_TYPE
} else {
MessageTypes.BASE_SENT_TYPE
@@ -632,6 +627,7 @@ class ChatItemImportInserter(
SimpleChatUpdate.Type.PAYMENT_ACTIVATION_REQUEST -> MessageTypes.SPECIAL_TYPE_PAYMENTS_ACTIVATE_REQUEST or typeWithoutBase
SimpleChatUpdate.Type.UNSUPPORTED_PROTOCOL_MESSAGE -> MessageTypes.UNSUPPORTED_MESSAGE_TYPE or typeWithoutBase
SimpleChatUpdate.Type.REPORTED_SPAM -> MessageTypes.SPECIAL_TYPE_REPORTED_SPAM or typeWithoutBase
else -> throw NotImplementedError()
}
}
updateMessage.expirationTimerChange != null -> {
@@ -846,7 +842,7 @@ class ChatItemImportInserter(
}
val networkFailures = chatItem.outgoing.sendStatus
.filter { status -> status.networkFailure }
.filter { status -> status.failed?.network ?: false }
.mapNotNull { status -> importState.remoteToLocalRecipientId[status.recipientId] }
.map { recipientId -> NetworkFailure(recipientId) }
.toSet()
@@ -862,7 +858,7 @@ class ChatItemImportInserter(
}
val mismatches = chatItem.outgoing.sendStatus
.filter { status -> status.identityKeyMismatch }
.filter { status -> status.failed?.identityKeyMismatch ?: false }
.mapNotNull { status -> importState.remoteToLocalRecipientId[status.recipientId] }
.map { recipientId -> IdentityKeyMismatch(recipientId, null) } // TODO We probably want the actual identity key in this status situation?
.toSet()
@@ -898,101 +894,73 @@ class ChatItemImportInserter(
)
}
private fun SendStatus.Status.toLocalSendStatus(): Int {
return when (this) {
SendStatus.Status.UNKNOWN -> GroupReceiptTable.STATUS_UNKNOWN
SendStatus.Status.FAILED -> GroupReceiptTable.STATUS_UNKNOWN
SendStatus.Status.PENDING -> GroupReceiptTable.STATUS_UNDELIVERED
SendStatus.Status.SENT -> GroupReceiptTable.STATUS_UNDELIVERED
SendStatus.Status.DELIVERED -> GroupReceiptTable.STATUS_DELIVERED
SendStatus.Status.READ -> GroupReceiptTable.STATUS_READ
SendStatus.Status.VIEWED -> GroupReceiptTable.STATUS_VIEWED
SendStatus.Status.SKIPPED -> GroupReceiptTable.STATUS_SKIPPED
private fun SendStatus.toLocalSendStatus(): Int {
return when {
this.pending != null -> GroupReceiptTable.STATUS_UNKNOWN
this.sent != null -> GroupReceiptTable.STATUS_UNDELIVERED
this.delivered != null -> GroupReceiptTable.STATUS_DELIVERED
this.read != null -> GroupReceiptTable.STATUS_READ
this.viewed != null -> GroupReceiptTable.STATUS_VIEWED
this.skipped != null -> GroupReceiptTable.STATUS_SKIPPED
this.failed != null -> GroupReceiptTable.STATUS_UNKNOWN
else -> GroupReceiptTable.STATUS_UNKNOWN
}
}
private fun FilePointer?.toLocalAttachment(voiceNote: Boolean, borderless: Boolean, gif: Boolean, wasDownloaded: Boolean, stickerLocator: StickerLocator? = null, contentType: String? = this?.contentType, fileName: String? = this?.fileName, uuid: ByteString? = null): Attachment? {
if (this == null) return null
if (attachmentLocator != null) {
val signalAttachmentPointer = SignalServiceAttachmentPointer(
attachmentLocator.cdnNumber,
SignalServiceAttachmentRemoteId.from(attachmentLocator.cdnKey),
contentType,
attachmentLocator.key.toByteArray(),
Optional.ofNullable(attachmentLocator.size),
Optional.empty(),
width ?: 0,
height ?: 0,
Optional.ofNullable(attachmentLocator.digest.toByteArray()),
Optional.ofNullable(incrementalMac?.toByteArray()),
incrementalMacChunkSize ?: 0,
Optional.ofNullable(fileName),
voiceNote,
borderless,
gif,
Optional.ofNullable(caption),
Optional.ofNullable(blurHash),
attachmentLocator.uploadTimestamp,
UuidUtil.fromByteStringOrNull(uuid)
)
return PointerAttachment.forPointer(
pointer = Optional.of(signalAttachmentPointer),
stickerLocator = stickerLocator,
transferState = if (wasDownloaded) AttachmentTable.TRANSFER_NEEDS_RESTORE else AttachmentTable.TRANSFER_PROGRESS_PENDING
).orNull()
} else if (invalidAttachmentLocator != null) {
return TombstoneAttachment(
contentType = contentType,
incrementalMac = incrementalMac?.toByteArray(),
incrementalMacChunkSize = incrementalMacChunkSize,
width = width,
height = height,
caption = caption,
blurHash = blurHash,
voiceNote = voiceNote,
borderless = borderless,
gif = gif,
quote = false,
uuid = UuidUtil.fromByteStringOrNull(uuid)
)
} else if (backupLocator != null) {
return ArchivedAttachment(
contentType = contentType,
size = backupLocator.size.toLong(),
cdn = backupLocator.transitCdnNumber ?: Cdn.CDN_0.cdnNumber,
key = backupLocator.key.toByteArray(),
cdnKey = backupLocator.transitCdnKey,
archiveCdn = backupLocator.cdnNumber,
archiveMediaName = backupLocator.mediaName,
archiveMediaId = importState.backupKey.deriveMediaId(MediaName(backupLocator.mediaName)).encode(),
archiveThumbnailMediaId = importState.backupKey.deriveMediaId(MediaName.forThumbnailFromMediaName(backupLocator.mediaName)).encode(),
digest = backupLocator.digest.toByteArray(),
incrementalMac = incrementalMac?.toByteArray(),
incrementalMacChunkSize = incrementalMacChunkSize,
width = width,
height = height,
caption = caption,
blurHash = blurHash,
voiceNote = voiceNote,
borderless = borderless,
gif = gif,
quote = false,
stickerLocator = stickerLocator,
uuid = UuidUtil.fromByteStringOrNull(uuid)
)
private val SendStatus.sealedSender: Boolean
get() {
return this.sent?.sealedSender
?: this.delivered?.sealedSender
?: this.read?.sealedSender
?: this.viewed?.sealedSender
?: false
}
private fun LinkPreview.toLocalLinkPreview(): org.thoughtcrime.securesms.linkpreview.LinkPreview {
return org.thoughtcrime.securesms.linkpreview.LinkPreview(
this.url,
this.title ?: "",
this.description ?: "",
this.date ?: 0,
Optional.ofNullable(this.image?.toLocalAttachment())
)
}
private fun MessageAttachment.toLocalAttachment(contentType: String? = this.pointer?.contentType, fileName: String? = this.pointer?.fileName): Attachment? {
return this.pointer?.toLocalAttachment(
voiceNote = this.flag == MessageAttachment.Flag.VOICE_MESSAGE,
borderless = this.flag == MessageAttachment.Flag.BORDERLESS,
gif = this.flag == MessageAttachment.Flag.GIF,
wasDownloaded = this.wasDownloaded,
stickerLocator = null,
contentType = contentType,
fileName = fileName,
uuid = this.clientUuid
)
}
private fun Quote.QuotedAttachment.toLocalAttachment(): Attachment? {
val thumbnail = this.thumbnail?.toLocalAttachment(this.contentType, this.fileName)
return if (thumbnail != null) {
thumbnail
} else if (this.contentType == null) {
null
} else {
PointerAttachment.forPointer(
quotedAttachment = DataMessage.Quote.QuotedAttachment(
contentType = this.contentType,
fileName = this.fileName,
thumbnail = null
)
).orNull()
}
return null
}
private fun Sticker?.toLocalAttachment(): Attachment? {
if (this == null) return null
return data_.toLocalAttachment(
voiceNote = false,
gif = false,
borderless = false,
wasDownloaded = true,
stickerLocator = StickerLocator(
packId = Hex.toStringCondensed(packId.toByteArray()),
packKey = Hex.toStringCondensed(packKey.toByteArray()),
@@ -1002,24 +970,89 @@ class ChatItemImportInserter(
)
}
private fun LinkPreview.toLocalLinkPreview(): org.thoughtcrime.securesms.linkpreview.LinkPreview {
return org.thoughtcrime.securesms.linkpreview.LinkPreview(
this.url,
this.title ?: "",
this.description ?: "",
this.date ?: 0,
Optional.ofNullable(this.image?.toLocalAttachment(voiceNote = false, borderless = false, gif = false, wasDownloaded = true))
)
}
private fun MessageAttachment.toLocalAttachment(): Attachment? {
return pointer?.toLocalAttachment(
voiceNote = flag == MessageAttachment.Flag.VOICE_MESSAGE,
gif = flag == MessageAttachment.Flag.GIF,
borderless = flag == MessageAttachment.Flag.BORDERLESS,
wasDownloaded = wasDownloaded,
uuid = clientUuid
)
private fun FilePointer?.toLocalAttachment(
borderless: Boolean = false,
gif: Boolean = false,
voiceNote: Boolean = false,
wasDownloaded: Boolean = true,
stickerLocator: StickerLocator? = null,
contentType: String? = this?.contentType,
fileName: String? = this?.fileName,
uuid: ByteString? = null
): Attachment? {
return if (this == null) {
null
} else if (this.attachmentLocator != null) {
val signalAttachmentPointer = SignalServiceAttachmentPointer(
cdnNumber = this.attachmentLocator.cdnNumber,
remoteId = SignalServiceAttachmentRemoteId.from(this.attachmentLocator.cdnKey),
contentType = contentType,
key = this.attachmentLocator.key.toByteArray(),
size = Optional.ofNullable(this.attachmentLocator.size),
preview = Optional.empty(),
width = this.width ?: 0,
height = this.height ?: 0,
digest = Optional.ofNullable(this.attachmentLocator.digest.toByteArray()),
incrementalDigest = Optional.ofNullable(this.incrementalMac?.toByteArray()),
incrementalMacChunkSize = this.incrementalMacChunkSize ?: 0,
fileName = Optional.ofNullable(fileName),
voiceNote = voiceNote,
isBorderless = borderless,
isGif = gif,
caption = Optional.ofNullable(this.caption),
blurHash = Optional.ofNullable(this.blurHash),
uploadTimestamp = this.attachmentLocator.uploadTimestamp,
uuid = UuidUtil.fromByteStringOrNull(uuid)
)
PointerAttachment.forPointer(
pointer = Optional.of(signalAttachmentPointer),
stickerLocator = stickerLocator,
transferState = if (wasDownloaded) AttachmentTable.TRANSFER_NEEDS_RESTORE else AttachmentTable.TRANSFER_PROGRESS_PENDING
).orNull()
} else if (this.invalidAttachmentLocator != null) {
TombstoneAttachment(
contentType = contentType,
incrementalMac = this.incrementalMac?.toByteArray(),
incrementalMacChunkSize = this.incrementalMacChunkSize,
width = this.width,
height = this.height,
caption = this.caption,
blurHash = this.blurHash,
voiceNote = voiceNote,
borderless = borderless,
gif = gif,
quote = false,
uuid = UuidUtil.fromByteStringOrNull(uuid)
)
} else if (this.backupLocator != null) {
ArchivedAttachment(
contentType = contentType,
size = this.backupLocator.size.toLong(),
cdn = this.backupLocator.transitCdnNumber ?: Cdn.CDN_0.cdnNumber,
key = this.backupLocator.key.toByteArray(),
cdnKey = this.backupLocator.transitCdnKey,
archiveCdn = this.backupLocator.cdnNumber,
archiveMediaName = this.backupLocator.mediaName,
archiveMediaId = importState.backupKey.deriveMediaId(MediaName(this.backupLocator.mediaName)).encode(),
archiveThumbnailMediaId = importState.backupKey.deriveMediaId(MediaName.forThumbnailFromMediaName(this.backupLocator.mediaName)).encode(),
digest = this.backupLocator.digest.toByteArray(),
incrementalMac = this.incrementalMac?.toByteArray(),
incrementalMacChunkSize = this.incrementalMacChunkSize,
width = this.width,
height = this.height,
caption = this.caption,
blurHash = this.blurHash,
voiceNote = voiceNote,
borderless = borderless,
gif = gif,
quote = false,
stickerLocator = stickerLocator,
uuid = UuidUtil.fromByteStringOrNull(uuid),
fileName = fileName
)
} else {
null
}
}
private fun ContactAttachment.Name?.toLocal(): Contact.Name {
@@ -1058,23 +1091,6 @@ class ChatItemImportInserter(
}
}
private fun MessageAttachment.toLocalAttachment(contentType: String?, fileName: String?): Attachment? {
return pointer?.toLocalAttachment(
voiceNote = flag == MessageAttachment.Flag.VOICE_MESSAGE,
gif = flag == MessageAttachment.Flag.GIF,
borderless = flag == MessageAttachment.Flag.BORDERLESS,
wasDownloaded = wasDownloaded,
contentType = contentType,
fileName = fileName,
uuid = clientUuid
)
}
private fun Quote.QuotedAttachment.toLocalAttachment(): Attachment? {
return thumbnail?.toLocalAttachment(this.contentType, this.fileName)
?: if (this.contentType == null) null else PointerAttachment.forPointer(quotedAttachment = DataMessage.Quote.QuotedAttachment(contentType = this.contentType, fileName = this.fileName, thumbnail = null)).orNull()
}
private class MessageInsert(
val contentValues: ContentValues,
val followUp: ((Long) -> Unit)?,

View File

@@ -0,0 +1,13 @@
/*
* Copyright 2024 Signal Messenger, LLC
* SPDX-License-Identifier: AGPL-3.0-only
*/
package org.thoughtcrime.securesms.backup.v2.database
import org.signal.core.util.deleteAll
import org.thoughtcrime.securesms.database.InAppPaymentTable
fun InAppPaymentTable.clearAllDataForBackupRestore() {
writableDatabase.deleteAll(InAppPaymentTable.TABLE_NAME)
}

View File

@@ -16,7 +16,7 @@ import java.util.concurrent.TimeUnit
private val TAG = Log.tag(MessageTable::class.java)
private const val BASE_TYPE = "base_type"
fun MessageTable.getMessagesForBackup(backupTime: Long, archiveMedia: Boolean): ChatItemExportIterator {
fun MessageTable.getMessagesForBackup(backupTime: Long, mediaBackupEnabled: Boolean): ChatItemExportIterator {
val cursor = readableDatabase
.select(
MessageTable.ID,
@@ -66,7 +66,7 @@ fun MessageTable.getMessagesForBackup(backupTime: Long, archiveMedia: Boolean):
.orderBy("${MessageTable.DATE_RECEIVED} ASC")
.run()
return ChatItemExportIterator(cursor, 100, archiveMedia)
return ChatItemExportIterator(cursor, 100, mediaBackupEnabled)
}
fun MessageTable.createChatItemInserter(importState: ImportState): ChatItemImportInserter {

View File

@@ -0,0 +1,13 @@
/*
* Copyright 2023 Signal Messenger, LLC
* SPDX-License-Identifier: AGPL-3.0-only
*/
package org.thoughtcrime.securesms.backup.v2.database
import org.signal.core.util.deleteAll
import org.thoughtcrime.securesms.database.ReactionTable
fun ReactionTable.clearAllDataForBackupRestore() {
writableDatabase.deleteAll(ReactionTable.TABLE_NAME)
}

View File

@@ -303,22 +303,23 @@ private fun Member.Role.toSnapshot(): Group.Member.Role {
}
private fun DecryptedGroup.toSnapshot(): Group.GroupSnapshot? {
if (revision == GroupsV2StateProcessor.RESTORE_PLACEHOLDER_REVISION || revision == GroupsV2StateProcessor.PLACEHOLDER_REVISION) {
if (this.revision == GroupsV2StateProcessor.RESTORE_PLACEHOLDER_REVISION || this.revision == GroupsV2StateProcessor.PLACEHOLDER_REVISION) {
return null
}
return Group.GroupSnapshot(
title = Group.GroupAttributeBlob(title = title),
avatarUrl = avatar,
disappearingMessagesTimer = Group.GroupAttributeBlob(disappearingMessagesDuration = disappearingMessagesTimer?.duration ?: 0),
accessControl = accessControl?.toSnapshot(),
version = revision,
members = members.map { it.toSnapshot() },
membersPendingProfileKey = pendingMembers.map { it.toSnapshot() },
membersPendingAdminApproval = requestingMembers.map { it.toSnapshot() },
inviteLinkPassword = inviteLinkPassword,
description = Group.GroupAttributeBlob(descriptionText = description),
announcements_only = isAnnouncementGroup == EnabledState.ENABLED,
members_banned = bannedMembers.map { it.toSnapshot() }
title = Group.GroupAttributeBlob(title = this.title),
avatarUrl = this.avatar,
disappearingMessagesTimer = Group.GroupAttributeBlob(disappearingMessagesDuration = this.disappearingMessagesTimer?.duration ?: 0),
accessControl = this.accessControl?.toSnapshot(),
version = this.revision,
members = this.members.map { it.toSnapshot() },
membersPendingProfileKey = this.pendingMembers.map { it.toSnapshot() },
membersPendingAdminApproval = this.requestingMembers.map { it.toSnapshot() },
inviteLinkPassword = this.inviteLinkPassword,
description = this.description.takeUnless { it.isBlank() }?.let { Group.GroupAttributeBlob(descriptionText = it) },
announcements_only = this.isAnnouncementGroup == EnabledState.ENABLED,
members_banned = this.bannedMembers.map { it.toSnapshot() }
)
}
@@ -343,58 +344,58 @@ private fun Group.MemberPendingProfileKey.toLocal(operations: GroupsV2Operations
private fun DecryptedPendingMember.toSnapshot(): Group.MemberPendingProfileKey {
return Group.MemberPendingProfileKey(
member = Group.Member(
userId = serviceIdBytes,
role = role.toSnapshot()
userId = this.serviceIdBytes,
role = this.role.toSnapshot()
),
addedByUserId = addedByAci,
timestamp = timestamp
addedByUserId = this.addedByAci,
timestamp = this.timestamp
)
}
private fun Group.MemberPendingAdminApproval.toLocal(): DecryptedRequestingMember {
return DecryptedRequestingMember(
aciBytes = userId,
profileKey = profileKey,
timestamp = timestamp
aciBytes = this.userId,
profileKey = this.profileKey,
timestamp = this.timestamp
)
}
private fun DecryptedRequestingMember.toSnapshot(): Group.MemberPendingAdminApproval {
return Group.MemberPendingAdminApproval(
userId = aciBytes,
profileKey = profileKey,
timestamp = timestamp
userId = this.aciBytes,
profileKey = this.profileKey,
timestamp = this.timestamp
)
}
private fun Group.MemberBanned.toLocal(): DecryptedBannedMember {
return DecryptedBannedMember(
serviceIdBytes = userId,
timestamp = timestamp
serviceIdBytes = this.userId,
timestamp = this.timestamp
)
}
private fun DecryptedBannedMember.toSnapshot(): Group.MemberBanned {
return Group.MemberBanned(
userId = serviceIdBytes,
timestamp = timestamp
userId = this.serviceIdBytes,
timestamp = this.timestamp
)
}
private fun Group.GroupSnapshot.toDecryptedGroup(operations: GroupsV2Operations.GroupOperations): DecryptedGroup {
return DecryptedGroup(
title = title?.title ?: "",
avatar = avatarUrl,
disappearingMessagesTimer = DecryptedTimer(duration = disappearingMessagesTimer?.disappearingMessagesDuration ?: 0),
accessControl = accessControl?.toLocal(),
revision = version,
members = members.map { member -> member.toLocal() },
pendingMembers = membersPendingProfileKey.map { pending -> pending.toLocal(operations) },
requestingMembers = membersPendingAdminApproval.map { requesting -> requesting.toLocal() },
inviteLinkPassword = inviteLinkPassword,
description = description?.descriptionText ?: "",
isAnnouncementGroup = if (announcements_only) EnabledState.ENABLED else EnabledState.DISABLED,
bannedMembers = members_banned.map { it.toLocal() }
title = this.title?.title ?: "",
avatar = this.avatarUrl,
disappearingMessagesTimer = DecryptedTimer(duration = this.disappearingMessagesTimer?.disappearingMessagesDuration ?: 0),
accessControl = this.accessControl?.toLocal(),
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() },
inviteLinkPassword = this.inviteLinkPassword,
description = this.description?.descriptionText ?: "",
isAnnouncementGroup = if (this.announcements_only) EnabledState.ENABLED else EnabledState.DISABLED,
bannedMembers = this.members_banned.map { it.toLocal() }
)
}

View File

@@ -19,7 +19,7 @@ object ChatItemBackupProcessor {
val TAG = Log.tag(ChatItemBackupProcessor::class.java)
fun export(db: SignalDatabase, exportState: ExportState, emitter: BackupFrameEmitter) {
db.messageTable.getMessagesForBackup(exportState.backupTime, exportState.allowMediaBackup).use { chatItems ->
db.messageTable.getMessagesForBackup(exportState.backupTime, exportState.mediaBackupEnabled).use { chatItems ->
while (chatItems.hasNext()) {
val chatItem = chatItems.next()
if (chatItem != null) {

View File

@@ -1855,6 +1855,7 @@ class AttachmentTable(
put(ARCHIVE_THUMBNAIL_MEDIA_ID, attachment.archiveThumbnailMediaId)
put(THUMBNAIL_RESTORE_STATE, ThumbnailRestoreState.NEEDS_RESTORE.value)
put(ATTACHMENT_UUID, attachment.uuid?.toString())
put(BLUR_HASH, attachment.blurHash?.hash)
attachment.stickerLocator?.let { sticker ->
put(STICKER_PACK_ID, sticker.packId)

View File

@@ -13,7 +13,7 @@ import org.thoughtcrime.securesms.keyvalue.SignalStore
class ChatColorsTable(context: Context, databaseHelper: SignalDatabase) : DatabaseTable(context, databaseHelper) {
companion object {
private const val TABLE_NAME = "chat_colors"
const val TABLE_NAME = "chat_colors"
private const val ID = "_id"
private const val CHAT_COLORS = "chat_colors"

View File

@@ -51,7 +51,7 @@ import kotlin.time.Duration.Companion.seconds
class InAppPaymentTable(context: Context, databaseHelper: SignalDatabase) : DatabaseTable(context, databaseHelper) {
companion object {
private const val TABLE_NAME = "in_app_payment"
const val TABLE_NAME = "in_app_payment"
/**
* Row ID

View File

@@ -234,6 +234,10 @@ public class KeyValueDatabase extends SQLiteOpenHelper implements SignalDatabase
}
}
public void clear() {
getWritableDatabase().delete(TABLE_NAME, null, null);
}
private enum Type {
BLOB(0), BOOLEAN(1), FLOAT(2), INTEGER(3), LONG(4), STRING(5);

View File

@@ -126,7 +126,7 @@ object GroupsV2UpdateMessageConverter {
fun translateDecryptedChangeUpdate(selfIds: ServiceIds, groupContext: DecryptedGroupV2Context): GroupChangeChatUpdate {
var previousGroupState = groupContext.previousGroupState
val change = groupContext.change!!
if (DecryptedGroup().equals(previousGroupState)) {
if (DecryptedGroup() == previousGroupState) {
previousGroupState = null
}
val updates: MutableList<GroupChangeChatUpdate.Update> = LinkedList()

View File

@@ -433,7 +433,7 @@ public class ApplicationDependencyProvider implements AppDependencies.Provider {
BuildConfig.SIGNAL_AGENT,
healthMonitor,
Stories.isFeatureEnabled(),
LibSignalNetworkExtensions.createChatService(libSignalNetworkSupplier.get(), null),
LibSignalNetworkExtensions.createChatService(libSignalNetworkSupplier.get(), null, Stories.isFeatureEnabled()),
shadowPercentage,
bridge
);
@@ -442,7 +442,7 @@ public class ApplicationDependencyProvider implements AppDependencies.Provider {
Network network = libSignalNetworkSupplier.get();
return new LibSignalChatConnection(
"libsignal-unauth",
LibSignalNetworkExtensions.createChatService(network, null),
LibSignalNetworkExtensions.createChatService(network, null, Stories.isFeatureEnabled()),
healthMonitor,
false);
} else {

View File

@@ -146,10 +146,10 @@ class ArchiveThumbnailUploadJob private constructor(
val uri: DecryptableUri = attachment.uri?.let { DecryptableUri(it) } ?: return null
return if (MediaUtil.isImageType(attachment.contentType)) {
ImageCompressionUtil.compress(context, attachment.contentType, uri, 256, 50)
ImageCompressionUtil.compress(context, attachment.contentType ?: "", uri, 256, 50)
} else if (Build.VERSION.SDK_INT >= 23 && MediaUtil.isVideoType(attachment.contentType)) {
MediaUtil.getVideoThumbnail(context, attachment.uri)?.let {
ImageCompressionUtil.compress(context, attachment.contentType, uri, 256, 50)
ImageCompressionUtil.compress(context, attachment.contentType ?: "", uri, 256, 50)
}
} else {
null

View File

@@ -288,10 +288,5 @@ class SignalStore(private val store: KeyValueStore) {
instanceOverride = store
_instance.reset()
}
fun clearAllDataForBackupRestore() {
releaseChannel.clearReleaseChannelRecipientId()
account.clearRegistrationButKeepCredentials()
}
}
}