Update to latest Backup.proto.

This commit is contained in:
Greyson Parrelli
2024-07-29 10:26:19 -04:00
committed by mtang-signal
parent c2bdac80dc
commit fb2a332513
17 changed files with 211 additions and 227 deletions

View File

@@ -128,7 +128,7 @@ class ImportExportTest {
linkPreviews = true, linkPreviews = true,
notDiscoverableByPhoneNumber = true, notDiscoverableByPhoneNumber = true,
preferContactAvatars = true, preferContactAvatars = true,
universalExpireTimer = 42, universalExpireTimerSeconds = 42,
displayBadgesOnProfile = true, displayBadgesOnProfile = true,
keepMutedChatsArchived = true, keepMutedChatsArchived = true,
hasSetMyStoriesPrivacy = true, hasSetMyStoriesPrivacy = true,
@@ -556,46 +556,6 @@ class ImportExportTest {
) )
} }
@Test
fun deletedDistributionList() {
val alexa = Recipient(
id = 4,
contact = Contact(
aci = TestRecipientUtils.nextAci().toByteString(),
pni = TestRecipientUtils.nextPni().toByteString(),
username = "cool.01",
e164 = 141255501234,
blocked = true,
visibility = Contact.Visibility.HIDDEN,
registered = Contact.Registered(),
profileKey = TestRecipientUtils.generateProfileKey().toByteString(),
profileSharing = true,
profileGivenName = "Alexa",
profileFamilyName = "Kim",
hideStory = true
)
)
val importData = exportFrames(
*standardFrames,
alexa,
Recipient(
id = 6,
distributionList = DistributionListItem(
distributionId = DistributionId.create().asUuid().toByteArray().toByteString(),
deletionTimestamp = 12345L
)
)
)
import(importData)
val exported = BackupRepository.debugExport()
val expected = exportFrames(
*standardFrames,
alexa
)
compare(expected, exported)
}
@Test @Test
fun chatThreads() { fun chatThreads() {
importExport( importExport(
@@ -1254,12 +1214,7 @@ class ImportExportTest {
chatId = 1, chatId = 1,
authorId = alice.id, authorId = alice.id,
dateSent = dateSentStart++, dateSent = dateSentStart++,
incoming = ChatItem.IncomingMessageDetails( directionless = ChatItem.DirectionlessMessageDetails(),
dateReceived = dateSentStart,
dateServerSent = dateSentStart,
read = true,
sealedSender = true
),
updateMessage = ChatUpdateMessage( updateMessage = ChatUpdateMessage(
simpleUpdate = SimpleChatUpdate( simpleUpdate = SimpleChatUpdate(
type = SimpleChatUpdate.Type.fromValue(i)!! type = SimpleChatUpdate.Type.fromValue(i)!!
@@ -1287,12 +1242,7 @@ class ImportExportTest {
chatId = 1, chatId = 1,
authorId = alice.id, authorId = alice.id,
dateSent = dateSentStart++, dateSent = dateSentStart++,
incoming = ChatItem.IncomingMessageDetails( directionless = ChatItem.DirectionlessMessageDetails(),
dateReceived = dateSentStart,
dateServerSent = dateSentStart,
read = true,
sealedSender = true
),
updateMessage = ChatUpdateMessage( updateMessage = ChatUpdateMessage(
expirationTimerChange = ExpirationTimerChatUpdate( expirationTimerChange = ExpirationTimerChatUpdate(
1000 1000
@@ -1303,11 +1253,7 @@ class ImportExportTest {
chatId = 1, chatId = 1,
authorId = selfRecipient.id, authorId = selfRecipient.id,
dateSent = dateSentStart++, dateSent = dateSentStart++,
outgoing = ChatItem.OutgoingMessageDetails( directionless = ChatItem.DirectionlessMessageDetails(),
sendStatus = listOf(
SendStatus(alice.id, deliveryStatus = SendStatus.Status.READ, sealedSender = true, lastStatusUpdateTimestamp = -1)
)
),
updateMessage = ChatUpdateMessage( updateMessage = ChatUpdateMessage(
expirationTimerChange = ExpirationTimerChatUpdate( expirationTimerChange = ExpirationTimerChatUpdate(
0 0
@@ -1318,9 +1264,7 @@ class ImportExportTest {
chatId = 1, chatId = 1,
authorId = selfRecipient.id, authorId = selfRecipient.id,
dateSent = dateSentStart++, dateSent = dateSentStart++,
outgoing = ChatItem.OutgoingMessageDetails( directionless = ChatItem.DirectionlessMessageDetails(),
sendStatus = listOf(SendStatus(alice.id, deliveryStatus = SendStatus.Status.READ, sealedSender = true, lastStatusUpdateTimestamp = -1))
),
updateMessage = ChatUpdateMessage( updateMessage = ChatUpdateMessage(
expirationTimerChange = ExpirationTimerChatUpdate( expirationTimerChange = ExpirationTimerChatUpdate(
10000 10000
@@ -1331,12 +1275,7 @@ class ImportExportTest {
chatId = 1, chatId = 1,
authorId = alice.id, authorId = alice.id,
dateSent = dateSentStart++, dateSent = dateSentStart++,
incoming = ChatItem.IncomingMessageDetails( directionless = ChatItem.DirectionlessMessageDetails(),
dateReceived = dateSentStart,
dateServerSent = dateSentStart,
read = true,
sealedSender = true
),
updateMessage = ChatUpdateMessage( updateMessage = ChatUpdateMessage(
expirationTimerChange = ExpirationTimerChatUpdate( expirationTimerChange = ExpirationTimerChatUpdate(
0 0
@@ -1348,7 +1287,6 @@ class ImportExportTest {
@Test @Test
fun profileChangeChatUpdateMessage() { fun profileChangeChatUpdateMessage() {
var dateSentStart = 100L
importExport( importExport(
*standardFrames, *standardFrames,
alice, alice,
@@ -1356,13 +1294,8 @@ class ImportExportTest {
ChatItem( ChatItem(
chatId = 1, chatId = 1,
authorId = alice.id, authorId = alice.id,
dateSent = dateSentStart++, dateSent = 100L,
incoming = ChatItem.IncomingMessageDetails( directionless = ChatItem.DirectionlessMessageDetails(),
dateReceived = dateSentStart,
dateServerSent = dateSentStart,
read = true,
sealedSender = true
),
updateMessage = ChatUpdateMessage( updateMessage = ChatUpdateMessage(
profileChange = ProfileChangeChatUpdate( profileChange = ProfileChangeChatUpdate(
previousName = "Aliceee Kim", previousName = "Aliceee Kim",
@@ -1375,7 +1308,6 @@ class ImportExportTest {
@Test @Test
fun threadMergeChatUpdate() { fun threadMergeChatUpdate() {
var dateSentStart = 100L
importExport( importExport(
*standardFrames, *standardFrames,
alice, alice,
@@ -1383,13 +1315,8 @@ class ImportExportTest {
ChatItem( ChatItem(
chatId = 1, chatId = 1,
authorId = alice.id, authorId = alice.id,
dateSent = dateSentStart++, dateSent = 100L,
incoming = ChatItem.IncomingMessageDetails( directionless = ChatItem.DirectionlessMessageDetails(),
dateReceived = dateSentStart,
dateServerSent = dateSentStart,
read = true,
sealedSender = true
),
updateMessage = ChatUpdateMessage( updateMessage = ChatUpdateMessage(
threadMerge = ThreadMergeChatUpdate( threadMerge = ThreadMergeChatUpdate(
previousE164 = 141255501237 previousE164 = 141255501237
@@ -1409,13 +1336,8 @@ class ImportExportTest {
ChatItem( ChatItem(
chatId = 1, chatId = 1,
authorId = alice.id, authorId = alice.id,
dateSent = dateSentStart++, dateSent = dateSentStart,
incoming = ChatItem.IncomingMessageDetails( directionless = ChatItem.DirectionlessMessageDetails(),
dateReceived = dateSentStart,
dateServerSent = dateSentStart,
read = true,
sealedSender = true
),
updateMessage = ChatUpdateMessage( updateMessage = ChatUpdateMessage(
sessionSwitchover = SessionSwitchoverChatUpdate( sessionSwitchover = SessionSwitchoverChatUpdate(
e164 = 141255501237 e164 = 141255501237

View File

@@ -220,8 +220,8 @@ object BackupRepository {
backupTimeMs = exportState.backupTime backupTimeMs = exportState.backupTime
) )
) )
// Note: Without a transaction, we may export inconsistent state. But because we have a transaction,
// writes from other threads are blocked. This is something to think more about. // We're using a snapshot, so the transaction is more for perf than correctness
dbSnapshot.rawWritableDatabase.withinTransaction { dbSnapshot.rawWritableDatabase.withinTransaction {
AccountDataProcessor.export(dbSnapshot, signalStoreSnapshot) { AccountDataProcessor.export(dbSnapshot, signalStoreSnapshot) {
writer.write(it) writer.write(it)
@@ -316,29 +316,29 @@ object BackupRepository {
SignalDatabase.recipients.setProfileSharing(selfId, true) SignalDatabase.recipients.setProfileSharing(selfId, true)
eventTimer.emit("setup") eventTimer.emit("setup")
val backupState = BackupState(backupKey) val importState = ImportState(backupKey)
val chatItemInserter: ChatItemImportInserter = ChatItemBackupProcessor.beginImport(backupState) val chatItemInserter: ChatItemImportInserter = ChatItemBackupProcessor.beginImport(importState)
val totalLength = frameReader.getStreamLength() val totalLength = frameReader.getStreamLength()
for (frame in frameReader) { for (frame in frameReader) {
when { when {
frame.account != null -> { frame.account != null -> {
AccountDataProcessor.import(frame.account, selfId) AccountDataProcessor.import(frame.account, selfId, importState)
eventTimer.emit("account") eventTimer.emit("account")
} }
frame.recipient != null -> { frame.recipient != null -> {
RecipientBackupProcessor.import(frame.recipient, backupState) RecipientBackupProcessor.import(frame.recipient, importState)
eventTimer.emit("recipient") eventTimer.emit("recipient")
} }
frame.chat != null -> { frame.chat != null -> {
ChatBackupProcessor.import(frame.chat, backupState) ChatBackupProcessor.import(frame.chat, importState)
eventTimer.emit("chat") eventTimer.emit("chat")
} }
frame.adHocCall != null -> { frame.adHocCall != null -> {
AdHocCallBackupProcessor.import(frame.adHocCall, backupState) AdHocCallBackupProcessor.import(frame.adHocCall, importState)
eventTimer.emit("call") eventTimer.emit("call")
} }
@@ -362,7 +362,7 @@ object BackupRepository {
eventTimer.emit("chatItem") eventTimer.emit("chatItem")
} }
backupState.chatIdToLocalThreadId.values.forEach { importState.chatIdToLocalThreadId.values.forEach {
SignalDatabase.threads.update(it, unarchive = false, allowDeletion = false) SignalDatabase.threads.update(it, unarchive = false, allowDeletion = false)
} }
} }
@@ -947,15 +947,17 @@ data class ArchivedMediaObject(val mediaId: String, val cdn: Int)
data class BackupDirectories(val backupDir: String, val mediaDir: String) data class BackupDirectories(val backupDir: String, val mediaDir: String)
class ExportState(val backupTime: Long, val allowMediaBackup: Boolean) { class ExportState(val backupTime: Long, val allowMediaBackup: Boolean) {
val recipientIds = HashSet<Long>() val recipientIds: MutableSet<Long> = hashSetOf()
val threadIds = HashSet<Long>() val threadIds: MutableSet<Long> = hashSetOf()
val localToRemoteCustomChatColors: MutableMap<Long, Int> = hashMapOf()
} }
class BackupState(val backupKey: BackupKey) { class ImportState(val backupKey: BackupKey) {
val backupToLocalRecipientId = HashMap<Long, RecipientId>() val remoteToLocalRecipientId: MutableMap<Long, RecipientId> = hashMapOf()
val chatIdToLocalThreadId = HashMap<Long, Long>() val chatIdToLocalThreadId: MutableMap<Long, Long> = hashMapOf()
val chatIdToLocalRecipientId = HashMap<Long, RecipientId>() val chatIdToLocalRecipientId: MutableMap<Long, RecipientId> = hashMapOf()
val chatIdToBackupRecipientId = HashMap<Long, Long>() val chatIdToBackupRecipientId: MutableMap<Long, Long> = hashMapOf()
val remoteToLocalColorId: MutableMap<Long, Long> = hashMapOf()
} }
class BackupMetadata( class BackupMetadata(

View File

@@ -10,7 +10,7 @@ import android.database.sqlite.SQLiteDatabase
import androidx.core.content.contentValuesOf import androidx.core.content.contentValuesOf
import org.signal.core.util.requireLong import org.signal.core.util.requireLong
import org.signal.core.util.select import org.signal.core.util.select
import org.thoughtcrime.securesms.backup.v2.BackupState import org.thoughtcrime.securesms.backup.v2.ImportState
import org.thoughtcrime.securesms.backup.v2.proto.AdHocCall import org.thoughtcrime.securesms.backup.v2.proto.AdHocCall
import org.thoughtcrime.securesms.database.CallTable import org.thoughtcrime.securesms.database.CallTable
import org.thoughtcrime.securesms.database.RecipientTable import org.thoughtcrime.securesms.database.RecipientTable
@@ -26,7 +26,7 @@ fun CallTable.getAdhocCallsForBackup(): CallLogIterator {
) )
} }
fun CallTable.restoreCallLogFromBackup(call: AdHocCall, backupState: BackupState) { fun CallTable.restoreCallLogFromBackup(call: AdHocCall, importState: ImportState) {
val event = when (call.state) { val event = when (call.state) {
AdHocCall.State.GENERIC -> CallTable.Event.GENERIC_GROUP_CALL AdHocCall.State.GENERIC -> CallTable.Event.GENERIC_GROUP_CALL
AdHocCall.State.UNKNOWN_STATE -> CallTable.Event.GENERIC_GROUP_CALL AdHocCall.State.UNKNOWN_STATE -> CallTable.Event.GENERIC_GROUP_CALL
@@ -34,7 +34,7 @@ fun CallTable.restoreCallLogFromBackup(call: AdHocCall, backupState: BackupState
val values = contentValuesOf( val values = contentValuesOf(
CallTable.CALL_ID to call.callId, CallTable.CALL_ID to call.callId,
CallTable.PEER to backupState.backupToLocalRecipientId[call.recipientId]!!.serialize(), CallTable.PEER to importState.remoteToLocalRecipientId[call.recipientId]!!.serialize(),
CallTable.TYPE to CallTable.Type.serialize(CallTable.Type.AD_HOC_CALL), CallTable.TYPE to CallTable.Type.serialize(CallTable.Type.AD_HOC_CALL),
CallTable.DIRECTION to CallTable.Direction.serialize(CallTable.Direction.OUTGOING), CallTable.DIRECTION to CallTable.Direction.serialize(CallTable.Direction.OUTGOING),
CallTable.EVENT to CallTable.Event.serialize(event), CallTable.EVENT to CallTable.Event.serialize(event),

View File

@@ -183,7 +183,7 @@ class ChatItemExportIterator(private val cursor: Cursor, private val batchSize:
builder.updateMessage = simpleUpdate(SimpleChatUpdate.Type.REPORTED_SPAM) builder.updateMessage = simpleUpdate(SimpleChatUpdate.Type.REPORTED_SPAM)
} }
MessageTypes.isExpirationTimerUpdate(record.type) -> { MessageTypes.isExpirationTimerUpdate(record.type) -> {
builder.updateMessage = ChatUpdateMessage(expirationTimerChange = ExpirationTimerChatUpdate(record.expiresIn.toInt())) builder.updateMessage = ChatUpdateMessage(expirationTimerChange = ExpirationTimerChatUpdate(record.expiresIn))
builder.expiresInMs = 0 builder.expiresInMs = 0
} }
MessageTypes.isProfileChange(record.type) -> { MessageTypes.isProfileChange(record.type) -> {
@@ -774,7 +774,7 @@ class ChatItemExportIterator(private val cursor: Cursor, private val batchSize:
mediaName = archiveMediaName ?: this.getMediaName().toString(), mediaName = archiveMediaName ?: this.getMediaName().toString(),
cdnNumber = if (archiveMediaName != null) archiveCdn else Cdn.CDN_3.cdnNumber, // TODO (clark): Update when new proto with optional cdn is landed cdnNumber = if (archiveMediaName != null) archiveCdn else Cdn.CDN_3.cdnNumber, // TODO (clark): Update when new proto with optional cdn is landed
key = Base64.decode(remoteKey).toByteString(), key = Base64.decode(remoteKey).toByteString(),
size = this.size.toInt(), size = this.size,
digest = remoteDigest.toByteString() digest = remoteDigest.toByteString()
) )
} else { } else {

View File

@@ -20,7 +20,7 @@ import org.thoughtcrime.securesms.attachments.Attachment
import org.thoughtcrime.securesms.attachments.Cdn import org.thoughtcrime.securesms.attachments.Cdn
import org.thoughtcrime.securesms.attachments.PointerAttachment import org.thoughtcrime.securesms.attachments.PointerAttachment
import org.thoughtcrime.securesms.attachments.TombstoneAttachment import org.thoughtcrime.securesms.attachments.TombstoneAttachment
import org.thoughtcrime.securesms.backup.v2.BackupState import org.thoughtcrime.securesms.backup.v2.ImportState
import org.thoughtcrime.securesms.backup.v2.proto.BodyRange import org.thoughtcrime.securesms.backup.v2.proto.BodyRange
import org.thoughtcrime.securesms.backup.v2.proto.ChatItem import org.thoughtcrime.securesms.backup.v2.proto.ChatItem
import org.thoughtcrime.securesms.backup.v2.proto.ChatUpdateMessage import org.thoughtcrime.securesms.backup.v2.proto.ChatUpdateMessage
@@ -89,7 +89,7 @@ import org.thoughtcrime.securesms.backup.v2.proto.GiftBadge as BackupGiftBadge
*/ */
class ChatItemImportInserter( class ChatItemImportInserter(
private val db: SQLiteDatabase, private val db: SQLiteDatabase,
private val backupState: BackupState, private val importState: ImportState,
private val batchSize: Int private val batchSize: Int
) { ) {
companion object { companion object {
@@ -155,25 +155,25 @@ class ChatItemImportInserter(
* If this item causes the buffer to hit the batch size, then a batch of items will actually be inserted. * If this item causes the buffer to hit the batch size, then a batch of items will actually be inserted.
*/ */
fun insert(chatItem: ChatItem) { fun insert(chatItem: ChatItem) {
val fromLocalRecipientId: RecipientId? = backupState.backupToLocalRecipientId[chatItem.authorId] val fromLocalRecipientId: RecipientId? = importState.remoteToLocalRecipientId[chatItem.authorId]
if (fromLocalRecipientId == null) { if (fromLocalRecipientId == null) {
Log.w(TAG, "[insert] Could not find a local recipient for backup recipient ID ${chatItem.authorId}! Skipping.") Log.w(TAG, "[insert] Could not find a local recipient for backup recipient ID ${chatItem.authorId}! Skipping.")
return return
} }
val chatLocalRecipientId: RecipientId? = backupState.chatIdToLocalRecipientId[chatItem.chatId] val chatLocalRecipientId: RecipientId? = importState.chatIdToLocalRecipientId[chatItem.chatId]
if (chatLocalRecipientId == null) { if (chatLocalRecipientId == null) {
Log.w(TAG, "[insert] Could not find a local recipient for chatId ${chatItem.chatId}! Skipping.") Log.w(TAG, "[insert] Could not find a local recipient for chatId ${chatItem.chatId}! Skipping.")
return return
} }
val localThreadId: Long? = backupState.chatIdToLocalThreadId[chatItem.chatId] val localThreadId: Long? = importState.chatIdToLocalThreadId[chatItem.chatId]
if (localThreadId == null) { if (localThreadId == null) {
Log.w(TAG, "[insert] Could not find a local threadId for backup chatId ${chatItem.chatId}! Skipping.") Log.w(TAG, "[insert] Could not find a local threadId for backup chatId ${chatItem.chatId}! Skipping.")
return return
} }
val chatBackupRecipientId: Long? = backupState.chatIdToBackupRecipientId[chatItem.chatId] val chatBackupRecipientId: Long? = importState.chatIdToBackupRecipientId[chatItem.chatId]
if (chatBackupRecipientId == null) { if (chatBackupRecipientId == null) {
Log.w(TAG, "[insert] Could not find a backup recipientId for backup chatId ${chatItem.chatId}! Skipping.") Log.w(TAG, "[insert] Could not find a backup recipientId for backup chatId ${chatItem.chatId}! Skipping.")
return return
@@ -283,7 +283,7 @@ class ChatItemImportInserter(
CallTable.MESSAGE_ID to messageRowId, CallTable.MESSAGE_ID to messageRowId,
CallTable.PEER to chatRecipientId.serialize(), CallTable.PEER to chatRecipientId.serialize(),
CallTable.TYPE to CallTable.Type.serialize(CallTable.Type.GROUP_CALL), CallTable.TYPE to CallTable.Type.serialize(CallTable.Type.GROUP_CALL),
CallTable.DIRECTION to CallTable.Direction.serialize(if (backupState.backupToLocalRecipientId[updateMessage.groupCall.ringerRecipientId] == selfId) CallTable.Direction.OUTGOING else CallTable.Direction.INCOMING), CallTable.DIRECTION to CallTable.Direction.serialize(if (importState.remoteToLocalRecipientId[updateMessage.groupCall.ringerRecipientId] == selfId) CallTable.Direction.OUTGOING else CallTable.Direction.INCOMING),
CallTable.EVENT to CallTable.Event.serialize( CallTable.EVENT to CallTable.Event.serialize(
when (updateMessage.groupCall.state) { when (updateMessage.groupCall.state) {
GroupCall.State.ACCEPTED -> CallTable.Event.ACCEPTED GroupCall.State.ACCEPTED -> CallTable.Event.ACCEPTED
@@ -460,8 +460,8 @@ class ChatItemImportInserter(
contentValues.put(MessageTable.UNIDENTIFIED, this.outgoing.sendStatus.count { it.sealedSender }) contentValues.put(MessageTable.UNIDENTIFIED, this.outgoing.sendStatus.count { it.sealedSender })
contentValues.put(MessageTable.READ, 1) contentValues.put(MessageTable.READ, 1)
contentValues.addNetworkFailures(this, backupState) contentValues.addNetworkFailures(this, importState)
contentValues.addIdentityKeyMismatches(this, backupState) contentValues.addIdentityKeyMismatches(this, importState)
} else { } else {
contentValues.put(MessageTable.VIEWED_COLUMN, 0) contentValues.put(MessageTable.VIEWED_COLUMN, 0)
contentValues.put(MessageTable.HAS_READ_RECEIPT, 0) contentValues.put(MessageTable.HAS_READ_RECEIPT, 0)
@@ -529,7 +529,7 @@ class ChatItemImportInserter(
return reactions return reactions
.mapNotNull { .mapNotNull {
val authorId: Long? = backupState.backupToLocalRecipientId[it.authorId]?.toLong() val authorId: Long? = importState.remoteToLocalRecipientId[it.authorId]?.toLong()
if (authorId != null) { if (authorId != null) {
contentValuesOf( contentValuesOf(
@@ -557,7 +557,7 @@ class ChatItemImportInserter(
} }
return this.outgoing.sendStatus.mapNotNull { sendStatus -> return this.outgoing.sendStatus.mapNotNull { sendStatus ->
val recipientId = backupState.backupToLocalRecipientId[sendStatus.recipientId] val recipientId = importState.remoteToLocalRecipientId[sendStatus.recipientId]
if (recipientId != null) { if (recipientId != null) {
contentValuesOf( contentValuesOf(
@@ -674,7 +674,7 @@ class ChatItemImportInserter(
} }
updateMessage.groupCall != null -> { updateMessage.groupCall != null -> {
val startedCallRecipientId = if (updateMessage.groupCall.startedCallRecipientId != null) { val startedCallRecipientId = if (updateMessage.groupCall.startedCallRecipientId != null) {
backupState.backupToLocalRecipientId[updateMessage.groupCall.startedCallRecipientId] importState.remoteToLocalRecipientId[updateMessage.groupCall.startedCallRecipientId]
} else { } else {
null null
} }
@@ -815,7 +815,7 @@ class ChatItemImportInserter(
private fun ContentValues.addQuote(quote: Quote) { private fun ContentValues.addQuote(quote: Quote) {
this.put(MessageTable.QUOTE_ID, quote.targetSentTimestamp ?: MessageTable.QUOTE_TARGET_MISSING_ID) this.put(MessageTable.QUOTE_ID, quote.targetSentTimestamp ?: MessageTable.QUOTE_TARGET_MISSING_ID)
this.put(MessageTable.QUOTE_AUTHOR, backupState.backupToLocalRecipientId[quote.authorId]!!.serialize()) this.put(MessageTable.QUOTE_AUTHOR, importState.remoteToLocalRecipientId[quote.authorId]!!.serialize())
this.put(MessageTable.QUOTE_BODY, quote.text) this.put(MessageTable.QUOTE_BODY, quote.text)
this.put(MessageTable.QUOTE_TYPE, quote.type.toLocalQuoteType()) this.put(MessageTable.QUOTE_TYPE, quote.type.toLocalQuoteType())
this.put(MessageTable.QUOTE_BODY_RANGES, quote.bodyRanges.toLocalBodyRanges()?.encode()) this.put(MessageTable.QUOTE_BODY_RANGES, quote.bodyRanges.toLocalBodyRanges()?.encode())
@@ -840,14 +840,14 @@ class ChatItemImportInserter(
} }
} }
private fun ContentValues.addNetworkFailures(chatItem: ChatItem, backupState: BackupState) { private fun ContentValues.addNetworkFailures(chatItem: ChatItem, importState: ImportState) {
if (chatItem.outgoing == null) { if (chatItem.outgoing == null) {
return return
} }
val networkFailures = chatItem.outgoing.sendStatus val networkFailures = chatItem.outgoing.sendStatus
.filter { status -> status.networkFailure } .filter { status -> status.networkFailure }
.mapNotNull { status -> backupState.backupToLocalRecipientId[status.recipientId] } .mapNotNull { status -> importState.remoteToLocalRecipientId[status.recipientId] }
.map { recipientId -> NetworkFailure(recipientId) } .map { recipientId -> NetworkFailure(recipientId) }
.toSet() .toSet()
@@ -856,14 +856,14 @@ class ChatItemImportInserter(
} }
} }
private fun ContentValues.addIdentityKeyMismatches(chatItem: ChatItem, backupState: BackupState) { private fun ContentValues.addIdentityKeyMismatches(chatItem: ChatItem, importState: ImportState) {
if (chatItem.outgoing == null) { if (chatItem.outgoing == null) {
return return
} }
val mismatches = chatItem.outgoing.sendStatus val mismatches = chatItem.outgoing.sendStatus
.filter { status -> status.identityKeyMismatch } .filter { status -> status.identityKeyMismatch }
.mapNotNull { status -> backupState.backupToLocalRecipientId[status.recipientId] } .mapNotNull { status -> importState.remoteToLocalRecipientId[status.recipientId] }
.map { recipientId -> IdentityKeyMismatch(recipientId, null) } // TODO We probably want the actual identity key in this status situation? .map { recipientId -> IdentityKeyMismatch(recipientId, null) } // TODO We probably want the actual identity key in this status situation?
.toSet() .toSet()
@@ -965,8 +965,8 @@ class ChatItemImportInserter(
cdnKey = backupLocator.transitCdnKey, cdnKey = backupLocator.transitCdnKey,
archiveCdn = backupLocator.cdnNumber, archiveCdn = backupLocator.cdnNumber,
archiveMediaName = backupLocator.mediaName, archiveMediaName = backupLocator.mediaName,
archiveMediaId = backupState.backupKey.deriveMediaId(MediaName(backupLocator.mediaName)).encode(), archiveMediaId = importState.backupKey.deriveMediaId(MediaName(backupLocator.mediaName)).encode(),
archiveThumbnailMediaId = backupState.backupKey.deriveMediaId(MediaName.forThumbnailFromMediaName(backupLocator.mediaName)).encode(), archiveThumbnailMediaId = importState.backupKey.deriveMediaId(MediaName.forThumbnailFromMediaName(backupLocator.mediaName)).encode(),
digest = backupLocator.digest.toByteArray(), digest = backupLocator.digest.toByteArray(),
incrementalMac = incrementalMac?.toByteArray(), incrementalMac = incrementalMac?.toByteArray(),
incrementalMacChunkSize = incrementalMacChunkSize, incrementalMacChunkSize = incrementalMacChunkSize,

View File

@@ -15,7 +15,7 @@ import org.signal.core.util.requireNonNullString
import org.signal.core.util.requireObject import org.signal.core.util.requireObject
import org.signal.core.util.select import org.signal.core.util.select
import org.signal.core.util.withinTransaction import org.signal.core.util.withinTransaction
import org.thoughtcrime.securesms.backup.v2.BackupState import org.thoughtcrime.securesms.backup.v2.ImportState
import org.thoughtcrime.securesms.backup.v2.proto.DistributionList import org.thoughtcrime.securesms.backup.v2.proto.DistributionList
import org.thoughtcrime.securesms.backup.v2.proto.DistributionListItem import org.thoughtcrime.securesms.backup.v2.proto.DistributionListItem
import org.thoughtcrime.securesms.database.DistributionListTables import org.thoughtcrime.securesms.database.DistributionListTables
@@ -98,7 +98,7 @@ fun DistributionListTables.getMembersForBackup(id: DistributionListId): List<Rec
} }
} }
fun DistributionListTables.restoreFromBackup(dlistItem: DistributionListItem, backupState: BackupState): RecipientId? { fun DistributionListTables.restoreFromBackup(dlistItem: DistributionListItem, importState: ImportState): RecipientId? {
if (dlistItem.deletionTimestamp != null && dlistItem.deletionTimestamp > 0) { if (dlistItem.deletionTimestamp != null && dlistItem.deletionTimestamp > 0) {
val dlistId = createList( val dlistId = createList(
name = "", name = "",
@@ -115,7 +115,7 @@ fun DistributionListTables.restoreFromBackup(dlistItem: DistributionListItem, ba
val dlist = dlistItem.distributionList ?: return null val dlist = dlistItem.distributionList ?: return null
val members: List<RecipientId> = dlist.memberRecipientIds val members: List<RecipientId> = dlist.memberRecipientIds
.mapNotNull { backupState.backupToLocalRecipientId[it] } .mapNotNull { importState.remoteToLocalRecipientId[it] }
if (members.size != dlist.memberRecipientIds.size) { if (members.size != dlist.memberRecipientIds.size) {
Log.w(TAG, "Couldn't find some member recipients! Missing backup recipientIds: ${dlist.memberRecipientIds.toSet() - members.toSet()}") Log.w(TAG, "Couldn't find some member recipients! Missing backup recipientIds: ${dlist.memberRecipientIds.toSet() - members.toSet()}")

View File

@@ -8,7 +8,7 @@ package org.thoughtcrime.securesms.backup.v2.database
import org.signal.core.util.SqlUtil import org.signal.core.util.SqlUtil
import org.signal.core.util.logging.Log import org.signal.core.util.logging.Log
import org.signal.core.util.select import org.signal.core.util.select
import org.thoughtcrime.securesms.backup.v2.BackupState import org.thoughtcrime.securesms.backup.v2.ImportState
import org.thoughtcrime.securesms.database.MessageTable import org.thoughtcrime.securesms.database.MessageTable
import org.thoughtcrime.securesms.database.MessageTypes import org.thoughtcrime.securesms.database.MessageTypes
import java.util.concurrent.TimeUnit import java.util.concurrent.TimeUnit
@@ -69,8 +69,8 @@ fun MessageTable.getMessagesForBackup(backupTime: Long, archiveMedia: Boolean):
return ChatItemExportIterator(cursor, 100, archiveMedia) return ChatItemExportIterator(cursor, 100, archiveMedia)
} }
fun MessageTable.createChatItemInserter(backupState: BackupState): ChatItemImportInserter { fun MessageTable.createChatItemInserter(importState: ImportState): ChatItemImportInserter {
return ChatItemImportInserter(writableDatabase, backupState, 100) return ChatItemImportInserter(writableDatabase, importState, 100)
} }
fun MessageTable.clearAllDataForBackupRestore() { fun MessageTable.clearAllDataForBackupRestore() {

View File

@@ -16,12 +16,12 @@ import org.signal.core.util.requireBoolean
import org.signal.core.util.requireInt import org.signal.core.util.requireInt
import org.signal.core.util.requireLong import org.signal.core.util.requireLong
import org.signal.core.util.toInt import org.signal.core.util.toInt
import org.thoughtcrime.securesms.backup.v2.ImportState
import org.thoughtcrime.securesms.backup.v2.proto.Chat import org.thoughtcrime.securesms.backup.v2.proto.Chat
import org.thoughtcrime.securesms.backup.v2.proto.ChatStyle import org.thoughtcrime.securesms.backup.v2.proto.ChatStyle
import org.thoughtcrime.securesms.conversation.colors.ChatColors import org.thoughtcrime.securesms.conversation.colors.ChatColors
import org.thoughtcrime.securesms.conversation.colors.ChatColorsPalette import org.thoughtcrime.securesms.conversation.colors.ChatColorsPalette
import org.thoughtcrime.securesms.database.RecipientTable import org.thoughtcrime.securesms.database.RecipientTable
import org.thoughtcrime.securesms.database.SignalDatabase
import org.thoughtcrime.securesms.database.ThreadTable import org.thoughtcrime.securesms.database.ThreadTable
import org.thoughtcrime.securesms.database.model.databaseprotos.ChatColor import org.thoughtcrime.securesms.database.model.databaseprotos.ChatColor
import org.thoughtcrime.securesms.recipients.RecipientId import org.thoughtcrime.securesms.recipients.RecipientId
@@ -29,7 +29,7 @@ import java.io.Closeable
private val TAG = Log.tag(ThreadTable::class.java) private val TAG = Log.tag(ThreadTable::class.java)
fun ThreadTable.getThreadsForBackup(): ChatIterator { fun ThreadTable.getThreadsForBackup(): ChatExportIterator {
//language=sql //language=sql
val query = """ val query = """
SELECT SELECT
@@ -49,7 +49,7 @@ fun ThreadTable.getThreadsForBackup(): ChatIterator {
""" """
val cursor = readableDatabase.query(query) val cursor = readableDatabase.query(query)
return ChatIterator(cursor) return ChatExportIterator(cursor)
} }
fun ThreadTable.clearAllDataForBackupRestore() { fun ThreadTable.clearAllDataForBackupRestore() {
@@ -58,15 +58,10 @@ fun ThreadTable.clearAllDataForBackupRestore() {
clearCache() clearCache()
} }
fun ThreadTable.restoreFromBackup(chat: Chat, recipientId: RecipientId): Long? { fun ThreadTable.restoreFromBackup(chat: Chat, recipientId: RecipientId, importState: ImportState): Long? {
val chatColor = chat.style?.parseChatColor() val chatColor = chat.style?.remoteToLocalChatColors(importState)
val chatColorWithId = if (chatColor != null && chatColor.id is ChatColors.Id.NotSet) {
val savedColors = SignalDatabase.chatColors.getSavedChatColors() // TODO [backup] Wallpaper
val match = savedColors.find { it.matchesWithoutId(chatColor) }
match ?: SignalDatabase.chatColors.saveChatColors(chatColor)
} else {
chatColor
}
val threadId = writableDatabase val threadId = writableDatabase
.insertInto(ThreadTable.TABLE_NAME) .insertInto(ThreadTable.TABLE_NAME)
@@ -85,8 +80,8 @@ fun ThreadTable.restoreFromBackup(chat: Chat, recipientId: RecipientId): Long? {
RecipientTable.MENTION_SETTING to (if (chat.dontNotifyForMentionsIfMuted) RecipientTable.MentionSetting.DO_NOT_NOTIFY.id else RecipientTable.MentionSetting.ALWAYS_NOTIFY.id), RecipientTable.MENTION_SETTING to (if (chat.dontNotifyForMentionsIfMuted) RecipientTable.MentionSetting.DO_NOT_NOTIFY.id else RecipientTable.MentionSetting.ALWAYS_NOTIFY.id),
RecipientTable.MUTE_UNTIL to chat.muteUntilMs, RecipientTable.MUTE_UNTIL to chat.muteUntilMs,
RecipientTable.MESSAGE_EXPIRATION_TIME to chat.expirationTimerMs, RecipientTable.MESSAGE_EXPIRATION_TIME to chat.expirationTimerMs,
RecipientTable.CHAT_COLORS to chatColorWithId?.serialize()?.encode(), RecipientTable.CHAT_COLORS to chatColor?.serialize()?.encode(),
RecipientTable.CUSTOM_CHAT_COLORS_ID to (chatColorWithId?.id ?: ChatColors.Id.NotSet).longValue RecipientTable.CUSTOM_CHAT_COLORS_ID to (chatColor?.id ?: ChatColors.Id.NotSet).longValue
), ),
"${RecipientTable.ID} = ?", "${RecipientTable.ID} = ?",
SqlUtil.buildArgs(recipientId.toLong()) SqlUtil.buildArgs(recipientId.toLong())
@@ -95,7 +90,7 @@ fun ThreadTable.restoreFromBackup(chat: Chat, recipientId: RecipientId): Long? {
return threadId return threadId
} }
class ChatIterator(private val cursor: Cursor) : Iterator<Chat>, Closeable { class ChatExportIterator(private val cursor: Cursor) : Iterator<Chat>, Closeable {
override fun hasNext(): Boolean { override fun hasNext(): Boolean {
return cursor.count > 0 && !cursor.isLast return cursor.count > 0 && !cursor.isLast
} }
@@ -106,31 +101,32 @@ class ChatIterator(private val cursor: Cursor) : Iterator<Chat>, Closeable {
} }
val serializedChatColors = cursor.requireBlob(RecipientTable.CHAT_COLORS) val serializedChatColors = cursor.requireBlob(RecipientTable.CHAT_COLORS)
val customChatColorsId = ChatColors.Id.forLongValue(cursor.requireLong(RecipientTable.CUSTOM_CHAT_COLORS_ID)) val chatColorId = ChatColors.Id.forLongValue(cursor.requireLong(RecipientTable.CUSTOM_CHAT_COLORS_ID))
val chatColors: ChatColors? = if (serializedChatColors != null) { val chatColors: ChatColors? = serializedChatColors?.let { serialized ->
try { try {
ChatColors.forChatColor(customChatColorsId, ChatColor.ADAPTER.decode(serializedChatColors)) ChatColors.forChatColor(chatColorId, ChatColor.ADAPTER.decode(serialized))
} catch (e: InvalidProtocolBufferException) { } catch (e: InvalidProtocolBufferException) {
null null
} }
} else {
null
} }
var chatStyleBuilder: ChatStyle.Builder? = null var chatStyleBuilder: ChatStyle.Builder? = null
if (chatColors != null) { if (chatColors != null) {
chatStyleBuilder = ChatStyle.Builder() chatStyleBuilder = ChatStyle.Builder()
val presetBubbleColor = chatColors.tryToMapToBackupPreset() when (chatColorId) {
if (presetBubbleColor != null) { ChatColors.Id.NotSet -> {}
chatStyleBuilder.bubbleColorPreset = presetBubbleColor ChatColors.Id.Auto -> {
} else if (chatColors.isGradient()) { chatStyleBuilder.autoBubbleColor = ChatStyle.AutomaticBubbleColor()
chatStyleBuilder.bubbleGradient = ChatStyle.Gradient(angle = chatColors.getDegrees().toInt(), colors = chatColors.getColors().toList()) }
} else if (customChatColorsId is ChatColors.Id.Auto) { ChatColors.Id.BuiltIn -> {
chatStyleBuilder.autoBubbleColor = ChatStyle.AutomaticBubbleColor() chatStyleBuilder.bubbleColorPreset = chatColors.localToRemoteChatColors()
} else { }
chatStyleBuilder.bubbleSolidColor = chatColors.asSingleColor() is ChatColors.Id.Custom -> {
chatStyleBuilder.customColorId = chatColorId.longValue
}
} }
} }
// TODO [backup] wallpaper
return Chat( return Chat(
id = cursor.requireLong(ThreadTable.ID), id = cursor.requireLong(ThreadTable.ID),
@@ -150,9 +146,9 @@ class ChatIterator(private val cursor: Cursor) : Iterator<Chat>, Closeable {
} }
} }
private fun ChatStyle.parseChatColor(): ChatColors? { private fun ChatStyle.remoteToLocalChatColors(importState: ImportState): ChatColors? {
if (bubbleColorPreset != null) { if (this.bubbleColorPreset != null) {
return when (bubbleColorPreset) { return when (this.bubbleColorPreset) {
ChatStyle.BubbleColorPreset.SOLID_CRIMSON -> ChatColorsPalette.Bubbles.CRIMSON ChatStyle.BubbleColorPreset.SOLID_CRIMSON -> ChatColorsPalette.Bubbles.CRIMSON
ChatStyle.BubbleColorPreset.SOLID_VERMILION -> ChatColorsPalette.Bubbles.VERMILION ChatStyle.BubbleColorPreset.SOLID_VERMILION -> ChatColorsPalette.Bubbles.VERMILION
ChatStyle.BubbleColorPreset.SOLID_BURLAP -> ChatColorsPalette.Bubbles.BURLAP ChatStyle.BubbleColorPreset.SOLID_BURLAP -> ChatColorsPalette.Bubbles.BURLAP
@@ -177,27 +173,22 @@ private fun ChatStyle.parseChatColor(): ChatColors? {
ChatStyle.BubbleColorPreset.UNKNOWN_BUBBLE_COLOR_PRESET, ChatStyle.BubbleColorPreset.SOLID_ULTRAMARINE -> ChatColorsPalette.Bubbles.ULTRAMARINE ChatStyle.BubbleColorPreset.UNKNOWN_BUBBLE_COLOR_PRESET, ChatStyle.BubbleColorPreset.SOLID_ULTRAMARINE -> ChatColorsPalette.Bubbles.ULTRAMARINE
} }
} }
if (autoBubbleColor != null) {
if (this.autoBubbleColor != null) {
return ChatColorsPalette.Bubbles.default.withId(ChatColors.Id.Auto) return ChatColorsPalette.Bubbles.default.withId(ChatColors.Id.Auto)
} }
if (bubbleSolidColor != null) {
return ChatColors(id = ChatColors.Id.NotSet, singleColor = bubbleSolidColor, linearGradient = null) if (this.customColorId != null) {
} return importState.remoteToLocalColorId[this.customColorId]?.let { localId ->
if (bubbleGradient != null) { val colorId = ChatColors.Id.forLongValue(localId)
return ChatColors( ChatColorsPalette.Bubbles.default.withId(colorId)
id = ChatColors.Id.NotSet, }
singleColor = null,
linearGradient = ChatColors.LinearGradient(
degrees = bubbleGradient.angle.toFloat(),
colors = bubbleGradient.colors.toIntArray(),
positions = floatArrayOf(0f, 1f)
)
)
} }
return null return null
} }
private fun ChatColors.tryToMapToBackupPreset(): ChatStyle.BubbleColorPreset? { private fun ChatColors.localToRemoteChatColors(): ChatStyle.BubbleColorPreset? {
when (this) { when (this) {
// Solids // Solids
ChatColorsPalette.Bubbles.CRIMSON -> return ChatStyle.BubbleColorPreset.SOLID_CRIMSON ChatColorsPalette.Bubbles.CRIMSON -> return ChatStyle.BubbleColorPreset.SOLID_CRIMSON

View File

@@ -7,12 +7,16 @@ package org.thoughtcrime.securesms.backup.v2.processor
import okio.ByteString.Companion.EMPTY import okio.ByteString.Companion.EMPTY
import okio.ByteString.Companion.toByteString import okio.ByteString.Companion.toByteString
import org.signal.core.util.logging.Log
import org.thoughtcrime.securesms.backup.v2.ImportState
import org.thoughtcrime.securesms.backup.v2.database.restoreSelfFromBackup import org.thoughtcrime.securesms.backup.v2.database.restoreSelfFromBackup
import org.thoughtcrime.securesms.backup.v2.proto.AccountData 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.proto.Frame
import org.thoughtcrime.securesms.backup.v2.stream.BackupFrameEmitter import org.thoughtcrime.securesms.backup.v2.stream.BackupFrameEmitter
import org.thoughtcrime.securesms.components.settings.app.subscription.InAppPaymentsRepository import org.thoughtcrime.securesms.components.settings.app.subscription.InAppPaymentsRepository
import org.thoughtcrime.securesms.components.settings.app.usernamelinks.UsernameQrCodeColorScheme import org.thoughtcrime.securesms.components.settings.app.usernamelinks.UsernameQrCodeColorScheme
import org.thoughtcrime.securesms.conversation.colors.ChatColors
import org.thoughtcrime.securesms.database.SignalDatabase import org.thoughtcrime.securesms.database.SignalDatabase
import org.thoughtcrime.securesms.database.model.InAppPaymentSubscriberRecord import org.thoughtcrime.securesms.database.model.InAppPaymentSubscriberRecord
import org.thoughtcrime.securesms.dependencies.AppDependencies import org.thoughtcrime.securesms.dependencies.AppDependencies
@@ -32,6 +36,8 @@ import java.util.Currency
object AccountDataProcessor { object AccountDataProcessor {
private val TAG = Log.tag(AccountDataProcessor::class)
fun export(db: SignalDatabase, signalStore: SignalStore, emitter: BackupFrameEmitter) { fun export(db: SignalDatabase, signalStore: SignalStore, emitter: BackupFrameEmitter) {
val context = AppDependencies.application val context = AppDependencies.application
@@ -53,7 +59,7 @@ object AccountDataProcessor {
AccountData.UsernameLink( AccountData.UsernameLink(
entropy = signalStore.accountValues.usernameLink?.entropy?.toByteString() ?: EMPTY, entropy = signalStore.accountValues.usernameLink?.entropy?.toByteString() ?: EMPTY,
serverId = signalStore.accountValues.usernameLink?.serverId?.toByteArray()?.toByteString() ?: EMPTY, serverId = signalStore.accountValues.usernameLink?.serverId?.toByteArray()?.toByteString() ?: EMPTY,
color = signalStore.miscValues.usernameQrCodeColorScheme.toBackupUsernameColor() ?: AccountData.UsernameLink.Color.BLUE color = signalStore.miscValues.usernameQrCodeColorScheme.toBackupUsernameColor()
) )
} else { } else {
null null
@@ -67,7 +73,7 @@ object AccountDataProcessor {
notDiscoverableByPhoneNumber = signalStore.phoneNumberPrivacyValues.phoneNumberDiscoverabilityMode == PhoneNumberDiscoverabilityMode.NOT_DISCOVERABLE, notDiscoverableByPhoneNumber = signalStore.phoneNumberPrivacyValues.phoneNumberDiscoverabilityMode == PhoneNumberDiscoverabilityMode.NOT_DISCOVERABLE,
phoneNumberSharingMode = signalStore.phoneNumberPrivacyValues.phoneNumberSharingMode.toBackupPhoneNumberSharingMode(), phoneNumberSharingMode = signalStore.phoneNumberPrivacyValues.phoneNumberSharingMode.toBackupPhoneNumberSharingMode(),
preferContactAvatars = signalStore.settingsValues.isPreferSystemContactPhotos, preferContactAvatars = signalStore.settingsValues.isPreferSystemContactPhotos,
universalExpireTimer = signalStore.settingsValues.universalExpireTimer, universalExpireTimerSeconds = signalStore.settingsValues.universalExpireTimer,
preferredReactionEmoji = signalStore.emojiValues.rawReactions, preferredReactionEmoji = signalStore.emojiValues.rawReactions,
storiesDisabled = signalStore.storyValues.isFeatureDisabled, storiesDisabled = signalStore.storyValues.isFeatureDisabled,
hasViewedOnboardingStory = signalStore.storyValues.userHasViewedOnboardingStory, hasViewedOnboardingStory = signalStore.storyValues.userHasViewedOnboardingStory,
@@ -75,7 +81,8 @@ object AccountDataProcessor {
keepMutedChatsArchived = signalStore.settingsValues.shouldKeepMutedChatsArchived(), keepMutedChatsArchived = signalStore.settingsValues.shouldKeepMutedChatsArchived(),
displayBadgesOnProfile = signalStore.inAppPaymentValues.getDisplayBadgesOnProfile(), displayBadgesOnProfile = signalStore.inAppPaymentValues.getDisplayBadgesOnProfile(),
hasSeenGroupStoryEducationSheet = signalStore.storyValues.userHasSeenGroupStoryEducationSheet, hasSeenGroupStoryEducationSheet = signalStore.storyValues.userHasSeenGroupStoryEducationSheet,
hasCompletedUsernameOnboarding = signalStore.uiHintValues.hasCompletedUsernameOnboarding() hasCompletedUsernameOnboarding = signalStore.uiHintValues.hasCompletedUsernameOnboarding(),
customChatColors = db.chatColorsTable.getSavedChatColors().toRemoteChatColors()
), ),
donationSubscriberData = donationSubscriber?.toSubscriberData(signalStore.inAppPaymentValues.isDonationSubscriptionManuallyCancelled()) donationSubscriberData = donationSubscriber?.toSubscriberData(signalStore.inAppPaymentValues.isDonationSubscriptionManuallyCancelled())
) )
@@ -83,7 +90,7 @@ object AccountDataProcessor {
) )
} }
fun import(accountData: AccountData, selfId: RecipientId) { fun import(accountData: AccountData, selfId: RecipientId, importState: ImportState) {
SignalDatabase.recipients.restoreSelfFromBackup(accountData, selfId) SignalDatabase.recipients.restoreSelfFromBackup(accountData, selfId)
SignalStore.account.setRegistered(true) SignalStore.account.setRegistered(true)
@@ -99,7 +106,7 @@ object AccountDataProcessor {
SignalStore.phoneNumberPrivacy.phoneNumberDiscoverabilityMode = if (settings.notDiscoverableByPhoneNumber) PhoneNumberDiscoverabilityMode.NOT_DISCOVERABLE else PhoneNumberDiscoverabilityMode.DISCOVERABLE SignalStore.phoneNumberPrivacy.phoneNumberDiscoverabilityMode = if (settings.notDiscoverableByPhoneNumber) PhoneNumberDiscoverabilityMode.NOT_DISCOVERABLE else PhoneNumberDiscoverabilityMode.DISCOVERABLE
SignalStore.phoneNumberPrivacy.phoneNumberSharingMode = settings.phoneNumberSharingMode.toLocalPhoneNumberMode() SignalStore.phoneNumberPrivacy.phoneNumberSharingMode = settings.phoneNumberSharingMode.toLocalPhoneNumberMode()
SignalStore.settings.isPreferSystemContactPhotos = settings.preferContactAvatars SignalStore.settings.isPreferSystemContactPhotos = settings.preferContactAvatars
SignalStore.settings.universalExpireTimer = settings.universalExpireTimer SignalStore.settings.universalExpireTimer = settings.universalExpireTimerSeconds
SignalStore.emoji.reactions = settings.preferredReactionEmoji SignalStore.emoji.reactions = settings.preferredReactionEmoji
SignalStore.inAppPayments.setDisplayBadgesOnProfile(settings.displayBadgesOnProfile) SignalStore.inAppPayments.setDisplayBadgesOnProfile(settings.displayBadgesOnProfile)
SignalStore.settings.setKeepMutedChatsArchived(settings.keepMutedChatsArchived) SignalStore.settings.setKeepMutedChatsArchived(settings.keepMutedChatsArchived)
@@ -109,6 +116,32 @@ object AccountDataProcessor {
SignalStore.story.userHasSeenGroupStoryEducationSheet = settings.hasSeenGroupStoryEducationSheet SignalStore.story.userHasSeenGroupStoryEducationSheet = settings.hasSeenGroupStoryEducationSheet
SignalStore.story.viewedReceiptsEnabled = settings.storyViewReceiptsEnabled ?: settings.readReceipts SignalStore.story.viewedReceiptsEnabled = settings.storyViewReceiptsEnabled ?: settings.readReceipts
settings.customChatColors
.mapNotNull { chatColor ->
val id = ChatColors.Id.forLongValue(chatColor.id)
when {
chatColor.solid != null -> {
ChatColors.forColor(id, chatColor.solid)
}
chatColor.gradient != null -> {
ChatColors.forGradient(
id,
ChatColors.LinearGradient(
degrees = chatColor.gradient.angle.toFloat(),
colors = chatColor.gradient.colors.toIntArray(),
positions = chatColor.gradient.positions.toFloatArray()
)
)
}
else -> null
}
}
.forEach { chatColor ->
// We need to use the "NotSet" chatId so that this operation is treated as an insert rather than an update
val saved = SignalDatabase.chatColors.saveChatColors(chatColor.withId(ChatColors.Id.NotSet))
importState.remoteToLocalColorId[chatColor.id.longValue] = saved.id.longValue
}
if (accountData.donationSubscriberData != null) { if (accountData.donationSubscriberData != null) {
if (accountData.donationSubscriberData.subscriberId.size > 0) { if (accountData.donationSubscriberData.subscriberId.size > 0) {
val remoteSubscriberId = SubscriberId.fromBytes(accountData.donationSubscriberData.subscriberId.toByteArray()) val remoteSubscriberId = SubscriberId.fromBytes(accountData.donationSubscriberData.subscriberId.toByteArray())
@@ -204,4 +237,28 @@ object AccountDataProcessor {
val currencyCode = currency.currencyCode val currencyCode = currency.currencyCode
return AccountData.SubscriberData(subscriberId = subscriberId, currencyCode = currencyCode, manuallyCancelled = manuallyCancelled) return AccountData.SubscriberData(subscriberId = subscriberId, currencyCode = currencyCode, manuallyCancelled = manuallyCancelled)
} }
private fun List<ChatColors>.toRemoteChatColors(): List<ChatStyle.CustomChatColor> {
return this
.mapNotNull { local ->
if (local.linearGradient != null) {
ChatStyle.CustomChatColor(
id = local.id.longValue,
gradient = ChatStyle.Gradient(
angle = local.linearGradient.degrees.toInt(),
colors = local.linearGradient.colors.toList(),
positions = local.linearGradient.positions.toList()
)
)
} else if (local.singleColor != null) {
ChatStyle.CustomChatColor(
id = local.id.longValue,
solid = local.singleColor
)
} else {
Log.w(TAG, "Invalid custom color (id = ${local.id}, no gradient or solid color!")
null
}
}
}
} }

View File

@@ -6,7 +6,7 @@
package org.thoughtcrime.securesms.backup.v2.processor package org.thoughtcrime.securesms.backup.v2.processor
import org.signal.core.util.logging.Log import org.signal.core.util.logging.Log
import org.thoughtcrime.securesms.backup.v2.BackupState import org.thoughtcrime.securesms.backup.v2.ImportState
import org.thoughtcrime.securesms.backup.v2.database.getAdhocCallsForBackup import org.thoughtcrime.securesms.backup.v2.database.getAdhocCallsForBackup
import org.thoughtcrime.securesms.backup.v2.database.restoreCallLogFromBackup import org.thoughtcrime.securesms.backup.v2.database.restoreCallLogFromBackup
import org.thoughtcrime.securesms.backup.v2.proto.AdHocCall import org.thoughtcrime.securesms.backup.v2.proto.AdHocCall
@@ -28,7 +28,7 @@ object AdHocCallBackupProcessor {
} }
} }
fun import(call: AdHocCall, backupState: BackupState) { fun import(call: AdHocCall, importState: ImportState) {
SignalDatabase.calls.restoreCallLogFromBackup(call, backupState) SignalDatabase.calls.restoreCallLogFromBackup(call, importState)
} }
} }

View File

@@ -6,8 +6,8 @@
package org.thoughtcrime.securesms.backup.v2.processor package org.thoughtcrime.securesms.backup.v2.processor
import org.signal.core.util.logging.Log import org.signal.core.util.logging.Log
import org.thoughtcrime.securesms.backup.v2.BackupState
import org.thoughtcrime.securesms.backup.v2.ExportState import org.thoughtcrime.securesms.backup.v2.ExportState
import org.thoughtcrime.securesms.backup.v2.ImportState
import org.thoughtcrime.securesms.backup.v2.database.getThreadsForBackup import org.thoughtcrime.securesms.backup.v2.database.getThreadsForBackup
import org.thoughtcrime.securesms.backup.v2.database.restoreFromBackup import org.thoughtcrime.securesms.backup.v2.database.restoreFromBackup
import org.thoughtcrime.securesms.backup.v2.proto.Chat import org.thoughtcrime.securesms.backup.v2.proto.Chat
@@ -32,19 +32,17 @@ object ChatBackupProcessor {
} }
} }
fun import(chat: Chat, backupState: BackupState) { fun import(chat: Chat, importState: ImportState) {
val recipientId: RecipientId? = backupState.backupToLocalRecipientId[chat.recipientId] val recipientId: RecipientId? = importState.remoteToLocalRecipientId[chat.recipientId]
if (recipientId == null) { if (recipientId == null) {
Log.w(TAG, "Missing recipient for chat ${chat.id}") Log.w(TAG, "Missing recipient for chat ${chat.id}")
return return
} }
SignalDatabase.threads.restoreFromBackup(chat, recipientId)?.let { threadId -> SignalDatabase.threads.restoreFromBackup(chat, recipientId, importState)?.let { threadId ->
backupState.chatIdToLocalRecipientId[chat.id] = recipientId importState.chatIdToLocalRecipientId[chat.id] = recipientId
backupState.chatIdToLocalThreadId[chat.id] = threadId importState.chatIdToLocalThreadId[chat.id] = threadId
backupState.chatIdToBackupRecipientId[chat.id] = chat.recipientId importState.chatIdToBackupRecipientId[chat.id] = chat.recipientId
} }
// TODO there's several fields in the chat that actually need to be restored on the recipient table
} }
} }

View File

@@ -6,8 +6,8 @@
package org.thoughtcrime.securesms.backup.v2.processor package org.thoughtcrime.securesms.backup.v2.processor
import org.signal.core.util.logging.Log import org.signal.core.util.logging.Log
import org.thoughtcrime.securesms.backup.v2.BackupState
import org.thoughtcrime.securesms.backup.v2.ExportState import org.thoughtcrime.securesms.backup.v2.ExportState
import org.thoughtcrime.securesms.backup.v2.ImportState
import org.thoughtcrime.securesms.backup.v2.database.ChatItemImportInserter import org.thoughtcrime.securesms.backup.v2.database.ChatItemImportInserter
import org.thoughtcrime.securesms.backup.v2.database.createChatItemInserter import org.thoughtcrime.securesms.backup.v2.database.createChatItemInserter
import org.thoughtcrime.securesms.backup.v2.database.getMessagesForBackup import org.thoughtcrime.securesms.backup.v2.database.getMessagesForBackup
@@ -31,7 +31,7 @@ object ChatItemBackupProcessor {
} }
} }
fun beginImport(backupState: BackupState): ChatItemImportInserter { fun beginImport(importState: ImportState): ChatItemImportInserter {
return SignalDatabase.messages.createChatItemInserter(backupState) return SignalDatabase.messages.createChatItemInserter(importState)
} }
} }

View File

@@ -6,8 +6,8 @@
package org.thoughtcrime.securesms.backup.v2.processor package org.thoughtcrime.securesms.backup.v2.processor
import org.signal.core.util.logging.Log import org.signal.core.util.logging.Log
import org.thoughtcrime.securesms.backup.v2.BackupState
import org.thoughtcrime.securesms.backup.v2.ExportState import org.thoughtcrime.securesms.backup.v2.ExportState
import org.thoughtcrime.securesms.backup.v2.ImportState
import org.thoughtcrime.securesms.backup.v2.database.BackupRecipient import org.thoughtcrime.securesms.backup.v2.database.BackupRecipient
import org.thoughtcrime.securesms.backup.v2.database.getAllForBackup import org.thoughtcrime.securesms.backup.v2.database.getAllForBackup
import org.thoughtcrime.securesms.backup.v2.database.getCallLinksForBackup import org.thoughtcrime.securesms.backup.v2.database.getCallLinksForBackup
@@ -28,11 +28,11 @@ object RecipientBackupProcessor {
val TAG = Log.tag(RecipientBackupProcessor::class.java) val TAG = Log.tag(RecipientBackupProcessor::class.java)
fun export(db: SignalDatabase, signalStore: SignalStore, state: ExportState, emitter: BackupFrameEmitter) { fun export(db: SignalDatabase, signalStore: SignalStore, exportState: ExportState, emitter: BackupFrameEmitter) {
val selfId = db.recipientTable.getByAci(signalStore.accountValues.aci!!).get().toLong() val selfId = db.recipientTable.getByAci(signalStore.accountValues.aci!!).get().toLong()
val releaseChannelId = signalStore.releaseChannelValues.releaseChannelRecipientId val releaseChannelId = signalStore.releaseChannelValues.releaseChannelRecipientId
if (releaseChannelId != null) { if (releaseChannelId != null) {
state.recipientIds.add(releaseChannelId.toLong()) exportState.recipientIds.add(releaseChannelId.toLong())
emitter.emit( emitter.emit(
Frame( Frame(
recipient = BackupRecipient( recipient = BackupRecipient(
@@ -46,7 +46,7 @@ object RecipientBackupProcessor {
db.recipientTable.getContactsForBackup(selfId).use { reader -> db.recipientTable.getContactsForBackup(selfId).use { reader ->
for (backupRecipient in reader) { for (backupRecipient in reader) {
if (backupRecipient != null) { if (backupRecipient != null) {
state.recipientIds.add(backupRecipient.id) exportState.recipientIds.add(backupRecipient.id)
emitter.emit(Frame(recipient = backupRecipient)) emitter.emit(Frame(recipient = backupRecipient))
} }
} }
@@ -54,27 +54,27 @@ object RecipientBackupProcessor {
db.recipientTable.getGroupsForBackup().use { reader -> db.recipientTable.getGroupsForBackup().use { reader ->
for (backupRecipient in reader) { for (backupRecipient in reader) {
state.recipientIds.add(backupRecipient.id) exportState.recipientIds.add(backupRecipient.id)
emitter.emit(Frame(recipient = backupRecipient)) emitter.emit(Frame(recipient = backupRecipient))
} }
} }
db.distributionListTables.getAllForBackup().forEach { db.distributionListTables.getAllForBackup().forEach {
state.recipientIds.add(it.id) exportState.recipientIds.add(it.id)
emitter.emit(Frame(recipient = it)) emitter.emit(Frame(recipient = it))
} }
db.callLinkTable.getCallLinksForBackup().forEach { db.callLinkTable.getCallLinksForBackup().forEach {
state.recipientIds.add(it.id) exportState.recipientIds.add(it.id)
emitter.emit(Frame(recipient = it)) emitter.emit(Frame(recipient = it))
} }
} }
fun import(recipient: BackupRecipient, backupState: BackupState) { fun import(recipient: BackupRecipient, importState: ImportState) {
val newId = when { val newId = when {
recipient.contact != null -> SignalDatabase.recipients.restoreContactFromBackup(recipient.contact) recipient.contact != null -> SignalDatabase.recipients.restoreContactFromBackup(recipient.contact)
recipient.group != null -> SignalDatabase.recipients.restoreGroupFromBackup(recipient.group) recipient.group != null -> SignalDatabase.recipients.restoreGroupFromBackup(recipient.group)
recipient.distributionList != null -> SignalDatabase.distributionLists.restoreFromBackup(recipient.distributionList, backupState) recipient.distributionList != null -> SignalDatabase.distributionLists.restoreFromBackup(recipient.distributionList, importState)
recipient.self != null -> Recipient.self().id recipient.self != null -> Recipient.self().id
recipient.releaseNotes != null -> SignalDatabase.recipients.restoreReleaseNotes() recipient.releaseNotes != null -> SignalDatabase.recipients.restoreReleaseNotes()
recipient.callLink != null -> SignalDatabase.callLinks.restoreFromBackup(recipient.callLink) recipient.callLink != null -> SignalDatabase.callLinks.restoreFromBackup(recipient.callLink)
@@ -84,7 +84,7 @@ object RecipientBackupProcessor {
} }
} }
if (newId != null) { if (newId != null) {
backupState.backupToLocalRecipientId[recipient.id] = newId importState.remoteToLocalRecipientId[recipient.id] = newId
} }
} }
} }

View File

@@ -28,8 +28,8 @@ import kotlin.math.min
@Parcelize @Parcelize
class ChatColors( class ChatColors(
val id: Id, val id: Id,
private val linearGradient: LinearGradient?, val linearGradient: LinearGradient?,
private val singleColor: Int? val singleColor: Int?
) : Parcelable { ) : Parcelable {
fun isGradient(): Boolean = linearGradient != null fun isGradient(): Boolean = linearGradient != null

View File

@@ -384,7 +384,7 @@ object GroupsV2UpdateMessageConverter {
updates.add( updates.add(
GroupChangeChatUpdate.Update( GroupChangeChatUpdate.Update(
groupExpirationTimerUpdate = GroupExpirationTimerUpdate( groupExpirationTimerUpdate = GroupExpirationTimerUpdate(
expiresInMs = (change.newTimer!!.duration * 1000L).toUInt().toInt(), expiresInMs = (change.newTimer!!.duration * 1000L).toUInt().toLong(),
updaterAci = if (editorUnknown) null else change.editorServiceIdBytes updaterAci = if (editorUnknown) null else change.editorServiceIdBytes
) )
) )

View File

@@ -234,7 +234,7 @@ final class GroupsV2UpdateMessageProducer {
} }
} }
private void describeGroupExpirationTimerUpdate(@NonNull GroupExpirationTimerUpdate update, @NonNull List<UpdateDescription> updates) { private void describeGroupExpirationTimerUpdate(@NonNull GroupExpirationTimerUpdate update, @NonNull List<UpdateDescription> updates) {
final int duration = Math.toIntExact(Integer.toUnsignedLong(update.expiresInMs) / 1000); final int duration = Math.toIntExact(update.expiresInMs / 1000);
String time = ExpirationUtil.getExpirationDisplayValue(context, duration); String time = ExpirationUtil.getExpirationDisplayValue(context, duration);
if (update.updaterAci == null) { if (update.updaterAci == null) {
updates.add(updateDescription(context.getString(R.string.MessageRecord_disappearing_message_time_set_to_s, time), R.drawable.ic_update_timer_16)); updates.add(updateDescription(context.getString(R.string.MessageRecord_disappearing_message_time_set_to_s, time), R.drawable.ic_update_timer_16));

View File

@@ -63,7 +63,7 @@ message AccountData {
bool linkPreviews = 4; bool linkPreviews = 4;
bool notDiscoverableByPhoneNumber = 5; bool notDiscoverableByPhoneNumber = 5;
bool preferContactAvatars = 6; bool preferContactAvatars = 6;
uint32 universalExpireTimer = 7; // 0 means no universal expire timer. uint32 universalExpireTimerSeconds = 7; // 0 means no universal expire timer.
repeated string preferredReactionEmoji = 8; repeated string preferredReactionEmoji = 8;
bool displayBadgesOnProfile = 9; bool displayBadgesOnProfile = 9;
bool keepMutedChatsArchived = 10; bool keepMutedChatsArchived = 10;
@@ -75,6 +75,7 @@ message AccountData {
bool hasCompletedUsernameOnboarding = 16; bool hasCompletedUsernameOnboarding = 16;
PhoneNumberSharingMode phoneNumberSharingMode = 17; PhoneNumberSharingMode phoneNumberSharingMode = 17;
ChatStyle defaultChatStyle = 18; ChatStyle defaultChatStyle = 18;
repeated ChatStyle.CustomChatColor customChatColors = 19;
} }
message SubscriberData { message SubscriberData {
@@ -564,7 +565,7 @@ message FilePointer {
optional uint32 cdnNumber = 2; optional uint32 cdnNumber = 2;
bytes key = 3; bytes key = 3;
bytes digest = 4; bytes digest = 4;
uint32 size = 5; uint64 size = 5;
// Fallback in case backup tier upload failed. // Fallback in case backup tier upload failed.
optional string transitCdnKey = 6; optional string transitCdnKey = 6;
optional uint32 transitCdnNumber = 7; optional uint32 transitCdnNumber = 7;
@@ -756,7 +757,7 @@ message SimpleChatUpdate {
// For 1:1 chat updates only. // For 1:1 chat updates only.
// For group thread updates use GroupExpirationTimerUpdate. // For group thread updates use GroupExpirationTimerUpdate.
message ExpirationTimerChatUpdate { message ExpirationTimerChatUpdate {
uint32 expiresInMs = 1; // 0 means the expiration timer was disabled uint64 expiresInMs = 1; // 0 means the expiration timer was disabled
} }
message ProfileChangeChatUpdate { message ProfileChangeChatUpdate {
@@ -1022,7 +1023,7 @@ message GroupV2MigrationDroppedMembersUpdate {
// For 1:1 timer updates, use ExpirationTimerChatUpdate. // For 1:1 timer updates, use ExpirationTimerChatUpdate.
message GroupExpirationTimerUpdate { message GroupExpirationTimerUpdate {
uint32 expiresInMs = 1; // 0 means the expiration timer was disabled uint64 expiresInMs = 1; // 0 means the expiration timer was disabled
optional bytes updaterAci = 2; optional bytes updaterAci = 2;
} }
@@ -1034,10 +1035,19 @@ message StickerPack {
message ChatStyle { message ChatStyle {
message Gradient { message Gradient {
uint32 angle = 1; // degrees uint32 angle = 1; // degrees
repeated uint32 colors = 2; repeated fixed32 colors = 2;
repeated float positions = 3; // percent from 0 to 1 repeated float positions = 3; // percent from 0 to 1
} }
message CustomChatColor {
uint64 id = 1;
oneof color {
fixed32 solid = 2;
Gradient gradient = 3;
}
}
message AutomaticBubbleColor { message AutomaticBubbleColor {
} }
@@ -1098,10 +1108,14 @@ message ChatStyle {
} }
oneof bubbleColor { oneof bubbleColor {
BubbleColorPreset bubbleColorPreset = 3; // Bubble setting is automatically determined based on the wallpaper setting,
Gradient bubbleGradient = 4; // or `SOLID_ULTRAMARINE` for `noWallpaper`
uint32 bubbleSolidColor = 5; AutomaticBubbleColor autoBubbleColor = 3;
// Bubble setting is automatically determined based on the wallpaper setting. BubbleColorPreset bubbleColorPreset = 4;
AutomaticBubbleColor autoBubbleColor = 6;
// See AccountSettings.customChatColors
uint64 customColorId = 5;
} }
bool dimWallpaperInDarkMode = 7;
} }