Extract base archive classes into their own module.

This commit is contained in:
Greyson Parrelli
2026-03-21 08:53:47 -04:00
committed by Cody Henthorne
parent 08491579dd
commit 8a887b65a1
64 changed files with 668 additions and 525 deletions

View File

@@ -92,6 +92,7 @@ wire {
protoPath {
srcDir("${project.rootDir}/lib/libsignal-service/src/main/protowire")
srcDir("${project.rootDir}/lib/archive/src/main/protowire")
}
// Handled by libsignal
prune("signalservice.DecryptionErrorMessage")
@@ -594,6 +595,7 @@ dependencies {
ktlintRuleset(libs.ktlint.twitter.compose)
coreLibraryDesugaring(libs.android.tools.desugar)
implementation(project(":lib:archive"))
implementation(project(":lib:libsignal-service"))
implementation(project(":lib:paging"))
implementation(project(":core:util"))

View File

@@ -13,6 +13,8 @@ import org.junit.Assert.assertTrue
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
import org.signal.archive.proto.Frame
import org.signal.archive.stream.PlainTextBackupReader
import org.signal.core.models.ServiceId
import org.signal.core.util.Base64
import org.signal.core.util.logging.Log
@@ -20,8 +22,6 @@ import org.signal.core.util.readFully
import org.signal.libsignal.messagebackup.ComparableBackup
import org.signal.libsignal.messagebackup.MessageBackup
import org.signal.libsignal.zkgroup.profiles.ProfileKey
import org.thoughtcrime.securesms.backup.v2.proto.Frame
import org.thoughtcrime.securesms.backup.v2.stream.PlainTextBackupReader
import org.thoughtcrime.securesms.database.KeyValueDatabase
import org.thoughtcrime.securesms.dependencies.AppDependencies
import org.thoughtcrime.securesms.keyvalue.SignalStore

View File

@@ -5,6 +5,6 @@
package org.thoughtcrime.securesms.backup.v2
typealias ArchiveRecipient = org.thoughtcrime.securesms.backup.v2.proto.Recipient
typealias ArchiveGroup = org.thoughtcrime.securesms.backup.v2.proto.Group
typealias ArchiveCallLink = org.thoughtcrime.securesms.backup.v2.proto.CallLink
typealias ArchiveRecipient = org.signal.archive.proto.Recipient
typealias ArchiveGroup = org.signal.archive.proto.Group
typealias ArchiveCallLink = org.signal.archive.proto.CallLink

View File

@@ -18,6 +18,15 @@ import kotlinx.coroutines.withContext
import okio.ByteString
import okio.ByteString.Companion.toByteString
import org.greenrobot.eventbus.EventBus
import org.signal.archive.proto.BackupDebugInfo
import org.signal.archive.proto.BackupInfo
import org.signal.archive.proto.Frame
import org.signal.archive.stream.BackupExportWriter
import org.signal.archive.stream.BackupImportReader
import org.signal.archive.stream.EncryptedBackupReader
import org.signal.archive.stream.EncryptedBackupWriter
import org.signal.archive.stream.PlainTextBackupReader
import org.signal.archive.stream.PlainTextBackupWriter
import org.signal.core.models.AccountEntropyPool
import org.signal.core.models.ServiceId.ACI
import org.signal.core.models.ServiceId.PNI
@@ -76,15 +85,6 @@ import org.thoughtcrime.securesms.backup.v2.processor.ChatItemArchiveProcessor
import org.thoughtcrime.securesms.backup.v2.processor.NotificationProfileArchiveProcessor
import org.thoughtcrime.securesms.backup.v2.processor.RecipientArchiveProcessor
import org.thoughtcrime.securesms.backup.v2.processor.StickerArchiveProcessor
import org.thoughtcrime.securesms.backup.v2.proto.BackupDebugInfo
import org.thoughtcrime.securesms.backup.v2.proto.BackupInfo
import org.thoughtcrime.securesms.backup.v2.proto.Frame
import org.thoughtcrime.securesms.backup.v2.stream.BackupExportWriter
import org.thoughtcrime.securesms.backup.v2.stream.BackupImportReader
import org.thoughtcrime.securesms.backup.v2.stream.EncryptedBackupReader
import org.thoughtcrime.securesms.backup.v2.stream.EncryptedBackupWriter
import org.thoughtcrime.securesms.backup.v2.stream.PlainTextBackupReader
import org.thoughtcrime.securesms.backup.v2.stream.PlainTextBackupWriter
import org.thoughtcrime.securesms.backup.v2.ui.BackupAlert
import org.thoughtcrime.securesms.backup.v2.ui.subscription.MessageBackupsType
import org.thoughtcrime.securesms.components.settings.app.AppSettingsActivity
@@ -1311,51 +1311,59 @@ object BackupRepository {
val totalLength = frameReader.getStreamLength()
var frameCount = 0
for (frame in frameReader) {
val frameAccount = frame.account
val frameRecipient = frame.recipient
val frameChat = frame.chat
val frameAdHocCall = frame.adHocCall
val frameStickerPack = frame.stickerPack
val frameNotificationProfile = frame.notificationProfile
val frameChatFolder = frame.chatFolder
val frameChatItem = frame.chatItem
when {
frame.account != null -> {
AccountDataArchiveProcessor.import(frame.account, selfId, importState)
frameAccount != null -> {
AccountDataArchiveProcessor.import(frameAccount, selfId, importState)
eventTimer.emit("account")
frameCount++
}
frame.recipient != null -> {
RecipientArchiveProcessor.import(frame.recipient, importState)
frameRecipient != null -> {
RecipientArchiveProcessor.import(frameRecipient, importState)
eventTimer.emit("recipient")
frameCount++
}
frame.chat != null -> {
ChatArchiveProcessor.import(frame.chat, importState)
frameChat != null -> {
ChatArchiveProcessor.import(frameChat, importState)
eventTimer.emit("chat")
frameCount++
}
frame.adHocCall != null -> {
AdHocCallArchiveProcessor.import(frame.adHocCall, importState)
frameAdHocCall != null -> {
AdHocCallArchiveProcessor.import(frameAdHocCall, importState)
eventTimer.emit("call")
frameCount++
}
frame.stickerPack != null -> {
StickerArchiveProcessor.import(frame.stickerPack)
frameStickerPack != null -> {
StickerArchiveProcessor.import(frameStickerPack)
eventTimer.emit("sticker-pack")
frameCount++
}
frame.notificationProfile != null -> {
NotificationProfileArchiveProcessor.import(frame.notificationProfile, importState)
frameNotificationProfile != null -> {
NotificationProfileArchiveProcessor.import(frameNotificationProfile, importState)
eventTimer.emit("notification-profile")
frameCount++
}
frame.chatFolder != null -> {
ChatFolderArchiveProcessor.import(frame.chatFolder, importState)
frameChatFolder != null -> {
ChatFolderArchiveProcessor.import(frameChatFolder, importState)
eventTimer.emit("chat-folder")
frameCount++
}
frame.chatItem != null -> {
chatItemInserter.import(frame.chatItem)
frameChatItem != null -> {
chatItemInserter.import(frameChatItem)
eventTimer.emit("chatItem")
frameCount++

View File

@@ -6,6 +6,7 @@
package org.thoughtcrime.securesms.backup.v2.database
import android.content.ContentValues
import org.signal.archive.proto.AccountData
import org.signal.core.models.ServiceId
import org.signal.core.util.Base64
import org.signal.core.util.logging.Log
@@ -15,7 +16,6 @@ import org.signal.core.util.update
import org.signal.libsignal.zkgroup.InvalidInputException
import org.thoughtcrime.securesms.backup.v2.exporters.ContactArchiveExporter
import org.thoughtcrime.securesms.backup.v2.exporters.GroupArchiveExporter
import org.thoughtcrime.securesms.backup.v2.proto.AccountData
import org.thoughtcrime.securesms.database.GroupTable
import org.thoughtcrime.securesms.database.IdentityTable
import org.thoughtcrime.securesms.database.RecipientTable

View File

@@ -6,8 +6,8 @@
package org.thoughtcrime.securesms.backup.v2.database
import android.database.Cursor
import org.signal.archive.proto.AdHocCall
import org.signal.core.util.requireLong
import org.thoughtcrime.securesms.backup.v2.proto.AdHocCall
import org.thoughtcrime.securesms.backup.v2.util.clampToValidBackupRange
import org.thoughtcrime.securesms.database.CallTable
import java.io.Closeable

View File

@@ -7,10 +7,10 @@ package org.thoughtcrime.securesms.backup.v2.database
import android.database.Cursor
import okio.ByteString.Companion.toByteString
import org.signal.archive.proto.CallLink
import org.signal.core.util.nullIfEmpty
import org.signal.ringrtc.CallLinkState
import org.thoughtcrime.securesms.backup.v2.ArchiveRecipient
import org.thoughtcrime.securesms.backup.v2.proto.CallLink
import org.thoughtcrime.securesms.backup.v2.util.clampToValidBackupRange
import org.thoughtcrime.securesms.database.CallLinkTable
import java.io.Closeable

View File

@@ -6,6 +6,7 @@
package org.thoughtcrime.securesms.backup.v2.exporters
import android.database.Cursor
import org.signal.archive.proto.Chat
import org.signal.core.util.decodeOrNull
import org.signal.core.util.requireBlob
import org.signal.core.util.requireBoolean
@@ -13,7 +14,6 @@ import org.signal.core.util.requireInt
import org.signal.core.util.requireIntOrNull
import org.signal.core.util.requireLong
import org.thoughtcrime.securesms.backup.v2.ExportState
import org.thoughtcrime.securesms.backup.v2.proto.Chat
import org.thoughtcrime.securesms.backup.v2.util.ChatStyleConverter
import org.thoughtcrime.securesms.backup.v2.util.isValid
import org.thoughtcrime.securesms.conversation.colors.ChatColors

View File

@@ -9,6 +9,38 @@ import android.database.Cursor
import okio.ByteString.Companion.toByteString
import org.json.JSONArray
import org.json.JSONException
import org.signal.archive.proto.AdminDeletedMessage
import org.signal.archive.proto.ChatItem
import org.signal.archive.proto.ChatUpdateMessage
import org.signal.archive.proto.ContactAttachment
import org.signal.archive.proto.ContactMessage
import org.signal.archive.proto.DirectStoryReplyMessage
import org.signal.archive.proto.ExpirationTimerChatUpdate
import org.signal.archive.proto.GenericGroupUpdate
import org.signal.archive.proto.GroupCall
import org.signal.archive.proto.GroupChangeChatUpdate
import org.signal.archive.proto.GroupExpirationTimerUpdate
import org.signal.archive.proto.GroupV2MigrationUpdate
import org.signal.archive.proto.IndividualCall
import org.signal.archive.proto.LearnedProfileChatUpdate
import org.signal.archive.proto.MessageAttachment
import org.signal.archive.proto.PaymentNotification
import org.signal.archive.proto.PinMessageUpdate
import org.signal.archive.proto.Poll
import org.signal.archive.proto.PollTerminateUpdate
import org.signal.archive.proto.ProfileChangeChatUpdate
import org.signal.archive.proto.Quote
import org.signal.archive.proto.Reaction
import org.signal.archive.proto.RemoteDeletedMessage
import org.signal.archive.proto.SendStatus
import org.signal.archive.proto.SessionSwitchoverChatUpdate
import org.signal.archive.proto.SimpleChatUpdate
import org.signal.archive.proto.StandardMessage
import org.signal.archive.proto.Sticker
import org.signal.archive.proto.StickerMessage
import org.signal.archive.proto.Text
import org.signal.archive.proto.ThreadMergeChatUpdate
import org.signal.archive.proto.ViewOnceMessage
import org.signal.core.models.ServiceId
import org.signal.core.util.Base64
import org.signal.core.util.EventTimer
@@ -41,38 +73,6 @@ import org.thoughtcrime.securesms.backup.v2.BackupMode
import org.thoughtcrime.securesms.backup.v2.ExportOddities
import org.thoughtcrime.securesms.backup.v2.ExportSkips
import org.thoughtcrime.securesms.backup.v2.ExportState
import org.thoughtcrime.securesms.backup.v2.proto.AdminDeletedMessage
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
import org.thoughtcrime.securesms.backup.v2.proto.GroupChangeChatUpdate
import org.thoughtcrime.securesms.backup.v2.proto.GroupExpirationTimerUpdate
import org.thoughtcrime.securesms.backup.v2.proto.GroupV2MigrationUpdate
import org.thoughtcrime.securesms.backup.v2.proto.IndividualCall
import org.thoughtcrime.securesms.backup.v2.proto.LearnedProfileChatUpdate
import org.thoughtcrime.securesms.backup.v2.proto.MessageAttachment
import org.thoughtcrime.securesms.backup.v2.proto.PaymentNotification
import org.thoughtcrime.securesms.backup.v2.proto.PinMessageUpdate
import org.thoughtcrime.securesms.backup.v2.proto.Poll
import org.thoughtcrime.securesms.backup.v2.proto.PollTerminateUpdate
import org.thoughtcrime.securesms.backup.v2.proto.ProfileChangeChatUpdate
import org.thoughtcrime.securesms.backup.v2.proto.Quote
import org.thoughtcrime.securesms.backup.v2.proto.Reaction
import org.thoughtcrime.securesms.backup.v2.proto.RemoteDeletedMessage
import org.thoughtcrime.securesms.backup.v2.proto.SendStatus
import org.thoughtcrime.securesms.backup.v2.proto.SessionSwitchoverChatUpdate
import org.thoughtcrime.securesms.backup.v2.proto.SimpleChatUpdate
import org.thoughtcrime.securesms.backup.v2.proto.StandardMessage
import org.thoughtcrime.securesms.backup.v2.proto.Sticker
import org.thoughtcrime.securesms.backup.v2.proto.StickerMessage
import org.thoughtcrime.securesms.backup.v2.proto.Text
import org.thoughtcrime.securesms.backup.v2.proto.ThreadMergeChatUpdate
import org.thoughtcrime.securesms.backup.v2.proto.ViewOnceMessage
import org.thoughtcrime.securesms.backup.v2.util.clampToValidBackupRange
import org.thoughtcrime.securesms.backup.v2.util.toRemoteFilePointer
import org.thoughtcrime.securesms.contactshare.Contact
@@ -117,8 +117,8 @@ import java.util.concurrent.ExecutorService
import java.util.concurrent.Future
import kotlin.math.max
import kotlin.time.Duration.Companion.days
import org.thoughtcrime.securesms.backup.v2.proto.BodyRange as BackupBodyRange
import org.thoughtcrime.securesms.backup.v2.proto.GiftBadge as BackupGiftBadge
import org.signal.archive.proto.BodyRange as BackupBodyRange
import org.signal.archive.proto.GiftBadge as BackupGiftBadge
private val TAG = Log.tag(ChatItemArchiveExporter::class.java)
private val MAX_INLINED_BODY_SIZE = 128.kibiBytes.bytes.toInt()
@@ -958,8 +958,8 @@ private fun BackupMessageRecord.toRemoteLinkPreviews(attachments: List<DatabaseA
return emptyList()
}
private fun LinkPreview.toRemoteLinkPreview(backupMode: BackupMode): org.thoughtcrime.securesms.backup.v2.proto.LinkPreview {
return org.thoughtcrime.securesms.backup.v2.proto.LinkPreview(
private fun LinkPreview.toRemoteLinkPreview(backupMode: BackupMode): org.signal.archive.proto.LinkPreview {
return org.signal.archive.proto.LinkPreview(
url = url,
title = title.nullIfEmpty(),
image = (thumbnail.orNull() as? DatabaseAttachment)?.toRemoteMessageAttachment(backupMode = backupMode)?.pointer,
@@ -1685,17 +1685,18 @@ private fun ChatItem.validateChatItem(exportState: ExportState, selfRecipientId:
return null
}
if (this.updateMessage != null && this.updateMessage.isOnlyForIndividualChats() && exportState.threadIdToRecipientId[this.chatId] !in exportState.contactRecipientIds) {
val validatedUpdateMessage = this.updateMessage
if (validatedUpdateMessage != null && validatedUpdateMessage.isOnlyForIndividualChats() && exportState.threadIdToRecipientId[this.chatId] !in exportState.contactRecipientIds) {
Log.w(TAG, ExportSkips.individualChatUpdateInWrongTypeOfChat(this.dateSent))
return null
}
if (this.updateMessage != null && this.updateMessage.isOnlyForGroupChats() && exportState.threadIdToRecipientId[this.chatId] !in exportState.groupRecipientIds) {
if (validatedUpdateMessage != null && validatedUpdateMessage.isOnlyForGroupChats() && exportState.threadIdToRecipientId[this.chatId] !in exportState.groupRecipientIds) {
Log.w(TAG, ExportSkips.groupChatUpdateInWrongTypeOfChat(this.dateSent))
return null
}
if (this.updateMessage != null && this.updateMessage.canOnlyBeAuthoredBySelf() && this.authorId != selfRecipientId.toLong()) {
if (validatedUpdateMessage != null && validatedUpdateMessage.canOnlyBeAuthoredBySelf() && this.authorId != selfRecipientId.toLong()) {
Log.w(TAG, ExportSkips.individualChatUpdateNotAuthoredBySelf(this.dateSent))
return null
}
@@ -1777,17 +1778,15 @@ private fun List<MessageAttachment>.withFixedVoiceNotes(textPresent: Boolean): L
}
private fun ChatItem.withDowngradeVoiceNotes(): ChatItem {
if (this.standardMessage == null) {
return this
}
val msg = this.standardMessage ?: return this
if (this.standardMessage.attachments.none { it.flag == MessageAttachment.Flag.VOICE_MESSAGE }) {
if (msg.attachments.none { it.flag == MessageAttachment.Flag.VOICE_MESSAGE }) {
return this
}
return this.copy(
standardMessage = this.standardMessage.copy(
attachments = this.standardMessage.attachments.map {
standardMessage = msg.copy(
attachments = msg.attachments.map {
if (it.flag == MessageAttachment.Flag.VOICE_MESSAGE) {
it.copy(flag = MessageAttachment.Flag.NONE)
} else {

View File

@@ -7,6 +7,8 @@ package org.thoughtcrime.securesms.backup.v2.exporters
import android.database.Cursor
import okio.ByteString.Companion.toByteString
import org.signal.archive.proto.Contact
import org.signal.archive.proto.Self
import org.signal.core.models.ServiceId
import org.signal.core.util.Base64
import org.signal.core.util.logging.Log
@@ -18,8 +20,6 @@ import org.signal.core.util.requireLong
import org.signal.core.util.requireString
import org.signal.core.util.toByteArray
import org.thoughtcrime.securesms.backup.v2.ArchiveRecipient
import org.thoughtcrime.securesms.backup.v2.proto.Contact
import org.thoughtcrime.securesms.backup.v2.proto.Self
import org.thoughtcrime.securesms.backup.v2.util.clampToValidBackupRange
import org.thoughtcrime.securesms.backup.v2.util.isValidUsername
import org.thoughtcrime.securesms.backup.v2.util.toRemote

View File

@@ -7,6 +7,8 @@ package org.thoughtcrime.securesms.backup.v2.exporters
import android.database.Cursor
import okio.ByteString.Companion.toByteString
import org.signal.archive.proto.DistributionList
import org.signal.archive.proto.DistributionListItem
import org.signal.core.util.logging.Log
import org.signal.core.util.requireBoolean
import org.signal.core.util.requireLong
@@ -17,8 +19,6 @@ import org.thoughtcrime.securesms.backup.v2.ArchiveRecipient
import org.thoughtcrime.securesms.backup.v2.ExportOddities
import org.thoughtcrime.securesms.backup.v2.ExportState
import org.thoughtcrime.securesms.backup.v2.database.getMembersForBackup
import org.thoughtcrime.securesms.backup.v2.proto.DistributionList
import org.thoughtcrime.securesms.backup.v2.proto.DistributionListItem
import org.thoughtcrime.securesms.backup.v2.util.clampToValidBackupRange
import org.thoughtcrime.securesms.database.DistributionListTables
import org.thoughtcrime.securesms.database.model.DistributionListId

View File

@@ -7,6 +7,7 @@ package org.thoughtcrime.securesms.backup.v2.exporters
import android.database.Cursor
import okio.ByteString.Companion.toByteString
import org.signal.archive.proto.Group
import org.signal.core.models.ServiceId
import org.signal.core.util.requireBlob
import org.signal.core.util.requireBoolean
@@ -24,7 +25,6 @@ import org.signal.storageservice.storage.protos.groups.local.DecryptedRequesting
import org.signal.storageservice.storage.protos.groups.local.EnabledState
import org.thoughtcrime.securesms.backup.v2.ArchiveGroup
import org.thoughtcrime.securesms.backup.v2.ArchiveRecipient
import org.thoughtcrime.securesms.backup.v2.proto.Group
import org.thoughtcrime.securesms.backup.v2.util.toRemote
import org.thoughtcrime.securesms.conversation.colors.AvatarColor
import org.thoughtcrime.securesms.database.GroupTable

View File

@@ -5,10 +5,10 @@
package org.thoughtcrime.securesms.backup.v2.importer
import org.signal.archive.proto.AdHocCall
import org.signal.core.util.insertInto
import org.signal.core.util.logging.Log
import org.thoughtcrime.securesms.backup.v2.ImportState
import org.thoughtcrime.securesms.backup.v2.proto.AdHocCall
import org.thoughtcrime.securesms.database.CallTable
import org.thoughtcrime.securesms.database.SignalDatabase

View File

@@ -5,12 +5,12 @@
package org.thoughtcrime.securesms.backup.v2.importer
import org.signal.archive.proto.CallLink
import org.signal.core.util.isEmpty
import org.signal.core.util.logging.Log
import org.signal.ringrtc.CallLinkRootKey
import org.signal.ringrtc.CallLinkState
import org.thoughtcrime.securesms.backup.v2.ArchiveCallLink
import org.thoughtcrime.securesms.backup.v2.proto.CallLink
import org.thoughtcrime.securesms.database.CallLinkTable
import org.thoughtcrime.securesms.database.SignalDatabase
import org.thoughtcrime.securesms.recipients.RecipientId

View File

@@ -6,13 +6,13 @@
package org.thoughtcrime.securesms.backup.v2.importer
import androidx.core.content.contentValuesOf
import org.signal.archive.proto.Chat
import org.signal.core.util.SqlUtil
import org.signal.core.util.insertInto
import org.signal.core.util.toInt
import org.thoughtcrime.securesms.attachments.AttachmentId
import org.thoughtcrime.securesms.backup.v2.ImportState
import org.thoughtcrime.securesms.backup.v2.database.restoreWallpaperAttachment
import org.thoughtcrime.securesms.backup.v2.proto.Chat
import org.thoughtcrime.securesms.backup.v2.util.parseChatWallpaper
import org.thoughtcrime.securesms.backup.v2.util.toLocal
import org.thoughtcrime.securesms.backup.v2.util.toLocalAttachment

View File

@@ -7,6 +7,23 @@ package org.thoughtcrime.securesms.backup.v2.importer
import android.content.ContentValues
import androidx.core.content.contentValuesOf
import org.signal.archive.proto.BodyRange
import org.signal.archive.proto.ChatItem
import org.signal.archive.proto.ChatUpdateMessage
import org.signal.archive.proto.ContactAttachment
import org.signal.archive.proto.DirectStoryReplyMessage
import org.signal.archive.proto.GroupCall
import org.signal.archive.proto.IndividualCall
import org.signal.archive.proto.LinkPreview
import org.signal.archive.proto.MessageAttachment
import org.signal.archive.proto.PaymentNotification
import org.signal.archive.proto.Quote
import org.signal.archive.proto.Reaction
import org.signal.archive.proto.SendStatus
import org.signal.archive.proto.SimpleChatUpdate
import org.signal.archive.proto.StandardMessage
import org.signal.archive.proto.Sticker
import org.signal.archive.proto.ViewOnceMessage
import org.signal.core.models.ServiceId
import org.signal.core.util.Base64
import org.signal.core.util.Hex
@@ -24,23 +41,6 @@ import org.thoughtcrime.securesms.attachments.PointerAttachment
import org.thoughtcrime.securesms.attachments.TombstoneAttachment
import org.thoughtcrime.securesms.backup.v2.ImportSkips
import org.thoughtcrime.securesms.backup.v2.ImportState
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
import org.thoughtcrime.securesms.backup.v2.proto.MessageAttachment
import org.thoughtcrime.securesms.backup.v2.proto.PaymentNotification
import org.thoughtcrime.securesms.backup.v2.proto.Quote
import org.thoughtcrime.securesms.backup.v2.proto.Reaction
import org.thoughtcrime.securesms.backup.v2.proto.SendStatus
import org.thoughtcrime.securesms.backup.v2.proto.SimpleChatUpdate
import org.thoughtcrime.securesms.backup.v2.proto.StandardMessage
import org.thoughtcrime.securesms.backup.v2.proto.Sticker
import org.thoughtcrime.securesms.backup.v2.proto.ViewOnceMessage
import org.thoughtcrime.securesms.backup.v2.util.toLocalAttachment
import org.thoughtcrime.securesms.contactshare.Contact
import org.thoughtcrime.securesms.database.AttachmentTable
@@ -89,7 +89,7 @@ import java.math.BigInteger
import java.sql.SQLException
import java.util.Optional
import java.util.UUID
import org.thoughtcrime.securesms.backup.v2.proto.GiftBadge as BackupGiftBadge
import org.signal.archive.proto.GiftBadge as BackupGiftBadge
/**
* An object that will ingest all of the [ChatItem]s you want to write, buffer them until hitting a specified batch size, and then batch insert them
@@ -194,7 +194,8 @@ class ChatItemArchiveImporter(
return
}
if (chatItem.adminDeletedMessage != null && importState.remoteToLocalRecipientId[chatItem.adminDeletedMessage.adminId] == null) {
val adminDeletedMessage = chatItem.adminDeletedMessage
if (adminDeletedMessage != null && importState.remoteToLocalRecipientId[adminDeletedMessage.adminId] == null) {
Log.w(TAG, ImportSkips.missingAdminDeleteRecipient(chatItem.dateSent, chatItem.chatId))
return
}
@@ -286,17 +287,22 @@ class ChatItemArchiveImporter(
val followUps: MutableList<(Long) -> Unit> = mutableListOf()
if (this.updateMessage != null) {
if (this.updateMessage.individualCall != null && this.updateMessage.individualCall.callId != null) {
val updateMessage = this.updateMessage
if (updateMessage != null) {
val individualCall = updateMessage.individualCall
val groupCall = updateMessage.groupCall
val pollTerminate = updateMessage.pollTerminate
val pinMessage = updateMessage.pinMessage
if (individualCall != null && individualCall.callId != null) {
followUps += { messageRowId ->
val values = contentValuesOf(
CallTable.CALL_ID to updateMessage.individualCall.callId,
CallTable.CALL_ID to individualCall.callId,
CallTable.MESSAGE_ID to messageRowId,
CallTable.PEER to chatRecipientId.serialize(),
CallTable.TYPE to CallTable.Type.serialize(if (updateMessage.individualCall.type == IndividualCall.Type.VIDEO_CALL) CallTable.Type.VIDEO_CALL else CallTable.Type.AUDIO_CALL),
CallTable.DIRECTION to CallTable.Direction.serialize(if (updateMessage.individualCall.direction == IndividualCall.Direction.OUTGOING) CallTable.Direction.OUTGOING else CallTable.Direction.INCOMING),
CallTable.TYPE to CallTable.Type.serialize(if (individualCall.type == IndividualCall.Type.VIDEO_CALL) CallTable.Type.VIDEO_CALL else CallTable.Type.AUDIO_CALL),
CallTable.DIRECTION to CallTable.Direction.serialize(if (individualCall.direction == IndividualCall.Direction.OUTGOING) CallTable.Direction.OUTGOING else CallTable.Direction.INCOMING),
CallTable.EVENT to CallTable.Event.serialize(
when (updateMessage.individualCall.state) {
when (individualCall.state) {
IndividualCall.State.MISSED -> CallTable.Event.MISSED
IndividualCall.State.MISSED_NOTIFICATION_PROFILE -> CallTable.Event.MISSED_NOTIFICATION_PROFILE
IndividualCall.State.ACCEPTED -> CallTable.Event.ACCEPTED
@@ -304,24 +310,24 @@ class ChatItemArchiveImporter(
else -> CallTable.Event.MISSED
}
),
CallTable.TIMESTAMP to updateMessage.individualCall.startedCallTimestamp,
CallTable.READ to updateMessage.individualCall.read
CallTable.TIMESTAMP to individualCall.startedCallTimestamp,
CallTable.READ to individualCall.read
)
db.insert(CallTable.TABLE_NAME, SQLiteDatabase.CONFLICT_IGNORE, values)
}
} else if (this.updateMessage.groupCall != null && this.updateMessage.groupCall.callId != null) {
} else if (groupCall != null && groupCall.callId != null) {
followUps += { messageRowId ->
val ringer: RecipientId? = this.updateMessage.groupCall.ringerRecipientId?.let { importState.remoteToLocalRecipientId[it] }
val ringer: RecipientId? = groupCall.ringerRecipientId?.let { importState.remoteToLocalRecipientId[it] }
val values = contentValuesOf(
CallTable.CALL_ID to updateMessage.groupCall.callId,
CallTable.CALL_ID to groupCall.callId,
CallTable.MESSAGE_ID to messageRowId,
CallTable.PEER to chatRecipientId.serialize(),
CallTable.RINGER to ringer?.serialize(),
CallTable.TYPE to CallTable.Type.serialize(CallTable.Type.GROUP_CALL),
CallTable.DIRECTION to CallTable.Direction.serialize(if (ringer == selfId) CallTable.Direction.OUTGOING else CallTable.Direction.INCOMING),
CallTable.EVENT to CallTable.Event.serialize(
when (updateMessage.groupCall.state) {
when (groupCall.state) {
GroupCall.State.ACCEPTED -> CallTable.Event.ACCEPTED
GroupCall.State.MISSED -> CallTable.Event.MISSED
GroupCall.State.MISSED_NOTIFICATION_PROFILE -> CallTable.Event.MISSED_NOTIFICATION_PROFILE
@@ -333,17 +339,17 @@ class ChatItemArchiveImporter(
else -> CallTable.Event.GENERIC_GROUP_CALL
}
),
CallTable.TIMESTAMP to updateMessage.groupCall.startedCallTimestamp,
CallTable.TIMESTAMP to groupCall.startedCallTimestamp,
CallTable.READ to CallTable.ReadState.serialize(CallTable.ReadState.READ)
)
db.insert(CallTable.TABLE_NAME, SQLiteDatabase.CONFLICT_IGNORE, values)
}
} else if (this.updateMessage.pollTerminate != null) {
} else if (pollTerminate != null) {
followUps += { endPollMessageId ->
val pollMessageId = SignalDatabase.messages.getMessageFor(updateMessage.pollTerminate.targetSentTimestamp, fromRecipientId)?.id ?: -1
val pollMessageId = SignalDatabase.messages.getMessageFor(pollTerminate.targetSentTimestamp, fromRecipientId)?.id ?: -1
val pollId = SignalDatabase.polls.getPollId(pollMessageId)
val messageExtras = MessageExtras(pollTerminate = PollTerminate(question = updateMessage.pollTerminate.question, messageId = pollMessageId, targetTimestamp = updateMessage.pollTerminate.targetSentTimestamp))
val messageExtras = MessageExtras(pollTerminate = PollTerminate(question = pollTerminate.question, messageId = pollMessageId, targetTimestamp = pollTerminate.targetSentTimestamp))
db.update(MessageTable.TABLE_NAME)
.values(MessageTable.MESSAGE_EXTRAS to messageExtras.encode())
.where("${MessageTable.ID} = ?", endPollMessageId)
@@ -353,16 +359,16 @@ class ChatItemArchiveImporter(
SignalDatabase.polls.endPoll(pollId = pollId, endingMessageId = endPollMessageId)
}
}
} else if (this.updateMessage.pinMessage != null) {
} else if (pinMessage != null) {
followUps += { pinUpdateMessageId ->
val targetAuthorId = importState.remoteToLocalRecipientId[updateMessage.pinMessage.authorId]
val targetAuthorId = importState.remoteToLocalRecipientId[pinMessage.authorId]
if (targetAuthorId != null) {
val pinnedMessageId = SignalDatabase.messages.getMessageFor(updateMessage.pinMessage.targetSentTimestamp, targetAuthorId)?.id ?: -1
val pinnedMessageId = SignalDatabase.messages.getMessageFor(pinMessage.targetSentTimestamp, targetAuthorId)?.id ?: -1
val messageExtras = MessageExtras(
pinnedMessage = PinnedMessage(
pinnedMessageId = pinnedMessageId,
targetAuthorAci = recipients.getRecord(targetAuthorId).aci!!.toByteString(),
targetTimestamp = updateMessage.pinMessage.targetSentTimestamp
targetTimestamp = pinMessage.targetSentTimestamp
)
)
@@ -397,8 +403,9 @@ class ChatItemArchiveImporter(
}
}
if (this.contactMessage != null) {
val contact = this.contactMessage.contact?.let { backupContact ->
val contactMessage = this.contactMessage
if (contactMessage != null) {
val contact = contactMessage.contact?.let { backupContact ->
Contact(
backupContact.name.toLocal(),
backupContact.organization,
@@ -453,8 +460,9 @@ class ChatItemArchiveImporter(
}
}
if (this.directStoryReplyMessage != null) {
val (trimmedBodyText, longTextAttachment) = this.directStoryReplyMessage.parseBodyText(importState)
val directStoryReplyMessage = this.directStoryReplyMessage
if (directStoryReplyMessage != null) {
val (trimmedBodyText, longTextAttachment) = directStoryReplyMessage.parseBodyText(importState)
if (trimmedBodyText != null) {
contentValues.put(MessageTable.BODY, trimmedBodyText)
}
@@ -469,25 +477,26 @@ class ChatItemArchiveImporter(
}
}
if (this.standardMessage != null) {
val mentions = this.standardMessage.text?.bodyRanges.filterToLocalMentions()
val standardMessage = this.standardMessage
if (standardMessage != null) {
val mentions = standardMessage.text?.bodyRanges.filterToLocalMentions()
if (mentions.isNotEmpty()) {
followUps += { messageId ->
SignalDatabase.mentions.insert(threadId, messageId, mentions)
}
}
val linkPreviews = this.standardMessage.linkPreview.map { it.toLocalLinkPreview() }
val linkPreviews = standardMessage.linkPreview.map { it.toLocalLinkPreview() }
val linkPreviewAttachments: List<Attachment> = linkPreviews.mapNotNull { it.thumbnail.orNull() }
val attachments: List<Attachment> = this.standardMessage.attachments.mapNotNull { attachment ->
val attachments: List<Attachment> = standardMessage.attachments.mapNotNull { attachment ->
attachment.toLocalAttachment()
}
val (trimmedBodyText, longTextAttachment) = this.standardMessage.parseBodyText(importState)
val (trimmedBodyText, longTextAttachment) = standardMessage.parseBodyText(importState)
if (trimmedBodyText != null) {
contentValues.put(MessageTable.BODY, trimmedBodyText)
}
val quoteAttachments: List<Attachment> = this.standardMessage.quote?.toLocalAttachments() ?: emptyList()
val quoteAttachments: List<Attachment> = standardMessage.quote?.toLocalAttachments() ?: emptyList()
val hasAttachments = attachments.isNotEmpty() || linkPreviewAttachments.isNotEmpty() || quoteAttachments.isNotEmpty() || longTextAttachment != null
@@ -515,8 +524,9 @@ class ChatItemArchiveImporter(
}
}
if (this.stickerMessage != null) {
val sticker = this.stickerMessage.sticker
val stickerMessage = this.stickerMessage
if (stickerMessage != null) {
val sticker = stickerMessage.sticker
val attachment = sticker.toLocalAttachment()
if (attachment != null) {
followUps += { messageRowId ->
@@ -525,8 +535,9 @@ class ChatItemArchiveImporter(
}
}
if (this.viewOnceMessage != null) {
val attachment = this.viewOnceMessage.attachment?.toLocalAttachment()
val viewOnceMessage = this.viewOnceMessage
if (viewOnceMessage != null) {
val attachment = viewOnceMessage.attachment?.toLocalAttachment()
if (attachment != null) {
followUps += { messageRowId ->
SignalDatabase.attachments.insertAttachmentsForMessage(messageRowId, listOf(attachment), emptyList())
@@ -534,7 +545,8 @@ class ChatItemArchiveImporter(
}
}
if (this.poll != null) {
val poll = this.poll
if (poll != null) {
contentValues.put(MessageTable.BODY, poll.question)
contentValues.put(MessageTable.VOTES_LAST_SEEN, System.currentTimeMillis())
@@ -582,15 +594,14 @@ class ChatItemArchiveImporter(
* If the attachment is non-null, then you should store it along with the message, as it contains the long text.
*/
private fun StandardMessage.parseBodyText(importState: ImportState): Pair<String?, Attachment?> {
if (this.longText != null) {
return null to this.longText.toLocalAttachment(contentType = "text/x-signal-plain")
val longText = this.longText
if (longText != null) {
return null to longText.toLocalAttachment(contentType = "text/x-signal-plain")
}
if (this.text?.body == null) {
return null to null
}
val body = this.text?.body ?: return null to null
val splitResult = MessageUtil.getSplitMessage(AppDependencies.application, this.text.body)
val splitResult = MessageUtil.getSplitMessage(AppDependencies.application, body)
if (splitResult.textSlide.isPresent) {
return splitResult.body to splitResult.textSlide.get().asAttachment()
}
@@ -606,15 +617,15 @@ class ChatItemArchiveImporter(
* If the attachment is non-null, then you should store it along with the message, as it contains the long text.
*/
private fun DirectStoryReplyMessage.parseBodyText(importState: ImportState): Pair<String?, Attachment?> {
if (this.textReply?.longText != null) {
return null to this.textReply.longText.toLocalAttachment(contentType = "text/x-signal-plain")
val textReply = this.textReply
val longText = textReply?.longText
if (longText != null) {
return null to longText.toLocalAttachment(contentType = "text/x-signal-plain")
}
if (this.textReply?.text == null) {
return null to null
}
val body = textReply?.text?.body ?: return null to null
val splitResult = MessageUtil.getSplitMessage(AppDependencies.application, this.textReply.text.body)
val splitResult = MessageUtil.getSplitMessage(AppDependencies.application, body)
if (splitResult.textSlide.isPresent) {
return splitResult.body to splitResult.textSlide.get().asAttachment()
}
@@ -625,16 +636,20 @@ class ChatItemArchiveImporter(
private fun ChatItem.toMessageContentValues(fromRecipientId: RecipientId, chatRecipientId: RecipientId, threadId: Long): ContentValues {
val contentValues = ContentValues()
val toRecipientId = if (this.outgoing != null) chatRecipientId else selfId
val outgoing = this.outgoing
val incoming = this.incoming
val directionless = this.directionless
val toRecipientId = if (outgoing != null) chatRecipientId else selfId
contentValues.put(MessageTable.TYPE, this.getMessageType())
contentValues.put(MessageTable.DATE_SENT, this.dateSent)
contentValues.put(MessageTable.DATE_SERVER, this.incoming?.dateServerSent ?: -1)
contentValues.put(MessageTable.DATE_SERVER, incoming?.dateServerSent ?: -1)
contentValues.put(MessageTable.FROM_RECIPIENT_ID, fromRecipientId.serialize())
contentValues.put(MessageTable.TO_RECIPIENT_ID, toRecipientId.serialize())
contentValues.put(MessageTable.THREAD_ID, threadId)
contentValues.put(MessageTable.DATE_RECEIVED, this.incoming?.dateReceived ?: this.outgoing?.dateReceived?.takeUnless { it == 0L } ?: this.dateSent)
contentValues.put(MessageTable.RECEIPT_TIMESTAMP, this.outgoing?.sendStatus?.maxOfOrNull { it.timestamp } ?: 0)
contentValues.put(MessageTable.DATE_RECEIVED, incoming?.dateReceived ?: outgoing?.dateReceived?.takeUnless { it == 0L } ?: this.dateSent)
contentValues.put(MessageTable.RECEIPT_TIMESTAMP, outgoing?.sendStatus?.maxOfOrNull { it.timestamp } ?: 0)
contentValues.putNull(MessageTable.LATEST_REVISION_ID)
contentValues.putNull(MessageTable.ORIGINAL_MESSAGE_ID)
contentValues.put(MessageTable.REVISION_NUMBER, 0)
@@ -642,29 +657,29 @@ class ChatItemArchiveImporter(
contentValues.put(MessageTable.EXPIRE_STARTED, this.expireStartDate ?: 0)
when {
this.outgoing != null -> {
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 }
outgoing != null -> {
val viewed = outgoing.sendStatus.any { it.viewed != null }
val hasReadReceipt = viewed || outgoing.sendStatus.any { it.read != null }
val hasDeliveryReceipt = viewed || hasReadReceipt || outgoing.sendStatus.any { it.delivered != null }
contentValues.put(MessageTable.VIEWED_COLUMN, viewed.toInt())
contentValues.put(MessageTable.HAS_READ_RECEIPT, hasReadReceipt.toInt())
contentValues.put(MessageTable.HAS_DELIVERY_RECEIPT, hasDeliveryReceipt.toInt())
contentValues.put(MessageTable.UNIDENTIFIED, this.outgoing.sendStatus.count { it.sealedSender })
contentValues.put(MessageTable.UNIDENTIFIED, outgoing.sendStatus.count { it.sealedSender })
contentValues.put(MessageTable.READ, 1)
contentValues.addNetworkFailures(this, importState)
contentValues.addIdentityKeyMismatches(this, importState)
}
this.incoming != null -> {
incoming != null -> {
contentValues.put(MessageTable.VIEWED_COLUMN, 0)
contentValues.put(MessageTable.HAS_READ_RECEIPT, 0)
contentValues.put(MessageTable.HAS_DELIVERY_RECEIPT, 0)
contentValues.put(MessageTable.UNIDENTIFIED, this.incoming.sealedSender.toInt())
contentValues.put(MessageTable.READ, this.incoming.read.toInt())
contentValues.put(MessageTable.UNIDENTIFIED, incoming.sealedSender.toInt())
contentValues.put(MessageTable.READ, incoming.read.toInt())
contentValues.put(MessageTable.NOTIFIED, 1)
}
this.directionless != null -> {
directionless != null -> {
contentValues.put(MessageTable.VIEWED_COLUMN, 0)
contentValues.put(MessageTable.HAS_READ_RECEIPT, 0)
contentValues.put(MessageTable.HAS_DELIVERY_RECEIPT, 0)
@@ -680,21 +695,30 @@ class ChatItemArchiveImporter(
contentValues.put(MessageTable.VIEW_ONCE, 0)
contentValues.put(MessageTable.PARENT_STORY_ID, 0)
if (this.pinDetails != null) {
val pinnedUntil = if (this.pinDetails.pinNeverExpires == true) MessageTable.PIN_FOREVER else this.pinDetails.pinExpiresAtTimestamp
val pinDetails = this.pinDetails
if (pinDetails != null) {
val pinnedUntil = if (pinDetails.pinNeverExpires == true) MessageTable.PIN_FOREVER else pinDetails.pinExpiresAtTimestamp
contentValues.put(MessageTable.PINNED_UNTIL, pinnedUntil ?: 0)
contentValues.put(MessageTable.PINNED_AT, this.pinDetails.pinnedAtTimestamp)
contentValues.put(MessageTable.PINNED_AT, pinDetails.pinnedAtTimestamp)
}
val itemStandardMessage = this.standardMessage
val itemRemoteDeletedMessage = this.remoteDeletedMessage
val itemUpdateMessage = this.updateMessage
val itemPaymentNotification = this.paymentNotification
val itemGiftBadge = this.giftBadge
val itemViewOnceMessage = this.viewOnceMessage
val itemDirectStoryReplyMessage = this.directStoryReplyMessage
val itemAdminDeletedMessage = this.adminDeletedMessage
when {
this.standardMessage != null -> contentValues.addStandardMessage(this.standardMessage)
this.remoteDeletedMessage != null -> contentValues.put(MessageTable.DELETED_BY, fromRecipientId.toLong())
this.updateMessage != null -> contentValues.addUpdateMessage(this.updateMessage, fromRecipientId, toRecipientId)
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, toRecipientId)
this.adminDeletedMessage != null -> contentValues.put(MessageTable.DELETED_BY, importState.remoteToLocalRecipientId[this.adminDeletedMessage.adminId]!!.toLong())
itemStandardMessage != null -> contentValues.addStandardMessage(itemStandardMessage)
itemRemoteDeletedMessage != null -> contentValues.put(MessageTable.DELETED_BY, fromRecipientId.toLong())
itemUpdateMessage != null -> contentValues.addUpdateMessage(itemUpdateMessage, fromRecipientId, toRecipientId)
itemPaymentNotification != null -> contentValues.addPaymentNotification(this, chatRecipientId)
itemGiftBadge != null -> contentValues.addGiftBadge(itemGiftBadge)
itemViewOnceMessage != null -> contentValues.addViewOnce(itemViewOnceMessage)
itemDirectStoryReplyMessage != null -> contentValues.addDirectStoryReply(itemDirectStoryReplyMessage, toRecipientId)
itemAdminDeletedMessage != null -> contentValues.put(MessageTable.DELETED_BY, importState.remoteToLocalRecipientId[itemAdminDeletedMessage.adminId]!!.toLong())
}
return contentValues
@@ -733,15 +757,13 @@ class ChatItemArchiveImporter(
}
private fun ChatItem.toReactionContentValues(messageId: Long): List<ContentValues> {
val reactions: List<Reaction> = when {
this.standardMessage != null -> this.standardMessage.reactions
this.contactMessage != null -> this.contactMessage.reactions
this.stickerMessage != null -> this.stickerMessage.reactions
this.viewOnceMessage != null -> this.viewOnceMessage.reactions
this.directStoryReplyMessage != null -> this.directStoryReplyMessage.reactions
this.poll != null -> this.poll.reactions
else -> emptyList()
}
val reactions: List<Reaction> = this.standardMessage?.reactions
?: this.contactMessage?.reactions
?: this.stickerMessage?.reactions
?: this.viewOnceMessage?.reactions
?: this.directStoryReplyMessage?.reactions
?: this.poll?.reactions
?: emptyList()
return reactions
.mapNotNull {
@@ -763,16 +785,14 @@ class ChatItemArchiveImporter(
}
private fun ChatItem.toGroupReceiptContentValues(messageId: Long, chatBackupRecipientId: Long): List<ContentValues> {
if (this.outgoing == null) {
return emptyList()
}
val outgoing = this.outgoing ?: return emptyList()
// 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) {
if (outgoing.sendStatus.size == 1 && outgoing.sendStatus[0].recipientId == chatBackupRecipientId) {
return emptyList()
}
return this.outgoing.sendStatus.mapNotNull { sendStatus ->
return outgoing.sendStatus.mapNotNull { sendStatus ->
val recipientId = importState.remoteToLocalRecipientId[sendStatus.recipientId]
if (recipientId != null) {
@@ -791,16 +811,17 @@ class ChatItemArchiveImporter(
}
private fun ChatItem.getMessageType(): Long {
var type: Long = if (this.outgoing != null) {
if (this.outgoing.sendStatus.any { it.pending != null }) {
val outgoing = this.outgoing
var type: Long = if (outgoing != null) {
if (outgoing.sendStatus.any { it.pending != null }) {
MessageTypes.BASE_SENDING_TYPE
} else if (this.outgoing.sendStatus.any { it.failed?.reason == SendStatus.Failed.FailureReason.IDENTITY_KEY_MISMATCH }) {
} else if (outgoing.sendStatus.any { it.failed?.reason == SendStatus.Failed.FailureReason.IDENTITY_KEY_MISMATCH }) {
MessageTypes.BASE_SENT_FAILED_TYPE
} else if (this.outgoing.sendStatus.any { it.failed?.reason == SendStatus.Failed.FailureReason.UNKNOWN }) {
} else if (outgoing.sendStatus.any { it.failed?.reason == SendStatus.Failed.FailureReason.UNKNOWN }) {
MessageTypes.BASE_SENT_FAILED_TYPE
} else if (this.outgoing.sendStatus.any { it.failed?.reason == SendStatus.Failed.FailureReason.NETWORK }) {
} else if (outgoing.sendStatus.any { it.failed?.reason == SendStatus.Failed.FailureReason.NETWORK }) {
MessageTypes.BASE_SENT_FAILED_TYPE
} else if (this.outgoing.sendStatus.all { it.skipped != null }) {
} else if (outgoing.sendStatus.all { it.skipped != null }) {
MessageTypes.BASE_SENDING_SKIPPED_TYPE
} else {
MessageTypes.BASE_SENT_TYPE
@@ -825,25 +846,38 @@ class ChatItemArchiveImporter(
}
private fun ContentValues.addStandardMessage(standardMessage: StandardMessage) {
if (standardMessage.text != null) {
this.put(MessageTable.BODY, standardMessage.text.body)
val text = standardMessage.text
if (text != null) {
this.put(MessageTable.BODY, text.body)
if (standardMessage.text.bodyRanges.isNotEmpty()) {
this.put(MessageTable.MESSAGE_RANGES, standardMessage.text.bodyRanges.toLocalBodyRanges()?.encode())
if (text.bodyRanges.isNotEmpty()) {
this.put(MessageTable.MESSAGE_RANGES, text.bodyRanges.toLocalBodyRanges()?.encode())
}
}
if (standardMessage.quote != null) {
this.addQuote(standardMessage.quote)
val quote = standardMessage.quote
if (quote != null) {
this.addQuote(quote)
}
}
private fun ContentValues.addUpdateMessage(updateMessage: ChatUpdateMessage, fromRecipientId: RecipientId, toRecipientId: RecipientId) {
var typeFlags: Long = 0
val simpleUpdate = updateMessage.simpleUpdate
val expirationTimerChange = updateMessage.expirationTimerChange
val profileChange = updateMessage.profileChange
val learnedProfileChange = updateMessage.learnedProfileChange
val pollTerminate = updateMessage.pollTerminate
val pinMessage = updateMessage.pinMessage
val sessionSwitchover = updateMessage.sessionSwitchover
val threadMerge = updateMessage.threadMerge
val individualCall = updateMessage.individualCall
val groupCall = updateMessage.groupCall
val groupChange = updateMessage.groupChange
when {
updateMessage.simpleUpdate != null -> {
simpleUpdate != null -> {
val typeWithoutBase = (getAsLong(MessageTable.TYPE) and MessageTypes.BASE_TYPE_MASK.inv())
typeFlags = when (updateMessage.simpleUpdate.type) {
typeFlags = when (simpleUpdate.type) {
SimpleChatUpdate.Type.UNKNOWN -> typeWithoutBase
SimpleChatUpdate.Type.JOINED_SIGNAL -> MessageTypes.JOINED_TYPE or typeWithoutBase
SimpleChatUpdate.Type.IDENTITY_UPDATE -> MessageTypes.KEY_EXCHANGE_IDENTITY_UPDATE_BIT or typeWithoutBase
@@ -864,59 +898,59 @@ class ChatItemArchiveImporter(
}
// Identity verification changes have to/from swapped
if (updateMessage.simpleUpdate.type == SimpleChatUpdate.Type.IDENTITY_VERIFIED || updateMessage.simpleUpdate.type == SimpleChatUpdate.Type.IDENTITY_DEFAULT) {
if (simpleUpdate.type == SimpleChatUpdate.Type.IDENTITY_VERIFIED || simpleUpdate.type == SimpleChatUpdate.Type.IDENTITY_DEFAULT) {
put(MessageTable.FROM_RECIPIENT_ID, toRecipientId.serialize())
put(MessageTable.TO_RECIPIENT_ID, fromRecipientId.serialize())
}
}
updateMessage.expirationTimerChange != null -> {
expirationTimerChange != null -> {
typeFlags = getAsLong(MessageTable.TYPE) or MessageTypes.EXPIRATION_TIMER_UPDATE_BIT
put(MessageTable.EXPIRES_IN, updateMessage.expirationTimerChange.expiresInMs)
put(MessageTable.EXPIRES_IN, expirationTimerChange.expiresInMs)
}
updateMessage.profileChange != null -> {
profileChange != null -> {
typeFlags = MessageTypes.PROFILE_CHANGE_TYPE
val profileChangeDetails = ProfileChangeDetails(profileNameChange = ProfileChangeDetails.StringChange(previous = updateMessage.profileChange.previousName, newValue = updateMessage.profileChange.newName))
val profileChangeDetails = ProfileChangeDetails(profileNameChange = ProfileChangeDetails.StringChange(previous = profileChange.previousName, newValue = profileChange.newName))
val messageExtras = MessageExtras(profileChangeDetails = profileChangeDetails).encode()
put(MessageTable.MESSAGE_EXTRAS, messageExtras)
}
updateMessage.learnedProfileChange != null -> {
learnedProfileChange != null -> {
typeFlags = MessageTypes.PROFILE_CHANGE_TYPE
val profileChangeDetails = ProfileChangeDetails(learnedProfileName = ProfileChangeDetails.LearnedProfileName(e164 = updateMessage.learnedProfileChange.e164?.toString(), username = updateMessage.learnedProfileChange.username))
val profileChangeDetails = ProfileChangeDetails(learnedProfileName = ProfileChangeDetails.LearnedProfileName(e164 = learnedProfileChange.e164?.toString(), username = learnedProfileChange.username))
val messageExtras = MessageExtras(profileChangeDetails = profileChangeDetails).encode()
put(MessageTable.MESSAGE_EXTRAS, messageExtras)
}
updateMessage.pollTerminate != null -> {
pollTerminate != null -> {
typeFlags = MessageTypes.SPECIAL_TYPE_POLL_TERMINATE or (getAsLong(MessageTable.TYPE) and MessageTypes.BASE_TYPE_MASK.inv())
}
updateMessage.pinMessage != null -> {
pinMessage != null -> {
typeFlags = MessageTypes.SPECIAL_TYPE_PINNED_MESSAGE or (getAsLong(MessageTable.TYPE) and MessageTypes.BASE_TYPE_MASK.inv())
}
updateMessage.sessionSwitchover != null -> {
sessionSwitchover != null -> {
typeFlags = MessageTypes.SESSION_SWITCHOVER_TYPE or (getAsLong(MessageTable.TYPE) and MessageTypes.BASE_TYPE_MASK.inv())
val sessionSwitchoverDetails = SessionSwitchoverEvent(e164 = updateMessage.sessionSwitchover.e164.toString()).encode()
val sessionSwitchoverDetails = SessionSwitchoverEvent(e164 = sessionSwitchover.e164.toString()).encode()
put(MessageTable.BODY, Base64.encodeWithPadding(sessionSwitchoverDetails))
}
updateMessage.threadMerge != null -> {
threadMerge != null -> {
typeFlags = MessageTypes.THREAD_MERGE_TYPE or (getAsLong(MessageTable.TYPE) and MessageTypes.BASE_TYPE_MASK.inv())
val threadMergeDetails = ThreadMergeEvent(previousE164 = updateMessage.threadMerge.previousE164.toString()).encode()
val threadMergeDetails = ThreadMergeEvent(previousE164 = threadMerge.previousE164.toString()).encode()
put(MessageTable.BODY, Base64.encodeWithPadding(threadMergeDetails))
}
updateMessage.individualCall != null -> {
if (updateMessage.individualCall.state == IndividualCall.State.MISSED || updateMessage.individualCall.state == IndividualCall.State.MISSED_NOTIFICATION_PROFILE) {
typeFlags = if (updateMessage.individualCall.type == IndividualCall.Type.AUDIO_CALL) {
individualCall != null -> {
if (individualCall.state == IndividualCall.State.MISSED || individualCall.state == IndividualCall.State.MISSED_NOTIFICATION_PROFILE) {
typeFlags = if (individualCall.type == IndividualCall.Type.AUDIO_CALL) {
MessageTypes.MISSED_AUDIO_CALL_TYPE
} else {
MessageTypes.MISSED_VIDEO_CALL_TYPE
}
} else {
typeFlags = if (updateMessage.individualCall.direction == IndividualCall.Direction.OUTGOING) {
if (updateMessage.individualCall.type == IndividualCall.Type.AUDIO_CALL) {
typeFlags = if (individualCall.direction == IndividualCall.Direction.OUTGOING) {
if (individualCall.type == IndividualCall.Type.AUDIO_CALL) {
MessageTypes.OUTGOING_AUDIO_CALL_TYPE
} else {
MessageTypes.OUTGOING_VIDEO_CALL_TYPE
}
} else {
if (updateMessage.individualCall.type == IndividualCall.Type.AUDIO_CALL) {
if (individualCall.type == IndividualCall.Type.AUDIO_CALL) {
MessageTypes.INCOMING_AUDIO_CALL_TYPE
} else {
MessageTypes.INCOMING_VIDEO_CALL_TYPE
@@ -925,28 +959,24 @@ class ChatItemArchiveImporter(
}
this.put(MessageTable.READ, 1)
}
updateMessage.groupCall != null -> {
val startedCallRecipientId = if (updateMessage.groupCall.startedCallRecipientId != null) {
importState.remoteToLocalRecipientId[updateMessage.groupCall.startedCallRecipientId]
} else {
null
}
groupCall != null -> {
val startedCallRecipientId = groupCall.startedCallRecipientId?.let { importState.remoteToLocalRecipientId[it] }
val startedCall = if (startedCallRecipientId != null) {
recipients.getRecord(startedCallRecipientId).aci
} else {
null
}
this.put(MessageTable.BODY, GroupCallUpdateDetailsUtil.createBodyFromBackup(updateMessage.groupCall, startedCall))
this.put(MessageTable.READ, updateMessage.groupCall.read.toInt())
this.put(MessageTable.BODY, GroupCallUpdateDetailsUtil.createBodyFromBackup(groupCall, startedCall))
this.put(MessageTable.READ, groupCall.read.toInt())
typeFlags = MessageTypes.GROUP_CALL_TYPE
}
updateMessage.groupChange != null -> {
groupChange != null -> {
put(MessageTable.BODY, "")
put(
MessageTable.MESSAGE_EXTRAS,
MessageExtras(
gv2UpdateDescription =
GV2UpdateDescription(groupChangeUpdate = updateMessage.groupChange)
GV2UpdateDescription(groupChangeUpdate = groupChange)
).encode()
)
typeFlags = getAsLong(MessageTable.TYPE) or MessageTypes.GROUP_V2_BIT or MessageTypes.GROUP_UPDATE_BIT
@@ -962,18 +992,18 @@ class ChatItemArchiveImporter(
*/
private fun ContentValues.addPaymentNotification(chatItem: ChatItem, chatRecipientId: RecipientId) {
val paymentNotification = chatItem.paymentNotification!!
if (chatItem.paymentNotification.amountMob.isNullOrEmpty()) {
if (paymentNotification.amountMob.isNullOrEmpty()) {
this.addPaymentTombstoneNoAmount()
return
}
val amount = paymentNotification.amountMob?.tryParseMoney() ?: return this.addPaymentTombstoneNoAmount()
val fee = paymentNotification.feeMob?.tryParseMoney() ?: return this.addPaymentTombstoneNoAmount()
if (chatItem.paymentNotification.transactionDetails?.failedTransaction != null) {
if (paymentNotification.transactionDetails?.failedTransaction != null) {
this.addFailedPaymentNotification(chatItem, amount, fee, chatRecipientId)
return
}
this.addPaymentTombstoneNoMetadata(chatItem.paymentNotification)
this.addPaymentTombstoneNoMetadata(paymentNotification)
}
private fun PaymentNotification.TransactionDetails.MobileCoinTxoIdentification.toLocal(): PaymentMetaData {
@@ -1062,13 +1092,15 @@ class ChatItemArchiveImporter(
put(MessageTable.QUOTE_ID, MessageTable.QUOTE_TARGET_MISSING_ID)
put(MessageTable.QUOTE_AUTHOR, toRecipientId.serialize())
if (directStoryReply.emoji != null) {
put(MessageTable.BODY, directStoryReply.emoji)
val emoji = directStoryReply.emoji
if (emoji != null) {
put(MessageTable.BODY, emoji)
}
if (directStoryReply.textReply != null) {
put(MessageTable.BODY, directStoryReply.textReply.text?.body)
put(MessageTable.MESSAGE_RANGES, directStoryReply.textReply.text?.bodyRanges?.toLocalBodyRanges()?.encode())
val textReply = directStoryReply.textReply
if (textReply != null) {
put(MessageTable.BODY, textReply.text?.body)
put(MessageTable.MESSAGE_RANGES, textReply.text?.bodyRanges?.toLocalBodyRanges()?.encode())
}
}
@@ -1126,11 +1158,9 @@ class ChatItemArchiveImporter(
}
private fun ContentValues.addNetworkFailures(chatItem: ChatItem, importState: ImportState) {
if (chatItem.outgoing == null) {
return
}
val outgoing = chatItem.outgoing ?: return
val networkFailures = chatItem.outgoing.sendStatus
val networkFailures = outgoing.sendStatus
.filter { status -> status.failed?.reason == SendStatus.Failed.FailureReason.NETWORK }
.mapNotNull { status -> importState.remoteToLocalRecipientId[status.recipientId] }
.map { recipientId -> NetworkFailure(recipientId) }
@@ -1142,11 +1172,9 @@ class ChatItemArchiveImporter(
}
private fun ContentValues.addIdentityKeyMismatches(chatItem: ChatItem, importState: ImportState) {
if (chatItem.outgoing == null) {
return
}
val outgoing = chatItem.outgoing ?: return
val mismatches = chatItem.outgoing.sendStatus
val mismatches = outgoing.sendStatus
.filter { status -> status.failed?.reason == SendStatus.Failed.FailureReason.IDENTITY_KEY_MISMATCH }
.mapNotNull { status -> importState.remoteToLocalRecipientId[status.recipientId] }
.map { recipientId -> IdentityKeyMismatch(recipientId, null) } // TODO We probably want the actual identity key in this status situation?
@@ -1166,8 +1194,8 @@ class ChatItemArchiveImporter(
ranges = this.filter { includeMentions || it.mentionAci == null }.map { bodyRange ->
BodyRangeList.BodyRange(
mentionUuid = bodyRange.mentionAci?.let { UuidUtil.fromByteString(it) }?.toString(),
style = bodyRange.style?.let {
when (bodyRange.style) {
style = bodyRange.style?.let { style ->
when (style) {
BodyRange.Style.BOLD -> BodyRangeList.BodyRange.Style.BOLD
BodyRange.Style.ITALIC -> BodyRangeList.BodyRange.Style.ITALIC
BodyRange.Style.MONOSPACE -> BodyRangeList.BodyRange.Style.MONOSPACE
@@ -1217,13 +1245,11 @@ class ChatItemArchiveImporter(
return@mapNotNull thumbnail
}
if (attachment.contentType == null) {
return@mapNotNull null
}
val contentType = attachment.contentType ?: return@mapNotNull null
return@mapNotNull PointerAttachment.forPointer(
quotedAttachment = DataMessage.Quote.QuotedAttachment(
contentType = attachment.contentType,
contentType = contentType,
fileName = attachment.fileName,
thumbnail = null
)
@@ -1259,7 +1285,8 @@ class ChatItemArchiveImporter(
}
private fun MessageAttachment.toLocalAttachment(quote: Boolean = false, quoteTargetContentType: String? = null, contentType: String? = pointer?.contentType): Attachment? {
return pointer?.toLocalAttachment(
val pointer = this.pointer ?: return null
return pointer.toLocalAttachment(
voiceNote = flag == MessageAttachment.Flag.VOICE_MESSAGE,
borderless = flag == MessageAttachment.Flag.BORDERLESS,
gif = flag == MessageAttachment.Flag.GIF,

View File

@@ -6,6 +6,7 @@
package org.thoughtcrime.securesms.backup.v2.importer
import androidx.core.content.contentValuesOf
import org.signal.archive.proto.Contact
import org.signal.core.models.ServiceId.ACI
import org.signal.core.models.ServiceId.PNI
import org.signal.core.util.Base64
@@ -14,7 +15,6 @@ import org.signal.core.util.logging.Log
import org.signal.core.util.toInt
import org.signal.core.util.update
import org.thoughtcrime.securesms.backup.v2.ImportSkips
import org.thoughtcrime.securesms.backup.v2.proto.Contact
import org.thoughtcrime.securesms.backup.v2.util.toLocal
import org.thoughtcrime.securesms.database.IdentityTable
import org.thoughtcrime.securesms.database.RecipientTable
@@ -70,11 +70,12 @@ object ContactArchiveImporter {
RecipientTable.KEY_TRANSPARENCY_DATA to contact.keyTransparencyData?.toByteArray()
)
val notRegistered = contact.notRegistered
if (contact.registered != null) {
values.put(RecipientTable.UNREGISTERED_TIMESTAMP, 0L)
values.put(RecipientTable.REGISTERED, RecipientTable.RegisteredState.REGISTERED.id)
} else if (contact.notRegistered != null) {
values.put(RecipientTable.UNREGISTERED_TIMESTAMP, contact.notRegistered.unregisteredTimestamp)
} else if (notRegistered != null) {
values.put(RecipientTable.UNREGISTERED_TIMESTAMP, notRegistered.unregisteredTimestamp)
values.put(RecipientTable.REGISTERED, RecipientTable.RegisteredState.NOT_REGISTERED.id)
}
@@ -84,12 +85,13 @@ object ContactArchiveImporter {
.where("${RecipientTable.ID} = ?", id)
.run()
if (contact.identityKey != null && (aci != null || pni != null)) {
val identityKey = contact.identityKey
if (identityKey != null && (aci != null || pni != null)) {
SignalDatabase.writableDatabase
.insertInto(IdentityTable.TABLE_NAME)
.values(
IdentityTable.ADDRESS to (aci ?: pni).toString(),
IdentityTable.IDENTITY_KEY to Base64.encodeWithPadding(contact.identityKey.toByteArray()),
IdentityTable.IDENTITY_KEY to Base64.encodeWithPadding(identityKey.toByteArray()),
IdentityTable.VERIFIED to contact.identityState.toLocal().toInt()
)
.run(SQLiteDatabase.CONFLICT_REPLACE)

View File

@@ -5,11 +5,11 @@
package org.thoughtcrime.securesms.backup.v2.importer
import org.signal.archive.proto.DistributionList
import org.signal.archive.proto.DistributionListItem
import org.signal.core.util.UuidUtil
import org.signal.core.util.logging.Log
import org.thoughtcrime.securesms.backup.v2.ImportState
import org.thoughtcrime.securesms.backup.v2.proto.DistributionList
import org.thoughtcrime.securesms.backup.v2.proto.DistributionListItem
import org.thoughtcrime.securesms.database.SignalDatabase
import org.thoughtcrime.securesms.database.model.DistributionListPrivacyMode
import org.thoughtcrime.securesms.recipients.RecipientId
@@ -23,13 +23,14 @@ object DistributionListArchiveImporter {
private val TAG = Log.tag(DistributionListArchiveImporter.javaClass)
fun import(dlistItem: DistributionListItem, importState: ImportState): RecipientId? {
if (dlistItem.deletionTimestamp != null && dlistItem.deletionTimestamp > 0) {
val deletionTimestamp = dlistItem.deletionTimestamp
if (deletionTimestamp != null && deletionTimestamp > 0) {
val dlistId = SignalDatabase.distributionLists.createList(
name = "",
members = emptyList(),
distributionId = DistributionId.from(UuidUtil.fromByteString(dlistItem.distributionId)),
allowsReplies = false,
deletionTimestamp = dlistItem.deletionTimestamp,
deletionTimestamp = deletionTimestamp,
storageId = null,
privacyMode = DistributionListPrivacyMode.ONLY_WITH
)!!

View File

@@ -6,6 +6,7 @@
package org.thoughtcrime.securesms.backup.v2.importer
import android.content.ContentValues
import org.signal.archive.proto.Group
import org.signal.core.models.ServiceId
import org.signal.core.util.Base64
import org.signal.core.util.toInt
@@ -21,7 +22,6 @@ import org.signal.storageservice.storage.protos.groups.local.DecryptedRequesting
import org.signal.storageservice.storage.protos.groups.local.DecryptedTimer
import org.signal.storageservice.storage.protos.groups.local.EnabledState
import org.thoughtcrime.securesms.backup.v2.ArchiveGroup
import org.thoughtcrime.securesms.backup.v2.proto.Group
import org.thoughtcrime.securesms.backup.v2.util.toLocal
import org.thoughtcrime.securesms.conversation.colors.AvatarColorHash
import org.thoughtcrime.securesms.database.GroupTable
@@ -45,10 +45,11 @@ object GroupArchiveImporter {
val groupId = GroupId.v2(masterKey)
val operations = AppDependencies.groupsV2Operations.forGroup(GroupSecretParams.deriveFromMasterKey(masterKey))
val decryptedState = if (group.snapshot == null) {
val snapshot = group.snapshot
val decryptedState = if (snapshot == null) {
DecryptedGroup(revision = GroupsV2StateProcessor.RESTORE_PLACEHOLDER_REVISION)
} else {
group.snapshot.toLocal(operations)
snapshot.toLocal(operations)
}
val values = ContentValues().apply {
@@ -84,12 +85,13 @@ private fun Group.StorySendMode.toLocal(): GroupTable.ShowAsStoryState {
}
private fun Group.MemberPendingProfileKey.toLocal(operations: GroupsV2Operations.GroupOperations): DecryptedPendingMember {
val m = member!!
return DecryptedPendingMember(
serviceIdBytes = member!!.userId,
role = member.role.toLocal(),
serviceIdBytes = m.userId,
role = m.role.toLocal(),
addedByAci = addedByUserId,
timestamp = timestamp,
serviceIdCipherText = operations.encryptServiceId(ServiceId.Companion.parseOrNull(member.userId))
serviceIdCipherText = operations.encryptServiceId(ServiceId.Companion.parseOrNull(m.userId))
)
}

View File

@@ -13,6 +13,7 @@ import kotlinx.coroutines.async
import kotlinx.coroutines.awaitAll
import kotlinx.coroutines.coroutineScope
import kotlinx.coroutines.runBlocking
import org.signal.archive.local.ArchivedFilesReader
import org.signal.core.models.backup.MediaName
import org.signal.core.util.Stopwatch
import org.signal.core.util.androidx.DocumentFileInfo

View File

@@ -6,6 +6,10 @@
package org.thoughtcrime.securesms.backup.v2.local
import okio.ByteString.Companion.toByteString
import org.signal.archive.local.ArchivedFilesWriter
import org.signal.archive.local.proto.FilesFrame
import org.signal.archive.local.proto.Metadata
import org.signal.archive.stream.EncryptedBackupReader
import org.signal.core.models.backup.BackupId
import org.signal.core.models.backup.MediaName
import org.signal.core.models.backup.MessageBackupKey
@@ -16,9 +20,6 @@ import org.signal.core.util.logging.Log
import org.signal.core.util.readFully
import org.thoughtcrime.securesms.attachments.AttachmentId
import org.thoughtcrime.securesms.backup.v2.BackupRepository
import org.thoughtcrime.securesms.backup.v2.local.proto.FilesFrame
import org.thoughtcrime.securesms.backup.v2.local.proto.Metadata
import org.thoughtcrime.securesms.backup.v2.stream.EncryptedBackupReader
import org.thoughtcrime.securesms.database.AttachmentTable
import org.thoughtcrime.securesms.keyvalue.SignalStore
import org.thoughtcrime.securesms.keyvalue.protos.LocalBackupCreationProgress
@@ -166,12 +167,13 @@ object LocalArchiver {
return RestoreResult.failure(RestoreFailure.VersionMismatch(metadata.version, VERSION))
}
if (metadata.backupId == null) {
val encryptedBackupId = metadata.backupId
if (encryptedBackupId == null) {
Log.w(TAG, "Local backup metadata missing encrypted backup id")
return RestoreResult.failure(RestoreFailure.BackupIdMissing)
}
val backupId = decryptBackupId(metadata.backupId, messageBackupKey)
val backupId = decryptBackupId(encryptedBackupId, messageBackupKey)
val mainStreamLength = snapshotFileSystem.mainLength() ?: return ArchiveResult.failure(RestoreFailure.MainStream)

View File

@@ -8,6 +8,10 @@ package org.thoughtcrime.securesms.backup.v2.processor
import android.content.Context
import okio.ByteString.Companion.EMPTY
import okio.ByteString.Companion.toByteString
import org.signal.archive.proto.AccountData
import org.signal.archive.proto.ChatStyle
import org.signal.archive.proto.Frame
import org.signal.archive.stream.BackupFrameEmitter
import org.signal.core.util.UuidUtil
import org.signal.core.util.logging.Log
import org.signal.core.util.toByteArray
@@ -18,10 +22,6 @@ import org.thoughtcrime.securesms.backup.v2.ImportState
import org.thoughtcrime.securesms.backup.v2.MessageBackupTier
import org.thoughtcrime.securesms.backup.v2.database.restoreSelfFromBackup
import org.thoughtcrime.securesms.backup.v2.database.restoreWallpaperAttachment
import org.thoughtcrime.securesms.backup.v2.proto.AccountData
import org.thoughtcrime.securesms.backup.v2.proto.ChatStyle
import org.thoughtcrime.securesms.backup.v2.proto.Frame
import org.thoughtcrime.securesms.backup.v2.stream.BackupFrameEmitter
import org.thoughtcrime.securesms.backup.v2.util.ChatStyleConverter
import org.thoughtcrime.securesms.backup.v2.util.isValid
import org.thoughtcrime.securesms.backup.v2.util.isValidUsername
@@ -178,10 +178,11 @@ object AccountDataArchiveProcessor {
importSettings(context, settings, importState)
}
if (accountData.androidSpecificSettings != null) {
SignalStore.settings.isPreferSystemEmoji = accountData.androidSpecificSettings.useSystemEmoji
TextSecurePreferences.setScreenSecurityEnabled(context, accountData.androidSpecificSettings.screenshotSecurity)
SignalStore.settings.useCompactNavigationBar = accountData.androidSpecificSettings.navigationBarSize.toLocalNavigationBarSize()
val androidSpecificSettings = accountData.androidSpecificSettings
if (androidSpecificSettings != null) {
SignalStore.settings.isPreferSystemEmoji = androidSpecificSettings.useSystemEmoji
TextSecurePreferences.setScreenSecurityEnabled(context, androidSpecificSettings.screenshotSecurity)
SignalStore.settings.useCompactNavigationBar = androidSpecificSettings.navigationBarSize.toLocalNavigationBarSize()
} else if (Environment.IS_INSTRUMENTATION) {
SignalStore.backup.importedEmptyAndroidSettings = true
}
@@ -190,16 +191,17 @@ object AccountDataArchiveProcessor {
SignalDatabase.recipients.setAbout(selfId, accountData.bioText.takeIf { it.isNotBlank() }, accountData.bioEmoji.takeIf { it.isNotBlank() })
}
if (accountData.donationSubscriberData != null) {
if (accountData.donationSubscriberData.subscriberId.size > 0) {
val remoteSubscriberId = SubscriberId.fromBytes(accountData.donationSubscriberData.subscriberId.toByteArray())
val donationSubscriberData = accountData.donationSubscriberData
if (donationSubscriberData != null) {
if (donationSubscriberData.subscriberId.size > 0) {
val remoteSubscriberId = SubscriberId.fromBytes(donationSubscriberData.subscriberId.toByteArray())
val localSubscriber = InAppPaymentsRepository.getSubscriber(InAppPaymentSubscriberRecord.Type.DONATION)
val subscriber = InAppPaymentSubscriberRecord(
subscriberId = remoteSubscriberId,
currency = Currency.getInstance(accountData.donationSubscriberData.currencyCode),
currency = Currency.getInstance(donationSubscriberData.currencyCode),
type = InAppPaymentSubscriberRecord.Type.DONATION,
requiresCancel = localSubscriber?.requiresCancel ?: accountData.donationSubscriberData.manuallyCancelled,
requiresCancel = localSubscriber?.requiresCancel ?: donationSubscriberData.manuallyCancelled,
paymentMethodType = InAppPaymentsRepository.getLatestPaymentMethodType(InAppPaymentSubscriberRecord.Type.DONATION),
iapSubscriptionId = null
)
@@ -207,25 +209,27 @@ object AccountDataArchiveProcessor {
InAppPaymentsRepository.setSubscriber(subscriber)
}
if (accountData.donationSubscriberData.manuallyCancelled) {
if (donationSubscriberData.manuallyCancelled) {
SignalStore.inAppPayments.updateLocalStateForManualCancellation(InAppPaymentSubscriberRecord.Type.DONATION)
}
}
if (accountData.backupsSubscriberData != null && accountData.backupsSubscriberData.subscriberId.size > 0 && (accountData.backupsSubscriberData.purchaseToken != null || accountData.backupsSubscriberData.originalTransactionId != null)) {
val remoteSubscriberId = SubscriberId.fromBytes(accountData.backupsSubscriberData.subscriberId.toByteArray())
val backupsSubscriberData = accountData.backupsSubscriberData
if (backupsSubscriberData != null && backupsSubscriberData.subscriberId.size > 0 && (backupsSubscriberData.purchaseToken != null || backupsSubscriberData.originalTransactionId != null)) {
val remoteSubscriberId = SubscriberId.fromBytes(backupsSubscriberData.subscriberId.toByteArray())
val localSubscriber = InAppPaymentsRepository.getSubscriber(InAppPaymentSubscriberRecord.Type.BACKUP)
val purchaseToken = backupsSubscriberData.purchaseToken
val subscriber = InAppPaymentSubscriberRecord(
subscriberId = remoteSubscriberId,
currency = localSubscriber?.currency,
type = InAppPaymentSubscriberRecord.Type.BACKUP,
requiresCancel = localSubscriber?.requiresCancel ?: false,
paymentMethodType = InAppPaymentData.PaymentMethodType.UNKNOWN,
iapSubscriptionId = if (accountData.backupsSubscriberData.purchaseToken != null) {
GooglePlayBillingPurchaseToken(accountData.backupsSubscriberData.purchaseToken)
iapSubscriptionId = if (purchaseToken != null) {
GooglePlayBillingPurchaseToken(purchaseToken)
} else {
AppleIAPOriginalTransactionId(accountData.backupsSubscriberData.originalTransactionId!!)
AppleIAPOriginalTransactionId(backupsSubscriberData.originalTransactionId!!)
}
)
@@ -236,12 +240,13 @@ object AccountDataArchiveProcessor {
AppDependencies.jobManager.add(RetrieveProfileAvatarJob(Recipient.self().fresh(), accountData.avatarUrlPath))
}
if (accountData.usernameLink != null) {
val usernameLink = accountData.usernameLink
if (usernameLink != null) {
SignalStore.account.usernameLink = UsernameLinkComponents(
accountData.usernameLink.entropy.toByteArray(),
UuidUtil.parseOrThrow(accountData.usernameLink.serverId.toByteArray())
usernameLink.entropy.toByteArray(),
UuidUtil.parseOrThrow(usernameLink.serverId.toByteArray())
)
SignalStore.misc.usernameQrCodeColorScheme = accountData.usernameLink.color.toLocalUsernameColor()
SignalStore.misc.usernameQrCodeColorScheme = usernameLink.color.toLocalUsernameColor()
} else {
SignalStore.account.usernameLink = null
}
@@ -278,9 +283,10 @@ object AccountDataArchiveProcessor {
SignalStore.settings.setCallDataMode(settings.callsUseLessDataSetting.toLocalCallDataMode())
SignalStore.settings.automaticVerificationEnabled = settings.allowAutomaticKeyVerification
if (settings.autoDownloadSettings != null) {
val mobileAndWifiDownloadSet = settings.autoDownloadSettings.toLocalAutoDownloadSet(AccountData.AutoDownloadSettings.AutoDownloadOption.WIFI_AND_CELLULAR)
val wifiDownloadSet = mobileAndWifiDownloadSet + settings.autoDownloadSettings.toLocalAutoDownloadSet(AccountData.AutoDownloadSettings.AutoDownloadOption.WIFI)
val autoDownloadSettings = settings.autoDownloadSettings
if (autoDownloadSettings != null) {
val mobileAndWifiDownloadSet = autoDownloadSettings.toLocalAutoDownloadSet(AccountData.AutoDownloadSettings.AutoDownloadOption.WIFI_AND_CELLULAR)
val wifiDownloadSet = mobileAndWifiDownloadSet + autoDownloadSettings.toLocalAutoDownloadSet(AccountData.AutoDownloadSettings.AutoDownloadOption.WIFI)
TextSecurePreferences.getSharedPreferences(context).edit().apply {
putStringSet(TextSecurePreferences.MEDIA_DOWNLOAD_MOBILE_PREF, mobileAndWifiDownloadSet)
@@ -289,28 +295,32 @@ object AccountDataArchiveProcessor {
}
}
if (settings.screenLockTimeoutMinutes != null) {
SignalStore.settings.screenLockTimeout = settings.screenLockTimeoutMinutes.minutes.inWholeSeconds
val screenLockTimeoutMinutes = settings.screenLockTimeoutMinutes
if (screenLockTimeoutMinutes != null) {
SignalStore.settings.screenLockTimeout = screenLockTimeoutMinutes.minutes.inWholeSeconds
}
if (settings.pinReminders != null) {
SignalStore.pin.setPinRemindersEnabled(settings.pinReminders)
val pinReminders = settings.pinReminders
if (pinReminders != null) {
SignalStore.pin.setPinRemindersEnabled(pinReminders)
}
settings.customChatColors
.mapNotNull { chatColor ->
val id = ChatColors.Id.forLongValue(chatColor.id)
val solidColor = chatColor.solid
val gradientColor = chatColor.gradient
when {
chatColor.solid != null -> {
ChatColors.forColor(id, chatColor.solid)
solidColor != null -> {
ChatColors.forColor(id, solidColor)
}
chatColor.gradient != null -> {
gradientColor != null -> {
ChatColors.forGradient(
id,
ChatColors.LinearGradient(
degrees = chatColor.gradient.angle.toFloat(),
colors = chatColor.gradient.colors.toIntArray(),
positions = chatColor.gradient.positions.toFloatArray()
degrees = gradientColor.angle.toFloat(),
colors = gradientColor.colors.toIntArray(),
positions = gradientColor.positions.toFloatArray()
)
)
}
@@ -323,17 +333,18 @@ object AccountDataArchiveProcessor {
importState.remoteToLocalColorId[chatColor.id.longValue] = saved.id.longValue
}
if (settings.defaultChatStyle != null) {
val chatColors = settings.defaultChatStyle.toLocal(importState)
val defaultChatStyle = settings.defaultChatStyle
if (defaultChatStyle != null) {
val chatColors = defaultChatStyle.toLocal(importState)
SignalStore.chatColors.chatColors = chatColors
val wallpaperAttachmentId: AttachmentId? = settings.defaultChatStyle.wallpaperPhoto?.let { filePointer ->
val wallpaperAttachmentId: AttachmentId? = defaultChatStyle.wallpaperPhoto?.let { filePointer ->
filePointer.toLocalAttachment()?.let {
SignalDatabase.attachments.restoreWallpaperAttachment(it)
}
}
SignalStore.wallpaper.wallpaper = settings.defaultChatStyle.parseChatWallpaper(wallpaperAttachmentId)
SignalStore.wallpaper.wallpaper = defaultChatStyle.parseChatWallpaper(wallpaperAttachmentId)
} else {
SignalStore.chatColors.chatColors = null
SignalStore.wallpaper.wallpaper = null

View File

@@ -5,15 +5,15 @@
package org.thoughtcrime.securesms.backup.v2.processor
import org.signal.archive.proto.AdHocCall
import org.signal.archive.proto.Frame
import org.signal.archive.stream.BackupFrameEmitter
import org.signal.core.util.logging.Log
import org.thoughtcrime.securesms.backup.v2.ExportSkips
import org.thoughtcrime.securesms.backup.v2.ExportState
import org.thoughtcrime.securesms.backup.v2.ImportState
import org.thoughtcrime.securesms.backup.v2.database.getAdhocCallsForBackup
import org.thoughtcrime.securesms.backup.v2.importer.AdHodCallArchiveImporter
import org.thoughtcrime.securesms.backup.v2.proto.AdHocCall
import org.thoughtcrime.securesms.backup.v2.proto.Frame
import org.thoughtcrime.securesms.backup.v2.stream.BackupFrameEmitter
import org.thoughtcrime.securesms.database.SignalDatabase
/**

View File

@@ -5,15 +5,15 @@
package org.thoughtcrime.securesms.backup.v2.processor
import org.signal.archive.proto.Chat
import org.signal.archive.proto.Frame
import org.signal.archive.stream.BackupFrameEmitter
import org.signal.core.util.logging.Log
import org.thoughtcrime.securesms.backup.v2.ExportState
import org.thoughtcrime.securesms.backup.v2.ImportSkips
import org.thoughtcrime.securesms.backup.v2.ImportState
import org.thoughtcrime.securesms.backup.v2.database.getThreadsForBackup
import org.thoughtcrime.securesms.backup.v2.importer.ChatArchiveImporter
import org.thoughtcrime.securesms.backup.v2.proto.Chat
import org.thoughtcrime.securesms.backup.v2.proto.Frame
import org.thoughtcrime.securesms.backup.v2.stream.BackupFrameEmitter
import org.thoughtcrime.securesms.database.SignalDatabase
import org.thoughtcrime.securesms.recipients.RecipientId

View File

@@ -7,6 +7,9 @@ package org.thoughtcrime.securesms.backup.v2.processor
import androidx.core.content.contentValuesOf
import okio.ByteString.Companion.toByteString
import org.signal.archive.proto.ChatFolder
import org.signal.archive.proto.Frame
import org.signal.archive.stream.BackupFrameEmitter
import org.signal.core.util.Base64
import org.signal.core.util.SqlUtil
import org.signal.core.util.UuidUtil
@@ -15,16 +18,13 @@ import org.signal.core.util.logging.Log
import org.thoughtcrime.securesms.backup.v2.ExportState
import org.thoughtcrime.securesms.backup.v2.ImportSkips
import org.thoughtcrime.securesms.backup.v2.ImportState
import org.thoughtcrime.securesms.backup.v2.proto.ChatFolder
import org.thoughtcrime.securesms.backup.v2.proto.Frame
import org.thoughtcrime.securesms.backup.v2.stream.BackupFrameEmitter
import org.thoughtcrime.securesms.components.settings.app.chats.folders.ChatFolderRecord
import org.thoughtcrime.securesms.database.ChatFolderTables.ChatFolderMembershipTable
import org.thoughtcrime.securesms.database.ChatFolderTables.ChatFolderTable
import org.thoughtcrime.securesms.database.ChatFolderTables.MembershipType
import org.thoughtcrime.securesms.database.SignalDatabase
import org.thoughtcrime.securesms.storage.StorageSyncHelper
import org.thoughtcrime.securesms.backup.v2.proto.ChatFolder as ChatFolderProto
import org.signal.archive.proto.ChatFolder as ChatFolderProto
/**
* Handles exporting and importing [ChatFolderRecord]s.

View File

@@ -5,15 +5,15 @@
package org.thoughtcrime.securesms.backup.v2.processor
import org.signal.archive.proto.ChatItem
import org.signal.archive.proto.Frame
import org.signal.archive.stream.BackupFrameEmitter
import org.signal.core.util.logging.Log
import org.thoughtcrime.securesms.backup.v2.ExportState
import org.thoughtcrime.securesms.backup.v2.ImportState
import org.thoughtcrime.securesms.backup.v2.database.createChatItemInserter
import org.thoughtcrime.securesms.backup.v2.database.getMessagesForBackup
import org.thoughtcrime.securesms.backup.v2.importer.ChatItemArchiveImporter
import org.thoughtcrime.securesms.backup.v2.proto.ChatItem
import org.thoughtcrime.securesms.backup.v2.proto.Frame
import org.thoughtcrime.securesms.backup.v2.stream.BackupFrameEmitter
import org.thoughtcrime.securesms.database.SignalDatabase
import org.thoughtcrime.securesms.recipients.RecipientId

View File

@@ -6,6 +6,8 @@
package org.thoughtcrime.securesms.backup.v2.processor
import okio.ByteString.Companion.toByteString
import org.signal.archive.proto.Frame
import org.signal.archive.stream.BackupFrameEmitter
import org.signal.core.util.Base64
import org.signal.core.util.UuidUtil
import org.signal.core.util.insertInto
@@ -14,8 +16,6 @@ import org.signal.core.util.toInt
import org.thoughtcrime.securesms.backup.v2.ExportState
import org.thoughtcrime.securesms.backup.v2.ImportSkips
import org.thoughtcrime.securesms.backup.v2.ImportState
import org.thoughtcrime.securesms.backup.v2.proto.Frame
import org.thoughtcrime.securesms.backup.v2.stream.BackupFrameEmitter
import org.thoughtcrime.securesms.conversation.colors.AvatarColor
import org.thoughtcrime.securesms.database.NotificationProfileTables.NotificationProfileAllowedMembersTable
import org.thoughtcrime.securesms.database.NotificationProfileTables.NotificationProfileScheduleTable
@@ -26,7 +26,7 @@ import org.thoughtcrime.securesms.notifications.profiles.NotificationProfile
import org.thoughtcrime.securesms.recipients.RecipientId
import org.thoughtcrime.securesms.storage.StorageSyncHelper
import java.time.DayOfWeek
import org.thoughtcrime.securesms.backup.v2.proto.NotificationProfile as NotificationProfileProto
import org.signal.archive.proto.NotificationProfile as NotificationProfileProto
/**
* Handles exporting and importing [NotificationProfile] models.

View File

@@ -5,6 +5,9 @@
package org.thoughtcrime.securesms.backup.v2.processor
import org.signal.archive.proto.Frame
import org.signal.archive.proto.ReleaseNotes
import org.signal.archive.stream.BackupFrameEmitter
import org.signal.core.models.ServiceId
import org.signal.core.util.logging.Log
import org.signal.core.util.update
@@ -22,9 +25,6 @@ import org.thoughtcrime.securesms.backup.v2.importer.CallLinkArchiveImporter
import org.thoughtcrime.securesms.backup.v2.importer.ContactArchiveImporter
import org.thoughtcrime.securesms.backup.v2.importer.DistributionListArchiveImporter
import org.thoughtcrime.securesms.backup.v2.importer.GroupArchiveImporter
import org.thoughtcrime.securesms.backup.v2.proto.Frame
import org.thoughtcrime.securesms.backup.v2.proto.ReleaseNotes
import org.thoughtcrime.securesms.backup.v2.stream.BackupFrameEmitter
import org.thoughtcrime.securesms.backup.v2.util.toLocal
import org.thoughtcrime.securesms.database.RecipientTable
import org.thoughtcrime.securesms.database.SignalDatabase
@@ -110,16 +110,21 @@ object RecipientArchiveProcessor {
}
fun import(recipient: ArchiveRecipient, importState: ImportState) {
val recipientContact = recipient.contact
val recipientGroup = recipient.group
val recipientDistributionList = recipient.distributionList
val recipientCallLink = recipient.callLink
val recipientSelf = recipient.self
val newId: RecipientId? = when {
recipient.contact != null -> ContactArchiveImporter.import(recipient.contact)
recipient.group != null -> GroupArchiveImporter.import(recipient.group)
recipient.distributionList != null -> DistributionListArchiveImporter.import(recipient.distributionList, importState)
recipientContact != null -> ContactArchiveImporter.import(recipientContact)
recipientGroup != null -> GroupArchiveImporter.import(recipientGroup)
recipientDistributionList != null -> DistributionListArchiveImporter.import(recipientDistributionList, importState)
recipient.releaseNotes != null -> SignalDatabase.recipients.restoreReleaseNotes()
recipient.callLink != null -> CallLinkArchiveImporter.import(recipient.callLink)
recipient.self != null -> {
recipientCallLink != null -> CallLinkArchiveImporter.import(recipientCallLink)
recipientSelf != null -> {
SignalDatabase.writableDatabase
.update(RecipientTable.TABLE_NAME)
.values(RecipientTable.AVATAR_COLOR to recipient.self.avatarColor?.toLocal()?.serialize())
.values(RecipientTable.AVATAR_COLOR to recipientSelf.avatarColor?.toLocal()?.serialize())
.where("${RecipientTable.ID} = ?", Recipient.self().id)
.run()
Recipient.self().id

View File

@@ -6,13 +6,13 @@
package org.thoughtcrime.securesms.backup.v2.processor
import okio.ByteString.Companion.toByteString
import org.signal.archive.proto.Frame
import org.signal.archive.proto.StickerPack
import org.signal.archive.stream.BackupFrameEmitter
import org.signal.core.util.Hex
import org.signal.core.util.insertInto
import org.signal.core.util.logging.Log
import org.thoughtcrime.securesms.backup.v2.ExportSkips
import org.thoughtcrime.securesms.backup.v2.proto.Frame
import org.thoughtcrime.securesms.backup.v2.proto.StickerPack
import org.thoughtcrime.securesms.backup.v2.stream.BackupFrameEmitter
import org.thoughtcrime.securesms.database.SQLiteDatabase
import org.thoughtcrime.securesms.database.SignalDatabase
import org.thoughtcrime.securesms.database.StickerTable

View File

@@ -7,6 +7,7 @@ package org.thoughtcrime.securesms.backup.v2.util
import okio.ByteString
import okio.ByteString.Companion.toByteString
import org.signal.archive.proto.FilePointer
import org.signal.core.util.Base64
import org.signal.core.util.UuidUtil
import org.signal.core.util.isNotNullOrBlank
@@ -22,7 +23,6 @@ import org.thoughtcrime.securesms.attachments.PointerAttachment
import org.thoughtcrime.securesms.attachments.TombstoneAttachment
import org.thoughtcrime.securesms.backup.v2.BackupMode
import org.thoughtcrime.securesms.backup.v2.ExportState
import org.thoughtcrime.securesms.backup.v2.proto.FilePointer
import org.thoughtcrime.securesms.conversation.colors.AvatarColor
import org.thoughtcrime.securesms.conversation.colors.ChatColors
import org.thoughtcrime.securesms.database.AttachmentTable
@@ -30,7 +30,7 @@ import org.thoughtcrime.securesms.stickers.StickerLocator
import org.whispersystems.signalservice.api.messages.SignalServiceAttachmentPointer
import org.whispersystems.signalservice.api.messages.SignalServiceAttachmentRemoteId
import java.util.Optional
import org.thoughtcrime.securesms.backup.v2.proto.AvatarColor as RemoteAvatarColor
import org.signal.archive.proto.AvatarColor as RemoteAvatarColor
/**
* Converts a [FilePointer] to a local [Attachment] object for inserting into the database.
@@ -47,11 +47,12 @@ fun FilePointer?.toLocalAttachment(
quote: Boolean = false,
quoteTargetContentType: String? = null
): Attachment? {
if (this == null || this.locatorInfo == null) return null
if (this == null) return null
val locatorInfo = this.locatorInfo ?: return null
val attachmentType = when {
this.locatorInfo.plaintextHash != null -> AttachmentType.ARCHIVE
this.locatorInfo.encryptedDigest != null && this.locatorInfo.transitCdnKey != null -> AttachmentType.TRANSIT
locatorInfo.plaintextHash != null -> AttachmentType.ARCHIVE
locatorInfo.encryptedDigest != null && locatorInfo.transitCdnKey != null -> AttachmentType.TRANSIT
else -> AttachmentType.INVALID
}
@@ -59,13 +60,13 @@ fun FilePointer?.toLocalAttachment(
AttachmentType.ARCHIVE -> {
ArchivedAttachment(
contentType = contentType,
size = this.locatorInfo.size.toLong(),
cdn = this.locatorInfo.transitCdnNumber ?: Cdn.CDN_0.cdnNumber,
uploadTimestamp = this.locatorInfo.transitTierUploadTimestamp ?: 0,
key = this.locatorInfo.key.toByteArray(),
cdnKey = this.locatorInfo.transitCdnKey?.nullIfBlank(),
archiveCdn = this.locatorInfo.mediaTierCdnNumber,
plaintextHash = this.locatorInfo.plaintextHash!!.toByteArray(),
size = locatorInfo.size.toLong(),
cdn = locatorInfo.transitCdnNumber ?: Cdn.CDN_0.cdnNumber,
uploadTimestamp = locatorInfo.transitTierUploadTimestamp ?: 0,
key = locatorInfo.key.toByteArray(),
cdnKey = locatorInfo.transitCdnKey?.nullIfBlank(),
archiveCdn = locatorInfo.mediaTierCdnNumber,
plaintextHash = locatorInfo.plaintextHash!!.toByteArray(),
incrementalMac = this.incrementalMac?.toByteArray(),
incrementalMacChunkSize = this.incrementalMacChunkSize,
width = this.width,
@@ -80,20 +81,20 @@ fun FilePointer?.toLocalAttachment(
quoteTargetContentType = quoteTargetContentType,
uuid = UuidUtil.fromByteStringOrNull(uuid),
fileName = fileName,
localBackupKey = this.locatorInfo.localKey?.toByteArray()
localBackupKey = locatorInfo.localKey?.toByteArray()
)
}
AttachmentType.TRANSIT -> {
val signalAttachmentPointer = SignalServiceAttachmentPointer(
cdnNumber = this.locatorInfo.transitCdnNumber ?: Cdn.CDN_0.cdnNumber,
cdnNumber = locatorInfo.transitCdnNumber ?: Cdn.CDN_0.cdnNumber,
remoteId = SignalServiceAttachmentRemoteId.from(locatorInfo.transitCdnKey!!),
contentType = contentType,
key = this.locatorInfo.key.toByteArray(),
key = locatorInfo.key.toByteArray(),
size = Optional.ofNullable(locatorInfo.size),
preview = Optional.empty(),
width = this.width ?: 0,
height = this.height ?: 0,
digest = Optional.ofNullable(this.locatorInfo.encryptedDigest!!.toByteArray()),
digest = Optional.ofNullable(locatorInfo.encryptedDigest!!.toByteArray()),
incrementalDigest = Optional.ofNullable(this.incrementalMac?.toByteArray()),
incrementalMacChunkSize = this.incrementalMacChunkSize ?: 0,
fileName = Optional.ofNullable(fileName),
@@ -102,7 +103,7 @@ fun FilePointer?.toLocalAttachment(
isGif = gif,
caption = Optional.ofNullable(this.caption),
blurHash = Optional.ofNullable(this.blurHash),
uploadTimestamp = this.locatorInfo.transitTierUploadTimestamp?.clampToValidBackupRange() ?: 0,
uploadTimestamp = locatorInfo.transitTierUploadTimestamp?.clampToValidBackupRange() ?: 0,
uuid = UuidUtil.fromByteStringOrNull(uuid)
)
PointerAttachment.forPointer(

View File

@@ -5,12 +5,12 @@
package org.thoughtcrime.securesms.backup.v2.util
import org.signal.archive.proto.ChatStyle
import org.signal.archive.proto.FilePointer
import org.signal.core.util.logging.Log
import org.thoughtcrime.securesms.attachments.AttachmentId
import org.thoughtcrime.securesms.backup.v2.BackupMode
import org.thoughtcrime.securesms.backup.v2.ImportState
import org.thoughtcrime.securesms.backup.v2.proto.ChatStyle
import org.thoughtcrime.securesms.backup.v2.proto.FilePointer
import org.thoughtcrime.securesms.conversation.colors.ChatColors
import org.thoughtcrime.securesms.conversation.colors.ChatColorsPalette
import org.thoughtcrime.securesms.database.SignalDatabase
@@ -122,6 +122,7 @@ fun ChatStyle.toLocal(importState: ImportState): ChatColors? {
ChatStyle.BubbleColorPreset.GRADIENT_SEA -> ChatColorsPalette.Bubbles.SEA
ChatStyle.BubbleColorPreset.GRADIENT_TANGERINE -> ChatColorsPalette.Bubbles.TANGERINE
ChatStyle.BubbleColorPreset.UNKNOWN_BUBBLE_COLOR_PRESET, ChatStyle.BubbleColorPreset.SOLID_ULTRAMARINE -> ChatColorsPalette.Bubbles.ULTRAMARINE
else -> ChatColorsPalette.Bubbles.ULTRAMARINE
}
}
@@ -198,8 +199,9 @@ fun ChatStyle.WallpaperPreset.toLocal(): ChatWallpaper? {
}
fun ChatStyle.parseChatWallpaper(wallpaperAttachmentId: AttachmentId?): ChatWallpaper? {
val chatWallpaper = if (this.wallpaperPreset != null) {
this.wallpaperPreset.toLocal()
val localWallpaperPreset = this.wallpaperPreset
val chatWallpaper = if (localWallpaperPreset != null) {
localWallpaperPreset.toLocal()
} else if (wallpaperAttachmentId != null) {
UriChatWallpaper(PartAuthority.getAttachmentDataUri(wallpaperAttachmentId), 0f)
} else {

View File

@@ -6,20 +6,23 @@
package org.thoughtcrime.securesms.backup.v2.util
import okio.ByteString
import org.signal.archive.proto.AccountData
import org.signal.archive.proto.Chat
import org.signal.archive.proto.ChatItem
import org.signal.archive.proto.FilePointer
import org.signal.archive.proto.Frame
import org.signal.core.models.backup.MediaName
import org.thoughtcrime.securesms.attachments.Cdn
import org.thoughtcrime.securesms.backup.v2.proto.AccountData
import org.thoughtcrime.securesms.backup.v2.proto.Chat
import org.thoughtcrime.securesms.backup.v2.proto.ChatItem
import org.thoughtcrime.securesms.backup.v2.proto.FilePointer
import org.thoughtcrime.securesms.backup.v2.proto.Frame
fun Frame.getAllReferencedArchiveAttachmentInfos(): Set<ArchiveAttachmentInfo> {
val infos: MutableSet<ArchiveAttachmentInfo> = mutableSetOf()
val account = this.account
val chat = this.chat
val chatItem = this.chatItem
when {
this.account != null -> infos += this.account.getAllReferencedArchiveAttachmentInfos()
this.chat != null -> infos += this.chat.getAllReferencedArchiveAttachmentInfos()
this.chatItem != null -> infos += this.chatItem.getAllReferencedArchiveAttachmentInfos()
account != null -> infos += account.getAllReferencedArchiveAttachmentInfos()
chat != null -> infos += chat.getAllReferencedArchiveAttachmentInfos()
chatItem != null -> infos += chatItem.getAllReferencedArchiveAttachmentInfos()
}
return infos.toSet()
}
@@ -74,18 +77,15 @@ private fun ChatItem.getAllReferencedArchiveAttachmentInfos(): Set<ArchiveAttach
}
private fun FilePointer.toArchiveAttachmentInfo(forQuote: Boolean = false, isWallpaper: Boolean = false): ArchiveAttachmentInfo? {
if (this.locatorInfo?.key == null) {
return null
}
val locatorInfo = this.locatorInfo
if (this.locatorInfo.plaintextHash == null) {
return null
}
val key = locatorInfo?.key ?: return null
val plaintextHash = locatorInfo.plaintextHash ?: return null
return ArchiveAttachmentInfo(
plaintextHash = this.locatorInfo.plaintextHash,
remoteKey = this.locatorInfo.key,
cdn = this.locatorInfo.mediaTierCdnNumber ?: Cdn.CDN_0.cdnNumber,
plaintextHash = plaintextHash,
remoteKey = key,
cdn = locatorInfo.mediaTierCdnNumber ?: Cdn.CDN_0.cdnNumber,
contentType = this.contentType,
forQuote = forQuote,
isWallpaper = isWallpaper

View File

@@ -22,6 +22,8 @@ import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.update
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import org.signal.archive.stream.EncryptedBackupReader
import org.signal.archive.stream.EncryptedBackupReader.Companion.MAC_SIZE
import org.signal.core.models.ServiceId.ACI
import org.signal.core.models.backup.MessageBackupKey
import org.signal.core.util.Hex
@@ -42,8 +44,6 @@ import org.thoughtcrime.securesms.backup.v2.BackupRepository
import org.thoughtcrime.securesms.backup.v2.DebugBackupMetadata
import org.thoughtcrime.securesms.backup.v2.MessageBackupTier
import org.thoughtcrime.securesms.backup.v2.RemoteRestoreResult
import org.thoughtcrime.securesms.backup.v2.stream.EncryptedBackupReader
import org.thoughtcrime.securesms.backup.v2.stream.EncryptedBackupReader.Companion.MAC_SIZE
import org.thoughtcrime.securesms.database.AttachmentTable
import org.thoughtcrime.securesms.database.AttachmentTable.DebugAttachmentStats
import org.thoughtcrime.securesms.database.SignalDatabase

View File

@@ -25,6 +25,7 @@ import androidx.annotation.WorkerThread
import androidx.core.content.contentValuesOf
import com.bumptech.glide.Glide
import okio.ByteString.Companion.toByteString
import org.signal.archive.proto.BackupDebugInfo
import org.signal.blurhash.BlurHash
import org.signal.core.models.backup.MediaId
import org.signal.core.models.backup.MediaName
@@ -76,7 +77,6 @@ import org.thoughtcrime.securesms.attachments.WallpaperAttachment
import org.thoughtcrime.securesms.audio.AudioHash
import org.thoughtcrime.securesms.backup.v2.ArchivedMediaObject
import org.thoughtcrime.securesms.backup.v2.exporters.ChatItemArchiveExporter
import org.thoughtcrime.securesms.backup.v2.proto.BackupDebugInfo
import org.thoughtcrime.securesms.crypto.AttachmentSecret
import org.thoughtcrime.securesms.crypto.ClassicDecryptingPartInputStream
import org.thoughtcrime.securesms.crypto.ModernDecryptingPartInputStream

View File

@@ -8,12 +8,12 @@ package org.thoughtcrime.securesms.database.helpers.migration
import android.app.Application
import androidx.core.content.contentValuesOf
import okio.IOException
import org.signal.archive.proto.GroupInvitationRevokedUpdate
import org.signal.core.models.ServiceId
import org.signal.core.util.forEach
import org.signal.core.util.logging.Log
import org.signal.core.util.requireBlob
import org.signal.core.util.requireLong
import org.thoughtcrime.securesms.backup.v2.proto.GroupInvitationRevokedUpdate
import org.thoughtcrime.securesms.database.SQLiteDatabase
import org.thoughtcrime.securesms.database.model.databaseprotos.MessageExtras
@@ -50,8 +50,9 @@ object V258_FixGroupRevokedInviteeUpdate : SignalDatabaseMigration {
updates
.replaceAll { change ->
if (change.groupInvitationRevokedUpdate != null) {
val invitees = change.groupInvitationRevokedUpdate.invitees.toMutableList()
val revokedUpdate = change.groupInvitationRevokedUpdate
if (revokedUpdate != null) {
val invitees = revokedUpdate.invitees.toMutableList()
invitees.replaceAll { invitee ->
val inviteeAciFieldServiceId = ServiceId.parseOrNull(invitee.inviteeAci)
@@ -69,7 +70,7 @@ object V258_FixGroupRevokedInviteeUpdate : SignalDatabaseMigration {
}
}
change.copy(groupInvitationRevokedUpdate = change.groupInvitationRevokedUpdate.copy(invitees = invitees))
change.copy(groupInvitationRevokedUpdate = revokedUpdate.copy(invitees = invitees))
} else {
change
}

View File

@@ -8,12 +8,12 @@ package org.thoughtcrime.securesms.database.helpers.migration
import android.app.Application
import androidx.core.content.contentValuesOf
import okio.IOException
import org.signal.archive.proto.GroupMemberAddedUpdate
import org.signal.core.models.ServiceId
import org.signal.core.util.forEach
import org.signal.core.util.logging.Log
import org.signal.core.util.requireBlob
import org.signal.core.util.requireLong
import org.thoughtcrime.securesms.backup.v2.proto.GroupMemberAddedUpdate
import org.thoughtcrime.securesms.database.SQLiteDatabase
import org.thoughtcrime.securesms.database.model.databaseprotos.MessageExtras
@@ -50,9 +50,10 @@ object V264_FixGroupAddMemberUpdate : SignalDatabaseMigration {
updates
.replaceAll { change ->
if (change.groupMemberAddedUpdate != null && ServiceId.parseOrNull(change.groupMemberAddedUpdate.updaterAci) is ServiceId.PNI) {
val addedUpdate = change.groupMemberAddedUpdate
if (addedUpdate != null && ServiceId.parseOrNull(addedUpdate.updaterAci) is ServiceId.PNI) {
dataMigrated = true
change.copy(groupMemberAddedUpdate = change.groupMemberAddedUpdate.copy(updaterAci = null))
change.copy(groupMemberAddedUpdate = addedUpdate.copy(updaterAci = null))
} else {
change
}

View File

@@ -8,12 +8,12 @@ package org.thoughtcrime.securesms.database.helpers.migration
import android.app.Application
import androidx.core.content.contentValuesOf
import okio.IOException
import org.signal.archive.proto.GroupInvitationDeclinedUpdate
import org.signal.core.models.ServiceId
import org.signal.core.util.forEach
import org.signal.core.util.logging.Log
import org.signal.core.util.requireBlob
import org.signal.core.util.requireLong
import org.thoughtcrime.securesms.backup.v2.proto.GroupInvitationDeclinedUpdate
import org.thoughtcrime.securesms.database.SQLiteDatabase
import org.thoughtcrime.securesms.database.model.databaseprotos.MessageExtras
@@ -49,8 +49,9 @@ object V267_FixGroupInvitationDeclinedUpdate : SignalDatabaseMigration {
updates
.replaceAll { change ->
if (change.groupInvitationDeclinedUpdate != null && ServiceId.parseOrNull(change.groupInvitationDeclinedUpdate.inviteeAci) is ServiceId.PNI) {
change.copy(groupInvitationDeclinedUpdate = change.groupInvitationDeclinedUpdate.copy(inviteeAci = null))
val declinedUpdate = change.groupInvitationDeclinedUpdate
if (declinedUpdate != null && ServiceId.parseOrNull(declinedUpdate.inviteeAci) is ServiceId.PNI) {
change.copy(groupInvitationDeclinedUpdate = declinedUpdate.copy(inviteeAci = null))
} else {
change
}

View File

@@ -5,7 +5,7 @@ import androidx.annotation.Nullable;
import org.signal.core.util.Base64;
import org.signal.core.util.logging.Log;
import org.thoughtcrime.securesms.backup.v2.proto.GroupCall;
import org.signal.archive.proto.GroupCall;
import org.thoughtcrime.securesms.database.model.databaseprotos.GroupCallUpdateDetails;
import org.thoughtcrime.securesms.recipients.Recipient;
import org.signal.core.models.ServiceId;

View File

@@ -6,6 +6,40 @@
package org.thoughtcrime.securesms.database.model
import okio.ByteString
import org.signal.archive.proto.GenericGroupUpdate
import org.signal.archive.proto.GroupAdminStatusUpdate
import org.signal.archive.proto.GroupAnnouncementOnlyChangeUpdate
import org.signal.archive.proto.GroupAttributesAccessLevelChangeUpdate
import org.signal.archive.proto.GroupAvatarUpdate
import org.signal.archive.proto.GroupChangeChatUpdate
import org.signal.archive.proto.GroupCreationUpdate
import org.signal.archive.proto.GroupDescriptionUpdate
import org.signal.archive.proto.GroupExpirationTimerUpdate
import org.signal.archive.proto.GroupInvitationAcceptedUpdate
import org.signal.archive.proto.GroupInvitationDeclinedUpdate
import org.signal.archive.proto.GroupInvitationRevokedUpdate
import org.signal.archive.proto.GroupInviteLinkAdminApprovalUpdate
import org.signal.archive.proto.GroupInviteLinkDisabledUpdate
import org.signal.archive.proto.GroupInviteLinkEnabledUpdate
import org.signal.archive.proto.GroupInviteLinkResetUpdate
import org.signal.archive.proto.GroupJoinRequestApprovalUpdate
import org.signal.archive.proto.GroupJoinRequestCanceledUpdate
import org.signal.archive.proto.GroupJoinRequestUpdate
import org.signal.archive.proto.GroupMemberAddedUpdate
import org.signal.archive.proto.GroupMemberJoinedByLinkUpdate
import org.signal.archive.proto.GroupMemberJoinedUpdate
import org.signal.archive.proto.GroupMemberLabelAccessLevelChangeUpdate
import org.signal.archive.proto.GroupMemberLeftUpdate
import org.signal.archive.proto.GroupMemberRemovedUpdate
import org.signal.archive.proto.GroupMembershipAccessLevelChangeUpdate
import org.signal.archive.proto.GroupNameUpdate
import org.signal.archive.proto.GroupSelfInvitationRevokedUpdate
import org.signal.archive.proto.GroupSequenceOfRequestsAndCancelsUpdate
import org.signal.archive.proto.GroupTerminateChangeUpdate
import org.signal.archive.proto.GroupUnknownInviteeUpdate
import org.signal.archive.proto.GroupV2AccessLevel
import org.signal.archive.proto.SelfInvitedOtherUserToGroupUpdate
import org.signal.archive.proto.SelfInvitedToGroupUpdate
import org.signal.core.models.ServiceId
import org.signal.core.util.BidiUtil
import org.signal.core.util.UuidUtil
@@ -18,40 +52,6 @@ import org.signal.storageservice.storage.protos.groups.local.DecryptedGroupChang
import org.signal.storageservice.storage.protos.groups.local.DecryptedPendingMember
import org.signal.storageservice.storage.protos.groups.local.DecryptedRequestingMember
import org.signal.storageservice.storage.protos.groups.local.EnabledState
import org.thoughtcrime.securesms.backup.v2.proto.GenericGroupUpdate
import org.thoughtcrime.securesms.backup.v2.proto.GroupAdminStatusUpdate
import org.thoughtcrime.securesms.backup.v2.proto.GroupAnnouncementOnlyChangeUpdate
import org.thoughtcrime.securesms.backup.v2.proto.GroupAttributesAccessLevelChangeUpdate
import org.thoughtcrime.securesms.backup.v2.proto.GroupAvatarUpdate
import org.thoughtcrime.securesms.backup.v2.proto.GroupChangeChatUpdate
import org.thoughtcrime.securesms.backup.v2.proto.GroupCreationUpdate
import org.thoughtcrime.securesms.backup.v2.proto.GroupDescriptionUpdate
import org.thoughtcrime.securesms.backup.v2.proto.GroupExpirationTimerUpdate
import org.thoughtcrime.securesms.backup.v2.proto.GroupInvitationAcceptedUpdate
import org.thoughtcrime.securesms.backup.v2.proto.GroupInvitationDeclinedUpdate
import org.thoughtcrime.securesms.backup.v2.proto.GroupInvitationRevokedUpdate
import org.thoughtcrime.securesms.backup.v2.proto.GroupInviteLinkAdminApprovalUpdate
import org.thoughtcrime.securesms.backup.v2.proto.GroupInviteLinkDisabledUpdate
import org.thoughtcrime.securesms.backup.v2.proto.GroupInviteLinkEnabledUpdate
import org.thoughtcrime.securesms.backup.v2.proto.GroupInviteLinkResetUpdate
import org.thoughtcrime.securesms.backup.v2.proto.GroupJoinRequestApprovalUpdate
import org.thoughtcrime.securesms.backup.v2.proto.GroupJoinRequestCanceledUpdate
import org.thoughtcrime.securesms.backup.v2.proto.GroupJoinRequestUpdate
import org.thoughtcrime.securesms.backup.v2.proto.GroupMemberAddedUpdate
import org.thoughtcrime.securesms.backup.v2.proto.GroupMemberJoinedByLinkUpdate
import org.thoughtcrime.securesms.backup.v2.proto.GroupMemberJoinedUpdate
import org.thoughtcrime.securesms.backup.v2.proto.GroupMemberLabelAccessLevelChangeUpdate
import org.thoughtcrime.securesms.backup.v2.proto.GroupMemberLeftUpdate
import org.thoughtcrime.securesms.backup.v2.proto.GroupMemberRemovedUpdate
import org.thoughtcrime.securesms.backup.v2.proto.GroupMembershipAccessLevelChangeUpdate
import org.thoughtcrime.securesms.backup.v2.proto.GroupNameUpdate
import org.thoughtcrime.securesms.backup.v2.proto.GroupSelfInvitationRevokedUpdate
import org.thoughtcrime.securesms.backup.v2.proto.GroupSequenceOfRequestsAndCancelsUpdate
import org.thoughtcrime.securesms.backup.v2.proto.GroupTerminateChangeUpdate
import org.thoughtcrime.securesms.backup.v2.proto.GroupUnknownInviteeUpdate
import org.thoughtcrime.securesms.backup.v2.proto.GroupV2AccessLevel
import org.thoughtcrime.securesms.backup.v2.proto.SelfInvitedOtherUserToGroupUpdate
import org.thoughtcrime.securesms.backup.v2.proto.SelfInvitedToGroupUpdate
import org.thoughtcrime.securesms.database.model.databaseprotos.DecryptedGroupV2Context
import org.whispersystems.signalservice.api.groupsv2.DecryptedGroupUtil
import org.whispersystems.signalservice.api.push.ServiceIds

View File

@@ -26,43 +26,43 @@ import org.signal.storageservice.storage.protos.groups.local.DecryptedPendingMem
import org.signal.storageservice.storage.protos.groups.local.DecryptedRequestingMember;
import org.signal.storageservice.storage.protos.groups.local.EnabledState;
import org.thoughtcrime.securesms.R;
import org.thoughtcrime.securesms.backup.v2.proto.GenericGroupUpdate;
import org.thoughtcrime.securesms.backup.v2.proto.GroupAdminStatusUpdate;
import org.thoughtcrime.securesms.backup.v2.proto.GroupAnnouncementOnlyChangeUpdate;
import org.thoughtcrime.securesms.backup.v2.proto.GroupAttributesAccessLevelChangeUpdate;
import org.thoughtcrime.securesms.backup.v2.proto.GroupAvatarUpdate;
import org.thoughtcrime.securesms.backup.v2.proto.GroupChangeChatUpdate;
import org.thoughtcrime.securesms.backup.v2.proto.GroupCreationUpdate;
import org.thoughtcrime.securesms.backup.v2.proto.GroupDescriptionUpdate;
import org.thoughtcrime.securesms.backup.v2.proto.GroupExpirationTimerUpdate;
import org.thoughtcrime.securesms.backup.v2.proto.GroupInvitationAcceptedUpdate;
import org.thoughtcrime.securesms.backup.v2.proto.GroupInvitationDeclinedUpdate;
import org.thoughtcrime.securesms.backup.v2.proto.GroupInvitationRevokedUpdate;
import org.thoughtcrime.securesms.backup.v2.proto.GroupInviteLinkAdminApprovalUpdate;
import org.thoughtcrime.securesms.backup.v2.proto.GroupInviteLinkDisabledUpdate;
import org.thoughtcrime.securesms.backup.v2.proto.GroupInviteLinkEnabledUpdate;
import org.thoughtcrime.securesms.backup.v2.proto.GroupInviteLinkResetUpdate;
import org.thoughtcrime.securesms.backup.v2.proto.GroupJoinRequestApprovalUpdate;
import org.thoughtcrime.securesms.backup.v2.proto.GroupJoinRequestCanceledUpdate;
import org.thoughtcrime.securesms.backup.v2.proto.GroupJoinRequestUpdate;
import org.thoughtcrime.securesms.backup.v2.proto.GroupMemberAddedUpdate;
import org.thoughtcrime.securesms.backup.v2.proto.GroupMemberJoinedByLinkUpdate;
import org.thoughtcrime.securesms.backup.v2.proto.GroupMemberJoinedUpdate;
import org.thoughtcrime.securesms.backup.v2.proto.GroupMemberLabelAccessLevelChangeUpdate;
import org.thoughtcrime.securesms.backup.v2.proto.GroupMemberLeftUpdate;
import org.thoughtcrime.securesms.backup.v2.proto.GroupMemberRemovedUpdate;
import org.thoughtcrime.securesms.backup.v2.proto.GroupMembershipAccessLevelChangeUpdate;
import org.thoughtcrime.securesms.backup.v2.proto.GroupNameUpdate;
import org.thoughtcrime.securesms.backup.v2.proto.GroupSelfInvitationRevokedUpdate;
import org.thoughtcrime.securesms.backup.v2.proto.GroupTerminateChangeUpdate;
import org.thoughtcrime.securesms.backup.v2.proto.GroupUnknownInviteeUpdate;
import org.thoughtcrime.securesms.backup.v2.proto.GroupV2AccessLevel;
import org.thoughtcrime.securesms.backup.v2.proto.GroupV2MigrationDroppedMembersUpdate;
import org.thoughtcrime.securesms.backup.v2.proto.GroupV2MigrationInvitedMembersUpdate;
import org.thoughtcrime.securesms.backup.v2.proto.GroupV2MigrationSelfInvitedUpdate;
import org.thoughtcrime.securesms.backup.v2.proto.GroupV2MigrationUpdate;
import org.thoughtcrime.securesms.backup.v2.proto.SelfInvitedOtherUserToGroupUpdate;
import org.thoughtcrime.securesms.backup.v2.proto.SelfInvitedToGroupUpdate;
import org.signal.archive.proto.GenericGroupUpdate;
import org.signal.archive.proto.GroupAdminStatusUpdate;
import org.signal.archive.proto.GroupAnnouncementOnlyChangeUpdate;
import org.signal.archive.proto.GroupAttributesAccessLevelChangeUpdate;
import org.signal.archive.proto.GroupAvatarUpdate;
import org.signal.archive.proto.GroupChangeChatUpdate;
import org.signal.archive.proto.GroupCreationUpdate;
import org.signal.archive.proto.GroupDescriptionUpdate;
import org.signal.archive.proto.GroupExpirationTimerUpdate;
import org.signal.archive.proto.GroupInvitationAcceptedUpdate;
import org.signal.archive.proto.GroupInvitationDeclinedUpdate;
import org.signal.archive.proto.GroupInvitationRevokedUpdate;
import org.signal.archive.proto.GroupInviteLinkAdminApprovalUpdate;
import org.signal.archive.proto.GroupInviteLinkDisabledUpdate;
import org.signal.archive.proto.GroupInviteLinkEnabledUpdate;
import org.signal.archive.proto.GroupInviteLinkResetUpdate;
import org.signal.archive.proto.GroupJoinRequestApprovalUpdate;
import org.signal.archive.proto.GroupJoinRequestCanceledUpdate;
import org.signal.archive.proto.GroupJoinRequestUpdate;
import org.signal.archive.proto.GroupMemberAddedUpdate;
import org.signal.archive.proto.GroupMemberJoinedByLinkUpdate;
import org.signal.archive.proto.GroupMemberJoinedUpdate;
import org.signal.archive.proto.GroupMemberLabelAccessLevelChangeUpdate;
import org.signal.archive.proto.GroupMemberLeftUpdate;
import org.signal.archive.proto.GroupMemberRemovedUpdate;
import org.signal.archive.proto.GroupMembershipAccessLevelChangeUpdate;
import org.signal.archive.proto.GroupNameUpdate;
import org.signal.archive.proto.GroupSelfInvitationRevokedUpdate;
import org.signal.archive.proto.GroupTerminateChangeUpdate;
import org.signal.archive.proto.GroupUnknownInviteeUpdate;
import org.signal.archive.proto.GroupV2AccessLevel;
import org.signal.archive.proto.GroupV2MigrationDroppedMembersUpdate;
import org.signal.archive.proto.GroupV2MigrationInvitedMembersUpdate;
import org.signal.archive.proto.GroupV2MigrationSelfInvitedUpdate;
import org.signal.archive.proto.GroupV2MigrationUpdate;
import org.signal.archive.proto.SelfInvitedOtherUserToGroupUpdate;
import org.signal.archive.proto.SelfInvitedToGroupUpdate;
import org.thoughtcrime.securesms.fonts.SignalSymbols.Glyph;
import org.thoughtcrime.securesms.groups.GV2AccessLevelUtil;
import org.thoughtcrime.securesms.recipients.Recipient;

View File

@@ -38,8 +38,8 @@ import org.signal.core.util.logging.Log;
import org.signal.storageservice.storage.protos.groups.local.DecryptedGroup;
import org.signal.storageservice.storage.protos.groups.local.DecryptedGroupChange;
import org.thoughtcrime.securesms.R;
import org.thoughtcrime.securesms.backup.v2.proto.GroupChangeChatUpdate;
import org.thoughtcrime.securesms.backup.v2.proto.GroupCreationUpdate;
import org.signal.archive.proto.GroupChangeChatUpdate;
import org.signal.archive.proto.GroupCreationUpdate;
import org.thoughtcrime.securesms.components.emoji.EmojiProvider;
import org.thoughtcrime.securesms.components.emoji.parsing.EmojiParser;
import org.thoughtcrime.securesms.components.transfercontrols.TransferControlView;

View File

@@ -10,7 +10,7 @@ import org.signal.storageservice.storage.protos.groups.local.DecryptedGroup;
import org.signal.storageservice.storage.protos.groups.local.DecryptedGroupChange;
import org.signal.storageservice.storage.protos.groups.local.DecryptedMember;
import org.signal.storageservice.storage.protos.groups.local.DecryptedPendingMember;
import org.thoughtcrime.securesms.backup.v2.proto.GroupChangeChatUpdate;
import org.signal.archive.proto.GroupChangeChatUpdate;
import org.thoughtcrime.securesms.database.model.GroupsV2UpdateMessageConverter;
import org.thoughtcrime.securesms.database.model.databaseprotos.DecryptedGroupV2Context;
import org.thoughtcrime.securesms.database.model.databaseprotos.GV2UpdateDescription;

View File

@@ -1,8 +1,8 @@
package org.thoughtcrime.securesms.keyvalue
import org.signal.archive.proto.BackupDebugInfo
import org.signal.ringrtc.CallManager.DataMode
import org.thoughtcrime.securesms.BuildConfig
import org.thoughtcrime.securesms.backup.v2.proto.BackupDebugInfo
import org.thoughtcrime.securesms.util.Environment.Calling.defaultSfuUrl
import org.thoughtcrime.securesms.util.RemoteConfig

View File

@@ -6,6 +6,10 @@
package org.thoughtcrime.securesms
import okio.ByteString
import org.signal.archive.proto.AccountData
import org.signal.archive.proto.BackupDebugInfo
import org.signal.archive.proto.FilePointer
import org.signal.archive.stream.EncryptedBackupReader
import org.signal.core.util.bytes
import org.signal.core.util.decodeOrNull
import org.signal.core.util.logging.Log
@@ -14,10 +18,6 @@ import org.signal.spinner.Plugin
import org.signal.spinner.PluginResult
import org.thoughtcrime.securesms.backup.v2.BackupRepository
import org.thoughtcrime.securesms.backup.v2.local.SnapshotFileSystem
import org.thoughtcrime.securesms.backup.v2.proto.AccountData
import org.thoughtcrime.securesms.backup.v2.proto.BackupDebugInfo
import org.thoughtcrime.securesms.backup.v2.proto.FilePointer
import org.thoughtcrime.securesms.backup.v2.stream.EncryptedBackupReader
import org.thoughtcrime.securesms.dependencies.AppDependencies
import org.thoughtcrime.securesms.keyvalue.SignalStore
import org.thoughtcrime.securesms.net.SignalNetwork
@@ -205,7 +205,7 @@ class BackupPlugin : Plugin {
}
frame.recipient != null -> {
totalRecipients++
val recipient = frame.recipient
val recipient = frame.recipient!!
recipientNames[recipient.id] = extractRecipientName(recipient)
when {
@@ -218,7 +218,7 @@ class BackupPlugin : Plugin {
}
}
frame.chat != null -> {
val chat = frame.chat
val chat = frame.chat!!
chatInfos[chat.id] = ChatInfo(
chatId = chat.id,
recipientId = chat.recipientId,
@@ -234,7 +234,7 @@ class BackupPlugin : Plugin {
}
}
frame.chatItem != null -> {
val item = frame.chatItem
val item = frame.chatItem!!
chatInfos[item.chatId]?.let { chatInfo ->
chatInfo.messageCount++
chatInfo.attachmentCount += item.standardMessage?.attachments?.size ?: 0
@@ -319,7 +319,7 @@ class BackupPlugin : Plugin {
counters.total++
// Check for plaintextHash
val hasPlaintextHash = filePointer.locatorInfo?.plaintextHash != null && filePointer.locatorInfo.plaintextHash.size > 0
val hasPlaintextHash = filePointer.locatorInfo?.plaintextHash != null && filePointer.locatorInfo?.plaintextHash!!.size > 0
if (hasPlaintextHash) {
counters.withPlaintextHash++
} else {
@@ -328,7 +328,7 @@ class BackupPlugin : Plugin {
}
// Check for localKey
val hasLocalKey = filePointer.locatorInfo?.localKey != null && filePointer.locatorInfo.localKey.size > 0
val hasLocalKey = filePointer.locatorInfo?.localKey != null && filePointer.locatorInfo?.localKey!!.size > 0
if (hasLocalKey) {
counters.withLocalKey++
} else {
@@ -527,12 +527,12 @@ class BackupPlugin : Plugin {
""".trimIndent()
}
private fun extractRecipientName(recipient: org.thoughtcrime.securesms.backup.v2.proto.Recipient?): String {
private fun extractRecipientName(recipient: org.signal.archive.proto.Recipient?): String {
if (recipient == null) return "Unknown"
return when {
recipient.contact != null -> {
val contact = recipient.contact
val contact = recipient.contact!!
val name = buildString {
if (contact.profileGivenName?.isNotEmpty() == true) {
append(contact.profileGivenName)
@@ -540,9 +540,9 @@ class BackupPlugin : Plugin {
append(" ${contact.profileFamilyName}")
}
} else if (contact.nickname?.given?.isNotEmpty() == true) {
append(contact.nickname.given)
if (contact.nickname.family.isNotEmpty()) {
append(" ${contact.nickname.family}")
append(contact.nickname!!.given)
if (contact.nickname!!.family.isNotEmpty()) {
append(" ${contact.nickname!!.family}")
}
} else if (contact.e164 != null) {
append("+${contact.e164}")
@@ -553,7 +553,7 @@ class BackupPlugin : Plugin {
name
}
recipient.group != null -> {
val group = recipient.group
val group = recipient.group!!
group.snapshot?.title?.title ?: "Group ${recipient.id}"
}
recipient.self != null -> "Self"

View File

@@ -18,14 +18,14 @@ import org.junit.rules.TemporaryFolder
import org.junit.runner.RunWith
import org.robolectric.RobolectricTestRunner
import org.robolectric.annotation.Config
import org.signal.archive.local.proto.Metadata
import org.signal.archive.proto.AccountData
import org.signal.archive.proto.BackupInfo
import org.signal.archive.proto.Frame
import org.signal.archive.stream.EncryptedBackupWriter
import org.signal.core.models.backup.BackupId
import org.signal.core.models.backup.MessageBackupKey
import org.signal.core.util.Util
import org.thoughtcrime.securesms.backup.v2.local.proto.Metadata
import org.thoughtcrime.securesms.backup.v2.proto.AccountData
import org.thoughtcrime.securesms.backup.v2.proto.BackupInfo
import org.thoughtcrime.securesms.backup.v2.proto.Frame
import org.thoughtcrime.securesms.backup.v2.stream.EncryptedBackupWriter
import java.io.ByteArrayOutputStream
import javax.crypto.Cipher
import javax.crypto.spec.IvParameterSpec

View File

@@ -7,6 +7,11 @@ package org.thoughtcrime.securesms.backup.v2.stream
import org.junit.Assert.assertEquals
import org.junit.Test
import org.signal.archive.proto.AccountData
import org.signal.archive.proto.BackupInfo
import org.signal.archive.proto.Frame
import org.signal.archive.stream.EncryptedBackupReader
import org.signal.archive.stream.EncryptedBackupWriter
import org.signal.core.models.ServiceId.ACI
import org.signal.core.models.backup.BackupId
import org.signal.core.models.backup.MessageBackupKey
@@ -14,9 +19,6 @@ import org.signal.core.util.Base64
import org.signal.core.util.Hex
import org.signal.core.util.Util
import org.signal.libsignal.messagebackup.BackupForwardSecrecyToken
import org.thoughtcrime.securesms.backup.v2.proto.AccountData
import org.thoughtcrime.securesms.backup.v2.proto.BackupInfo
import org.thoughtcrime.securesms.backup.v2.proto.Frame
import java.io.ByteArrayOutputStream
import java.util.UUID

View File

@@ -0,0 +1,27 @@
plugins {
id("signal-library")
id("com.squareup.wire")
}
android {
namespace = "org.signal.archive"
}
wire {
kotlin {
javaInterop = true
}
sourcePath {
srcDir("src/main/protowire")
}
}
dependencies {
implementation(project(":core:util"))
implementation(project(":core:models-jvm"))
implementation(project(":lib:libsignal-service"))
implementation(libs.libsignal.android)
implementation(libs.google.guava.android)
}

View File

@@ -0,0 +1,16 @@
/*
* Copyright 2026 Signal Messenger, LLC
* SPDX-License-Identifier: AGPL-3.0-only
*/
package org.signal.archive
/**
* Metadata about a local backup file available for restore.
*/
data class LocalBackupMetadata(
/** Size of the backup in bytes. */
val sizeBytes: Long,
/** Timestamp when the backup was created, in milliseconds since epoch. */
val timestampMs: Long
)

View File

@@ -0,0 +1,30 @@
/*
* Copyright 2026 Signal Messenger, LLC
* SPDX-License-Identifier: AGPL-3.0-only
*/
package org.signal.archive
/**
* Represents the progress of a local backup restore operation.
* Emitted as a flow from the storage controller during restore.
*/
sealed interface LocalBackupRestoreProgress {
/** The restore is being prepared (e.g., reading metadata, validating). */
data object Preparing : LocalBackupRestoreProgress
/** The restore is actively in progress. */
data class InProgress(
val bytesRead: Long,
val totalBytes: Long
) : LocalBackupRestoreProgress {
val progressFraction: Float
get() = if (totalBytes > 0) (bytesRead.toFloat() / totalBytes.toFloat()).coerceIn(0f, 1f) else 0f
}
/** The restore completed successfully. */
data object Complete : LocalBackupRestoreProgress
/** The restore failed with an error. */
data class Error(val cause: Throwable) : LocalBackupRestoreProgress
}

View File

@@ -3,11 +3,11 @@
* SPDX-License-Identifier: AGPL-3.0-only
*/
package org.thoughtcrime.securesms.backup.v2.local
package org.signal.archive.local
import org.signal.archive.local.proto.FilesFrame
import org.signal.core.util.readNBytesOrThrow
import org.signal.core.util.readVarInt32
import org.thoughtcrime.securesms.backup.v2.local.proto.FilesFrame
import java.io.EOFException
import java.io.InputStream

View File

@@ -3,10 +3,10 @@
* SPDX-License-Identifier: AGPL-3.0-only
*/
package org.thoughtcrime.securesms.backup.v2.local
package org.signal.archive.local
import org.signal.archive.local.proto.FilesFrame
import org.signal.core.util.writeVarInt32
import org.thoughtcrime.securesms.backup.v2.local.proto.FilesFrame
import java.io.IOException
import java.io.OutputStream

View File

@@ -3,10 +3,10 @@
* SPDX-License-Identifier: AGPL-3.0-only
*/
package org.thoughtcrime.securesms.backup.v2.stream
package org.signal.archive.stream
import org.thoughtcrime.securesms.backup.v2.proto.BackupInfo
import org.thoughtcrime.securesms.backup.v2.proto.Frame
import org.signal.archive.proto.BackupInfo
import org.signal.archive.proto.Frame
interface BackupExportWriter : AutoCloseable {
fun write(header: BackupInfo)

View File

@@ -3,9 +3,9 @@
* SPDX-License-Identifier: AGPL-3.0-only
*/
package org.thoughtcrime.securesms.backup.v2.stream
package org.signal.archive.stream
import org.thoughtcrime.securesms.backup.v2.proto.Frame
import org.signal.archive.proto.Frame
/**
* An interface that lets sub-processors emit [Frame]s as they export data.

View File

@@ -3,10 +3,10 @@
* SPDX-License-Identifier: AGPL-3.0-only
*/
package org.thoughtcrime.securesms.backup.v2.stream
package org.signal.archive.stream
import org.thoughtcrime.securesms.backup.v2.proto.BackupInfo
import org.thoughtcrime.securesms.backup.v2.proto.Frame
import org.signal.archive.proto.BackupInfo
import org.signal.archive.proto.Frame
interface BackupImportReader : Iterator<Frame>, AutoCloseable {
fun getHeader(): BackupInfo?

View File

@@ -3,9 +3,9 @@
* SPDX-License-Identifier: AGPL-3.0-only
*/
package org.thoughtcrime.securesms.backup.v2.stream
package org.signal.archive.stream
import org.thoughtcrime.securesms.backup.v2.proto.Frame
import org.signal.archive.proto.Frame
interface BackupImportStream {
fun read(): Frame?

View File

@@ -3,10 +3,12 @@
* SPDX-License-Identifier: AGPL-3.0-only
*/
package org.thoughtcrime.securesms.backup.v2.stream
package org.signal.archive.stream
import androidx.annotation.VisibleForTesting
import com.google.common.io.CountingInputStream
import org.signal.archive.proto.BackupInfo
import org.signal.archive.proto.Frame
import org.signal.core.models.ServiceId.ACI
import org.signal.core.models.backup.BackupId
import org.signal.core.models.backup.MessageBackupKey
@@ -17,8 +19,6 @@ import org.signal.core.util.stream.LimitedInputStream
import org.signal.core.util.stream.MacInputStream
import org.signal.core.util.writeVarInt32
import org.signal.libsignal.messagebackup.BackupForwardSecrecyToken
import org.thoughtcrime.securesms.backup.v2.proto.BackupInfo
import org.thoughtcrime.securesms.backup.v2.proto.Frame
import java.io.ByteArrayOutputStream
import java.io.EOFException
import java.io.IOException

View File

@@ -3,8 +3,11 @@
* SPDX-License-Identifier: AGPL-3.0-only
*/
package org.thoughtcrime.securesms.backup.v2.stream
package org.signal.archive.stream
import org.signal.archive.proto.BackupInfo
import org.signal.archive.proto.Frame
import org.signal.archive.stream.EncryptedBackupReader.Companion.createForSignalBackup
import org.signal.core.models.ServiceId.ACI
import org.signal.core.models.backup.BackupId
import org.signal.core.models.backup.MessageBackupKey
@@ -12,8 +15,6 @@ import org.signal.core.util.Util
import org.signal.core.util.stream.MacOutputStream
import org.signal.core.util.writeVarInt32
import org.signal.libsignal.messagebackup.BackupForwardSecrecyToken
import org.thoughtcrime.securesms.backup.v2.proto.BackupInfo
import org.thoughtcrime.securesms.backup.v2.proto.Frame
import java.io.IOException
import java.io.OutputStream
import javax.crypto.Cipher

View File

@@ -3,7 +3,7 @@
* SPDX-License-Identifier: AGPL-3.0-only
*/
package org.thoughtcrime.securesms.backup.v2.stream
package org.signal.archive.stream
import org.whispersystems.signalservice.internal.crypto.PaddingInputStream
import java.io.FilterOutputStream

View File

@@ -3,13 +3,13 @@
* SPDX-License-Identifier: AGPL-3.0-only
*/
package org.thoughtcrime.securesms.backup.v2.stream
package org.signal.archive.stream
import com.google.common.io.CountingInputStream
import org.signal.archive.proto.BackupInfo
import org.signal.archive.proto.Frame
import org.signal.core.util.readNBytesOrThrow
import org.signal.core.util.readVarInt32
import org.thoughtcrime.securesms.backup.v2.proto.BackupInfo
import org.thoughtcrime.securesms.backup.v2.proto.Frame
import java.io.EOFException
import java.io.InputStream

View File

@@ -3,11 +3,11 @@
* SPDX-License-Identifier: AGPL-3.0-only
*/
package org.thoughtcrime.securesms.backup.v2.stream
package org.signal.archive.stream
import org.signal.archive.proto.BackupInfo
import org.signal.archive.proto.Frame
import org.signal.core.util.writeVarInt32
import org.thoughtcrime.securesms.backup.v2.proto.BackupInfo
import org.thoughtcrime.securesms.backup.v2.proto.Frame
import java.io.IOException
import java.io.OutputStream

View File

@@ -2,7 +2,7 @@ syntax = "proto3";
package signal.backup;
option java_package = "org.thoughtcrime.securesms.backup.v2.proto";
option java_package = "org.signal.archive.proto";
option swift_prefix = "BackupProto_";
message BackupInfo {

View File

@@ -1,6 +1,6 @@
syntax = "proto3";
option java_package = "org.thoughtcrime.securesms.backup.v2.proto";
option java_package = "org.signal.archive.proto";
message BackupDebugInfo {
message AttachmentDetails {

View File

@@ -2,7 +2,7 @@ syntax = "proto3";
package signal.backup.local;
option java_package = "org.thoughtcrime.securesms.backup.v2.local.proto";
option java_package = "org.signal.archive.local.proto";
option swift_prefix = "LocalBackupProto_";
message Metadata {

View File

@@ -88,6 +88,7 @@ include(":lib:image-editor")
include(":lib:debuglogs-viewer")
include(":lib:blurhash")
include(":lib:apng")
include(":lib:archive")
// Feature modules
include(":feature:registration")