mirror of
https://github.com/signalapp/Signal-Android.git
synced 2025-12-22 20:18:36 +00:00
Add backup support for DirectStoryReplyMessages.
This commit is contained in:
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -76,6 +76,11 @@ class ArchiveImportExportTests {
|
||||
runTests { it.startsWith("chat_item_contact_message_") }
|
||||
}
|
||||
|
||||
// @Test
|
||||
fun chatItemDirectStoryReplyMessage() {
|
||||
runTests { it.startsWith("chat_item_direct_story_reply_") }
|
||||
}
|
||||
|
||||
// @Test
|
||||
fun chatItemExpirationTimerUpdate() {
|
||||
runTests { it.startsWith("chat_item_expiration_timer_update_") }
|
||||
|
||||
@@ -43,6 +43,10 @@ object ExportSkips {
|
||||
return log(sentTimestamp, "Group update record is parseable, but has no updates.")
|
||||
}
|
||||
|
||||
fun directStoryReplyHasNoBody(sentTimestamp: Long): String {
|
||||
return log(sentTimestamp, "Direct story reply has no body.")
|
||||
}
|
||||
|
||||
private fun log(sentTimestamp: Long, message: String): String {
|
||||
return "[SKIP][$sentTimestamp] $message"
|
||||
}
|
||||
|
||||
@@ -59,7 +59,8 @@ fun MessageTable.getMessagesForBackup(db: SignalDatabase, backupTime: Long, medi
|
||||
${MessageTable.MISMATCHED_IDENTITIES},
|
||||
${MessageTable.TYPE},
|
||||
${MessageTable.MESSAGE_EXTRAS},
|
||||
${MessageTable.VIEW_ONCE}
|
||||
${MessageTable.VIEW_ONCE},
|
||||
${MessageTable.PARENT_STORY_ID}
|
||||
)
|
||||
""".trimMargin()
|
||||
)
|
||||
@@ -123,7 +124,8 @@ fun MessageTable.getMessagesForBackup(db: SignalDatabase, backupTime: Long, medi
|
||||
MessageTable.MISMATCHED_IDENTITIES,
|
||||
MessageTable.TYPE,
|
||||
MessageTable.MESSAGE_EXTRAS,
|
||||
MessageTable.VIEW_ONCE
|
||||
MessageTable.VIEW_ONCE,
|
||||
MessageTable.PARENT_STORY_ID
|
||||
)
|
||||
.from("${MessageTable.TABLE_NAME} INDEXED BY $dateReceivedIndex")
|
||||
.where("${MessageTable.STORY_TYPE} = 0 AND ${MessageTable.DATE_RECEIVED} >= $lastSeenReceivedTime")
|
||||
|
||||
@@ -32,6 +32,7 @@ import org.thoughtcrime.securesms.backup.v2.proto.ChatItem
|
||||
import org.thoughtcrime.securesms.backup.v2.proto.ChatUpdateMessage
|
||||
import org.thoughtcrime.securesms.backup.v2.proto.ContactAttachment
|
||||
import org.thoughtcrime.securesms.backup.v2.proto.ContactMessage
|
||||
import org.thoughtcrime.securesms.backup.v2.proto.DirectStoryReplyMessage
|
||||
import org.thoughtcrime.securesms.backup.v2.proto.ExpirationTimerChatUpdate
|
||||
import org.thoughtcrime.securesms.backup.v2.proto.GenericGroupUpdate
|
||||
import org.thoughtcrime.securesms.backup.v2.proto.GroupCall
|
||||
@@ -291,6 +292,10 @@ class ChatItemArchiveExporter(
|
||||
builder.viewOnceMessage = record.toRemoteViewOnceMessage(mediaArchiveEnabled = mediaArchiveEnabled, reactionRecords = extraData.reactionsById[id], attachments = extraData.attachmentsById[id])
|
||||
}
|
||||
|
||||
record.parentStoryId != 0L -> {
|
||||
builder.directStoryReplyMessage = record.toRemoteDirectStoryReplyMessage(mediaArchiveEnabled = mediaArchiveEnabled, reactionRecords = extraData.reactionsById[id], attachments = extraData.attachmentsById[record.id]) ?: continue
|
||||
}
|
||||
|
||||
else -> {
|
||||
if (record.body.isNullOrEmpty() && !extraData.attachmentsById.containsKey(record.id)) {
|
||||
Log.w(TAG, ExportSkips.emptyChatItem(record.dateSent))
|
||||
@@ -852,6 +857,36 @@ private fun Contact.PostalAddress.Type.toRemote(): ContactAttachment.PostalAddre
|
||||
}
|
||||
}
|
||||
|
||||
private fun BackupMessageRecord.toRemoteDirectStoryReplyMessage(mediaArchiveEnabled: Boolean, reactionRecords: List<ReactionRecord>?, attachments: List<DatabaseAttachment>?): DirectStoryReplyMessage? {
|
||||
if (this.body.isNullOrBlank()) {
|
||||
Log.w(TAG, ExportSkips.directStoryReplyHasNoBody(this.dateSent))
|
||||
return null
|
||||
}
|
||||
|
||||
val isReaction = MessageTypes.isStoryReaction(this.type)
|
||||
|
||||
return DirectStoryReplyMessage(
|
||||
storySentTimestamp = this.parentStoryId.takeUnless { it == MessageTable.PARENT_STORY_MISSING_ID },
|
||||
emoji = if (isReaction) {
|
||||
this.body
|
||||
} else {
|
||||
null
|
||||
},
|
||||
textReply = if (!isReaction) {
|
||||
DirectStoryReplyMessage.TextReply(
|
||||
text = Text(
|
||||
body = this.body,
|
||||
bodyRanges = this.bodyRanges?.toRemoteBodyRanges(this.dateSent) ?: emptyList()
|
||||
),
|
||||
longText = attachments?.firstOrNull { it.contentType == MediaUtil.LONG_TEXT }?.toRemoteFilePointer(mediaArchiveEnabled)
|
||||
)
|
||||
} else {
|
||||
null
|
||||
},
|
||||
reactions = reactionRecords.toRemote()
|
||||
)
|
||||
}
|
||||
|
||||
private fun BackupMessageRecord.toRemoteStandardMessage(db: SignalDatabase, mediaArchiveEnabled: Boolean, reactionRecords: List<ReactionRecord>?, mentions: List<Mention>?, attachments: List<DatabaseAttachment>?): StandardMessage {
|
||||
val text = body?.let {
|
||||
Text(
|
||||
@@ -1344,7 +1379,8 @@ private fun Cursor.toBackupMessageRecord(pastIds: Set<Long>, backupStartTime: Lo
|
||||
identityMismatchRecipientIds = this.requireString(MessageTable.MISMATCHED_IDENTITIES).parseIdentityMismatches(),
|
||||
baseType = this.requireLong(MessageTable.TYPE) and MessageTypes.BASE_TYPE_MASK,
|
||||
messageExtras = this.requireBlob(MessageTable.MESSAGE_EXTRAS).parseMessageExtras(),
|
||||
viewOnce = this.requireBoolean(MessageTable.VIEW_ONCE)
|
||||
viewOnce = this.requireBoolean(MessageTable.VIEW_ONCE),
|
||||
parentStoryId = this.requireLong(MessageTable.PARENT_STORY_ID)
|
||||
)
|
||||
}
|
||||
|
||||
@@ -1372,6 +1408,7 @@ private class BackupMessageRecord(
|
||||
val quoteBodyRanges: ByteArray?,
|
||||
val quoteType: Int,
|
||||
val originalMessageId: Long?,
|
||||
val parentStoryId: Long,
|
||||
val latestRevisionId: Long?,
|
||||
val hasDeliveryReceipt: Boolean,
|
||||
val hasReadReceipt: Boolean,
|
||||
|
||||
@@ -24,6 +24,7 @@ import org.thoughtcrime.securesms.backup.v2.proto.BodyRange
|
||||
import org.thoughtcrime.securesms.backup.v2.proto.ChatItem
|
||||
import org.thoughtcrime.securesms.backup.v2.proto.ChatUpdateMessage
|
||||
import org.thoughtcrime.securesms.backup.v2.proto.ContactAttachment
|
||||
import org.thoughtcrime.securesms.backup.v2.proto.DirectStoryReplyMessage
|
||||
import org.thoughtcrime.securesms.backup.v2.proto.GroupCall
|
||||
import org.thoughtcrime.securesms.backup.v2.proto.IndividualCall
|
||||
import org.thoughtcrime.securesms.backup.v2.proto.LinkPreview
|
||||
@@ -125,7 +126,8 @@ class ChatItemArchiveImporter(
|
||||
MessageTable.VIEW_ONCE,
|
||||
MessageTable.MESSAGE_EXTRAS,
|
||||
MessageTable.ORIGINAL_MESSAGE_ID,
|
||||
MessageTable.LATEST_REVISION_ID
|
||||
MessageTable.LATEST_REVISION_ID,
|
||||
MessageTable.PARENT_STORY_ID
|
||||
)
|
||||
|
||||
private val REACTION_COLUMNS = arrayOf(
|
||||
@@ -238,10 +240,11 @@ class ChatItemArchiveImporter(
|
||||
private fun ChatItem.toMessageInsert(fromRecipientId: RecipientId, chatRecipientId: RecipientId, threadId: Long): MessageInsert {
|
||||
val contentValues = this.toMessageContentValues(fromRecipientId, chatRecipientId, threadId)
|
||||
|
||||
var followUp: ((Long) -> Unit)? = null
|
||||
val followUps: MutableList<(Long) -> Unit> = mutableListOf()
|
||||
|
||||
if (this.updateMessage != null) {
|
||||
if (this.updateMessage.individualCall != null && this.updateMessage.individualCall.callId != null) {
|
||||
followUp = { messageRowId ->
|
||||
followUps += { messageRowId ->
|
||||
val values = contentValuesOf(
|
||||
CallTable.CALL_ID to updateMessage.individualCall.callId,
|
||||
CallTable.MESSAGE_ID to messageRowId,
|
||||
@@ -263,7 +266,7 @@ class ChatItemArchiveImporter(
|
||||
db.insert(CallTable.TABLE_NAME, SQLiteDatabase.CONFLICT_IGNORE, values)
|
||||
}
|
||||
} else if (this.updateMessage.groupCall != null && this.updateMessage.groupCall.callId != null) {
|
||||
followUp = { messageRowId ->
|
||||
followUps += { messageRowId ->
|
||||
val ringer: RecipientId? = this.updateMessage.groupCall.ringerRecipientId?.let { importState.remoteToLocalRecipientId[it] }
|
||||
|
||||
val values = contentValuesOf(
|
||||
@@ -295,7 +298,7 @@ class ChatItemArchiveImporter(
|
||||
}
|
||||
|
||||
if (this.paymentNotification != null) {
|
||||
followUp = { messageRowId ->
|
||||
followUps += { messageRowId ->
|
||||
val uuid = tryRestorePayment(this, chatRecipientId)
|
||||
if (uuid != null) {
|
||||
db.update(MessageTable.TABLE_NAME)
|
||||
@@ -347,7 +350,7 @@ class ChatItemArchiveImporter(
|
||||
|
||||
if (contact != null) {
|
||||
val contactAttachment: Attachment? = contact.avatarAttachment
|
||||
followUp = { messageRowId ->
|
||||
followUps += { messageRowId ->
|
||||
val attachmentMap = if (contactAttachment != null) {
|
||||
SignalDatabase.attachments.insertAttachmentsForMessage(messageRowId, listOf(contactAttachment), emptyList())
|
||||
} else {
|
||||
@@ -365,6 +368,19 @@ class ChatItemArchiveImporter(
|
||||
}
|
||||
}
|
||||
|
||||
if (this.directStoryReplyMessage != null) {
|
||||
val longTextAttachment: Attachment? = this.directStoryReplyMessage.textReply?.longText?.toLocalAttachment(
|
||||
importState = importState,
|
||||
contentType = "text/x-signal-plain"
|
||||
)
|
||||
|
||||
if (longTextAttachment != null) {
|
||||
followUps += { messageRowId ->
|
||||
SignalDatabase.attachments.insertAttachmentsForMessage(messageRowId, listOf(longTextAttachment), emptyList())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (this.standardMessage != null) {
|
||||
val bodyRanges = this.standardMessage.text?.bodyRanges
|
||||
if (!bodyRanges.isNullOrEmpty()) {
|
||||
@@ -380,7 +396,7 @@ class ChatItemArchiveImporter(
|
||||
}
|
||||
}
|
||||
if (mentions.isNotEmpty()) {
|
||||
followUp = { messageId ->
|
||||
followUps += { messageId ->
|
||||
SignalDatabase.mentions.insert(threadId, messageId, mentions)
|
||||
}
|
||||
}
|
||||
@@ -391,19 +407,17 @@ class ChatItemArchiveImporter(
|
||||
attachment.toLocalAttachment()
|
||||
}
|
||||
|
||||
val longTextAttachments: List<Attachment> = this.standardMessage.longText?.let { longTextPointer ->
|
||||
longTextPointer.toLocalAttachment(
|
||||
importState = importState,
|
||||
contentType = "text/x-signal-plain"
|
||||
)
|
||||
}?.let { listOf(it) } ?: emptyList()
|
||||
val longTextAttachments: List<Attachment> = this.standardMessage.longText?.toLocalAttachment(
|
||||
importState = importState,
|
||||
contentType = "text/x-signal-plain"
|
||||
)?.let { listOf(it) } ?: emptyList()
|
||||
|
||||
val quoteAttachments: List<Attachment> = this.standardMessage.quote?.toLocalAttachments() ?: emptyList()
|
||||
|
||||
val hasAttachments = attachments.isNotEmpty() || linkPreviewAttachments.isNotEmpty() || quoteAttachments.isNotEmpty() || longTextAttachments.isNotEmpty()
|
||||
|
||||
if (hasAttachments || linkPreviews.isNotEmpty()) {
|
||||
followUp = { messageRowId ->
|
||||
followUps += { messageRowId ->
|
||||
val attachmentMap = if (hasAttachments) {
|
||||
SignalDatabase.attachments.insertAttachmentsForMessage(messageRowId, attachments + linkPreviewAttachments + longTextAttachments, quoteAttachments)
|
||||
} else {
|
||||
@@ -424,7 +438,7 @@ class ChatItemArchiveImporter(
|
||||
val sticker = this.stickerMessage.sticker
|
||||
val attachment = sticker.toLocalAttachment()
|
||||
if (attachment != null) {
|
||||
followUp = { messageRowId ->
|
||||
followUps += { messageRowId ->
|
||||
SignalDatabase.attachments.insertAttachmentsForMessage(messageRowId, listOf(attachment), emptyList())
|
||||
}
|
||||
}
|
||||
@@ -433,12 +447,20 @@ class ChatItemArchiveImporter(
|
||||
if (this.viewOnceMessage != null) {
|
||||
val attachment = this.viewOnceMessage.attachment?.toLocalAttachment()
|
||||
if (attachment != null) {
|
||||
followUp = { messageRowId ->
|
||||
followUps += { messageRowId ->
|
||||
SignalDatabase.attachments.insertAttachmentsForMessage(messageRowId, listOf(attachment), emptyList())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
val followUp: ((Long) -> Unit)? = if (followUps.isNotEmpty()) {
|
||||
{ messageId ->
|
||||
followUps.forEach { it(messageId) }
|
||||
}
|
||||
} else {
|
||||
null
|
||||
}
|
||||
|
||||
return MessageInsert(contentValues, followUp)
|
||||
}
|
||||
|
||||
@@ -505,6 +527,7 @@ class ChatItemArchiveImporter(
|
||||
this.paymentNotification != null -> contentValues.addPaymentNotification(this, chatRecipientId)
|
||||
this.giftBadge != null -> contentValues.addGiftBadge(this.giftBadge)
|
||||
this.viewOnceMessage != null -> contentValues.addViewOnce(this.viewOnceMessage)
|
||||
this.directStoryReplyMessage != null -> contentValues.addDirectStoryReply(this.directStoryReplyMessage)
|
||||
}
|
||||
|
||||
return contentValues
|
||||
@@ -548,6 +571,7 @@ class ChatItemArchiveImporter(
|
||||
this.contactMessage != null -> this.contactMessage.reactions
|
||||
this.stickerMessage != null -> this.stickerMessage.reactions
|
||||
this.viewOnceMessage != null -> this.viewOnceMessage.reactions
|
||||
this.directStoryReplyMessage != null -> this.directStoryReplyMessage.reactions
|
||||
else -> emptyList()
|
||||
}
|
||||
|
||||
@@ -625,6 +649,10 @@ class ChatItemArchiveImporter(
|
||||
type = type or MessageTypes.SPECIAL_TYPE_GIFT_BADGE
|
||||
}
|
||||
|
||||
if (this.directStoryReplyMessage?.emoji != null) {
|
||||
type = type or MessageTypes.SPECIAL_TYPE_STORY_REACTION
|
||||
}
|
||||
|
||||
return type
|
||||
}
|
||||
|
||||
@@ -848,6 +876,19 @@ class ChatItemArchiveImporter(
|
||||
put(MessageTable.VIEW_ONCE, true.toInt())
|
||||
}
|
||||
|
||||
private fun ContentValues.addDirectStoryReply(directStoryReply: DirectStoryReplyMessage) {
|
||||
put(MessageTable.PARENT_STORY_ID, directStoryReply.storySentTimestamp?.takeUnless { it == 0L } ?: MessageTable.PARENT_STORY_MISSING_ID)
|
||||
|
||||
if (directStoryReply.emoji != null) {
|
||||
put(MessageTable.BODY, directStoryReply.emoji)
|
||||
}
|
||||
|
||||
if (directStoryReply.textReply != null) {
|
||||
put(MessageTable.BODY, directStoryReply.textReply.text?.body)
|
||||
put(MessageTable.MESSAGE_RANGES, directStoryReply.textReply.text?.bodyRanges?.toLocalBodyRanges()?.encode())
|
||||
}
|
||||
}
|
||||
|
||||
private fun String?.tryParseMoney(): Money? {
|
||||
if (this.isNullOrEmpty()) {
|
||||
return null
|
||||
|
||||
@@ -210,6 +210,7 @@ open class MessageTable(context: Context?, databaseHelper: SignalDatabase) : Dat
|
||||
const val QUOTE_TARGET_MISSING_ID = -1L
|
||||
|
||||
const val ADDRESSABLE_MESSAGE_LIMIT = 5
|
||||
const val PARENT_STORY_MISSING_ID = -1L
|
||||
|
||||
const val CREATE_TABLE = """
|
||||
CREATE TABLE $TABLE_NAME (
|
||||
|
||||
Reference in New Issue
Block a user