mirror of
https://github.com/signalapp/Signal-Android.git
synced 2026-02-23 11:15:44 +00:00
Update to latest Backup.proto and fix various backup bugs.
This commit is contained in:
committed by
mtang-signal
parent
e2e6a73e8d
commit
5ffb7b07da
@@ -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),
|
||||
|
||||
@@ -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")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -83,7 +83,7 @@ class DatabaseAttachment : Attachment {
|
||||
thumbnailRestoreState: AttachmentTable.ThumbnailRestoreState,
|
||||
uuid: UUID?
|
||||
) : super(
|
||||
contentType = contentType!!,
|
||||
contentType = contentType,
|
||||
transferState = transferProgress,
|
||||
size = size,
|
||||
fileName = fileName,
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
@@ -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()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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)?,
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
@@ -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 {
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
@@ -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() }
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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"
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -288,10 +288,5 @@ class SignalStore(private val store: KeyValueStore) {
|
||||
instanceOverride = store
|
||||
_instance.reset()
|
||||
}
|
||||
|
||||
fun clearAllDataForBackupRestore() {
|
||||
releaseChannel.clearReleaseChannelRecipientId()
|
||||
account.clearRegistrationButKeepCredentials()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user