From ac0e80ca05b40db422cc0eaf7f2df7a4f1fb235b Mon Sep 17 00:00:00 2001 From: Greyson Parrelli Date: Tue, 1 Oct 2024 14:07:08 -0400 Subject: [PATCH] Refactor archive importing. --- .../securesms/backup/v2/ArchiveTypeAliases.kt | 1 + .../securesms/backup/v2/BackupRepository.kt | 40 +-- ...kt => AttachmentTableArchiveExtensions.kt} | 0 .../CallLinkTableArchiveExtensions.kt | 18 + .../database/CallLinkTableBackupExtensions.kt | 57 --- .../v2/database/CallTableArchiveExtensions.kt | 19 + .../v2/database/CallTableBackupExtensions.kt | 41 --- ...kt => ChatColorsTableArchiveExtensions.kt} | 0 ...=> DatabaseAttachmentArchiveExtensions.kt} | 0 ...DistributionListTablesArchiveExtensions.kt | 45 +++ .../DistributionListTablesBackupExtensions.kt | 102 ------ ... => InAppPaymentTableArchiveExtensions.kt} | 0 ...ns.kt => MessageTableArchiveExtensions.kt} | 11 +- ...s.kt => ReactionTableArchiveExtensions.kt} | 0 .../RecipientTableArchiveExtensions.kt | 142 ++++++++ .../RecipientTableBackupExtensions.kt | 336 ------------------ ...ns.kt => StickerTableArchiveExtensions.kt} | 0 .../database/ThreadTableArchiveExtensions.kt | 43 +++ .../database/ThreadTableBackupExtensions.kt | 100 ------ ...terator.kt => AdHocCallArchiveExporter.kt} | 2 +- ...Iterator.kt => CallLinkArchiveExporter.kt} | 2 +- ...portIterator.kt => ChatArchiveExporter.kt} | 2 +- ...Iterator.kt => ChatItemArchiveExporter.kt} | 4 +- ...tIterator.kt => ContactArchiveExporter.kt} | 2 +- ....kt => DistributionListArchiveExporter.kt} | 2 +- ...ortIterator.kt => GroupArchiveExporter.kt} | 2 +- .../v2/importer/AdHodCallArchiveImporter.kt | 36 ++ .../v2/importer/CallLinkArchiveImporter.kt | 53 +++ .../backup/v2/importer/ChatArchiveImporter.kt | 71 ++++ .../ChatItemArchiveImporter.kt} | 10 +- .../v2/importer/ContactArchiveImporter.kt | 86 +++++ .../DistributionListArchiveImporter.kt | 72 ++++ .../v2/importer/GroupArchiveImporter.kt | 146 ++++++++ ...ssor.kt => AccountDataArchiveProcessor.kt} | 4 +- ...cessor.kt => AdHocCallArchiveProcessor.kt} | 8 +- ...upProcessor.kt => ChatArchiveProcessor.kt} | 15 +- ...ocessor.kt => ChatItemArchiveProcessor.kt} | 8 +- ...cessor.kt => RecipientArchiveProcessor.kt} | 19 +- ...rocessor.kt => StickerArchiveProcessor.kt} | 2 +- .../components/webrtc/v2/CallInfoCallbacks.kt | 4 +- .../securesms/database/SignalDatabase.kt | 8 + 41 files changed, 808 insertions(+), 705 deletions(-) rename app/src/main/java/org/thoughtcrime/securesms/backup/v2/database/{AttachmentTableBackupExtensions.kt => AttachmentTableArchiveExtensions.kt} (100%) create mode 100644 app/src/main/java/org/thoughtcrime/securesms/backup/v2/database/CallLinkTableArchiveExtensions.kt delete mode 100644 app/src/main/java/org/thoughtcrime/securesms/backup/v2/database/CallLinkTableBackupExtensions.kt create mode 100644 app/src/main/java/org/thoughtcrime/securesms/backup/v2/database/CallTableArchiveExtensions.kt delete mode 100644 app/src/main/java/org/thoughtcrime/securesms/backup/v2/database/CallTableBackupExtensions.kt rename app/src/main/java/org/thoughtcrime/securesms/backup/v2/database/{ChatColorsTableBackupExtensions.kt => ChatColorsTableArchiveExtensions.kt} (100%) rename app/src/main/java/org/thoughtcrime/securesms/backup/v2/database/{DatabaseAttachmentBackupExtensions.kt => DatabaseAttachmentArchiveExtensions.kt} (100%) create mode 100644 app/src/main/java/org/thoughtcrime/securesms/backup/v2/database/DistributionListTablesArchiveExtensions.kt delete mode 100644 app/src/main/java/org/thoughtcrime/securesms/backup/v2/database/DistributionListTablesBackupExtensions.kt rename app/src/main/java/org/thoughtcrime/securesms/backup/v2/database/{InAppPaymentTableBackupExtensions.kt => InAppPaymentTableArchiveExtensions.kt} (100%) rename app/src/main/java/org/thoughtcrime/securesms/backup/v2/database/{MessageTableBackupExtensions.kt => MessageTableArchiveExtensions.kt} (88%) rename app/src/main/java/org/thoughtcrime/securesms/backup/v2/database/{ReactionTableBackupExtensions.kt => ReactionTableArchiveExtensions.kt} (100%) create mode 100644 app/src/main/java/org/thoughtcrime/securesms/backup/v2/database/RecipientTableArchiveExtensions.kt delete mode 100644 app/src/main/java/org/thoughtcrime/securesms/backup/v2/database/RecipientTableBackupExtensions.kt rename app/src/main/java/org/thoughtcrime/securesms/backup/v2/database/{StickerTableBackupExtensions.kt => StickerTableArchiveExtensions.kt} (100%) create mode 100644 app/src/main/java/org/thoughtcrime/securesms/backup/v2/database/ThreadTableArchiveExtensions.kt delete mode 100644 app/src/main/java/org/thoughtcrime/securesms/backup/v2/database/ThreadTableBackupExtensions.kt rename app/src/main/java/org/thoughtcrime/securesms/backup/v2/exporters/{AdHocCallArchiveExportIterator.kt => AdHocCallArchiveExporter.kt} (91%) rename app/src/main/java/org/thoughtcrime/securesms/backup/v2/exporters/{CallLinkArchiveExportIterator.kt => CallLinkArchiveExporter.kt} (94%) rename app/src/main/java/org/thoughtcrime/securesms/backup/v2/exporters/{ChatArchiveExportIterator.kt => ChatArchiveExporter.kt} (95%) rename app/src/main/java/org/thoughtcrime/securesms/backup/v2/exporters/{ChatItemArchiveExportIterator.kt => ChatItemArchiveExporter.kt} (99%) rename app/src/main/java/org/thoughtcrime/securesms/backup/v2/exporters/{ContactArchiveExportIterator.kt => ContactArchiveExporter.kt} (96%) rename app/src/main/java/org/thoughtcrime/securesms/backup/v2/exporters/{DistributionListArchiveExportIterator.kt => DistributionListArchiveExporter.kt} (98%) rename app/src/main/java/org/thoughtcrime/securesms/backup/v2/exporters/{GroupArchiveExportIterator.kt => GroupArchiveExporter.kt} (98%) create mode 100644 app/src/main/java/org/thoughtcrime/securesms/backup/v2/importer/AdHodCallArchiveImporter.kt create mode 100644 app/src/main/java/org/thoughtcrime/securesms/backup/v2/importer/CallLinkArchiveImporter.kt create mode 100644 app/src/main/java/org/thoughtcrime/securesms/backup/v2/importer/ChatArchiveImporter.kt rename app/src/main/java/org/thoughtcrime/securesms/backup/v2/{database/ChatItemImportInserter.kt => importer/ChatItemArchiveImporter.kt} (99%) create mode 100644 app/src/main/java/org/thoughtcrime/securesms/backup/v2/importer/ContactArchiveImporter.kt create mode 100644 app/src/main/java/org/thoughtcrime/securesms/backup/v2/importer/DistributionListArchiveImporter.kt create mode 100644 app/src/main/java/org/thoughtcrime/securesms/backup/v2/importer/GroupArchiveImporter.kt rename app/src/main/java/org/thoughtcrime/securesms/backup/v2/processor/{AccountDataBackupProcessor.kt => AccountDataArchiveProcessor.kt} (99%) rename app/src/main/java/org/thoughtcrime/securesms/backup/v2/processor/{AdHocCallBackupProcessor.kt => AdHocCallArchiveProcessor.kt} (79%) rename app/src/main/java/org/thoughtcrime/securesms/backup/v2/processor/{ChatBackupProcessor.kt => ChatArchiveProcessor.kt} (76%) rename app/src/main/java/org/thoughtcrime/securesms/backup/v2/processor/{ChatItemBackupProcessor.kt => ChatItemArchiveProcessor.kt} (84%) rename app/src/main/java/org/thoughtcrime/securesms/backup/v2/processor/{RecipientBackupProcessor.kt => RecipientArchiveProcessor.kt} (79%) rename app/src/main/java/org/thoughtcrime/securesms/backup/v2/processor/{StickerBackupProcessor.kt => StickerArchiveProcessor.kt} (98%) diff --git a/app/src/main/java/org/thoughtcrime/securesms/backup/v2/ArchiveTypeAliases.kt b/app/src/main/java/org/thoughtcrime/securesms/backup/v2/ArchiveTypeAliases.kt index 9655d3339e..58c8f03724 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/backup/v2/ArchiveTypeAliases.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/backup/v2/ArchiveTypeAliases.kt @@ -7,3 +7,4 @@ 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 diff --git a/app/src/main/java/org/thoughtcrime/securesms/backup/v2/BackupRepository.kt b/app/src/main/java/org/thoughtcrime/securesms/backup/v2/BackupRepository.kt index edb5b3fb85..08735439d4 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/backup/v2/BackupRepository.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/backup/v2/BackupRepository.kt @@ -27,14 +27,14 @@ import org.thoughtcrime.securesms.attachments.Attachment import org.thoughtcrime.securesms.attachments.AttachmentId import org.thoughtcrime.securesms.attachments.Cdn import org.thoughtcrime.securesms.attachments.DatabaseAttachment -import org.thoughtcrime.securesms.backup.v2.database.ChatItemImportInserter import org.thoughtcrime.securesms.backup.v2.database.clearAllDataForBackupRestore -import org.thoughtcrime.securesms.backup.v2.processor.AccountDataBackupProcessor -import org.thoughtcrime.securesms.backup.v2.processor.AdHocCallBackupProcessor -import org.thoughtcrime.securesms.backup.v2.processor.ChatBackupProcessor -import org.thoughtcrime.securesms.backup.v2.processor.ChatItemBackupProcessor -import org.thoughtcrime.securesms.backup.v2.processor.RecipientBackupProcessor -import org.thoughtcrime.securesms.backup.v2.processor.StickerBackupProcessor +import org.thoughtcrime.securesms.backup.v2.importer.ChatItemArchiveImporter +import org.thoughtcrime.securesms.backup.v2.processor.AccountDataArchiveProcessor +import org.thoughtcrime.securesms.backup.v2.processor.AdHocCallArchiveProcessor +import org.thoughtcrime.securesms.backup.v2.processor.ChatArchiveProcessor +import org.thoughtcrime.securesms.backup.v2.processor.ChatItemArchiveProcessor +import org.thoughtcrime.securesms.backup.v2.processor.RecipientArchiveProcessor +import org.thoughtcrime.securesms.backup.v2.processor.StickerArchiveProcessor import org.thoughtcrime.securesms.backup.v2.proto.BackupInfo import org.thoughtcrime.securesms.backup.v2.stream.BackupExportWriter import org.thoughtcrime.securesms.backup.v2.stream.BackupImportReader @@ -285,37 +285,37 @@ object BackupRepository { // We're using a snapshot, so the transaction is more for perf than correctness dbSnapshot.rawWritableDatabase.withinTransaction { progressEmitter?.onAccount() - AccountDataBackupProcessor.export(dbSnapshot, signalStoreSnapshot) { + AccountDataArchiveProcessor.export(dbSnapshot, signalStoreSnapshot) { writer.write(it) eventTimer.emit("account") } progressEmitter?.onRecipient() - RecipientBackupProcessor.export(dbSnapshot, signalStoreSnapshot, exportState) { + RecipientArchiveProcessor.export(dbSnapshot, signalStoreSnapshot, exportState) { writer.write(it) eventTimer.emit("recipient") } progressEmitter?.onThread() - ChatBackupProcessor.export(dbSnapshot, exportState) { frame -> + ChatArchiveProcessor.export(dbSnapshot, exportState) { frame -> writer.write(frame) eventTimer.emit("thread") } progressEmitter?.onCall() - AdHocCallBackupProcessor.export(dbSnapshot) { frame -> + AdHocCallArchiveProcessor.export(dbSnapshot) { frame -> writer.write(frame) eventTimer.emit("call") } progressEmitter?.onSticker() - StickerBackupProcessor.export(dbSnapshot) { frame -> + StickerArchiveProcessor.export(dbSnapshot) { frame -> writer.write(frame) eventTimer.emit("sticker-pack") } progressEmitter?.onMessage() - ChatItemBackupProcessor.export(dbSnapshot, exportState) { frame -> + ChatItemArchiveProcessor.export(dbSnapshot, exportState) { frame -> writer.write(frame) eventTimer.emit("message") } @@ -406,38 +406,38 @@ object BackupRepository { eventTimer.emit("setup") val importState = ImportState(backupKey) - val chatItemInserter: ChatItemImportInserter = ChatItemBackupProcessor.beginImport(importState) + val chatItemInserter: ChatItemArchiveImporter = ChatItemArchiveProcessor.beginImport(importState) val totalLength = frameReader.getStreamLength() for (frame in frameReader) { when { frame.account != null -> { - AccountDataBackupProcessor.import(frame.account, selfId, importState) + AccountDataArchiveProcessor.import(frame.account, selfId, importState) eventTimer.emit("account") } frame.recipient != null -> { - RecipientBackupProcessor.import(frame.recipient, importState) + RecipientArchiveProcessor.import(frame.recipient, importState) eventTimer.emit("recipient") } frame.chat != null -> { - ChatBackupProcessor.import(frame.chat, importState) + ChatArchiveProcessor.import(frame.chat, importState) eventTimer.emit("chat") } frame.adHocCall != null -> { - AdHocCallBackupProcessor.import(frame.adHocCall, importState) + AdHocCallArchiveProcessor.import(frame.adHocCall, importState) eventTimer.emit("call") } frame.stickerPack != null -> { - StickerBackupProcessor.import(frame.stickerPack) + StickerArchiveProcessor.import(frame.stickerPack) eventTimer.emit("sticker-pack") } frame.chatItem != null -> { - chatItemInserter.insert(frame.chatItem) + chatItemInserter.import(frame.chatItem) eventTimer.emit("chatItem") // TODO if there's stuff in the stream after chatItems, we need to flush the inserter before going to the next phase } diff --git a/app/src/main/java/org/thoughtcrime/securesms/backup/v2/database/AttachmentTableBackupExtensions.kt b/app/src/main/java/org/thoughtcrime/securesms/backup/v2/database/AttachmentTableArchiveExtensions.kt similarity index 100% rename from app/src/main/java/org/thoughtcrime/securesms/backup/v2/database/AttachmentTableBackupExtensions.kt rename to app/src/main/java/org/thoughtcrime/securesms/backup/v2/database/AttachmentTableArchiveExtensions.kt diff --git a/app/src/main/java/org/thoughtcrime/securesms/backup/v2/database/CallLinkTableArchiveExtensions.kt b/app/src/main/java/org/thoughtcrime/securesms/backup/v2/database/CallLinkTableArchiveExtensions.kt new file mode 100644 index 0000000000..719f798996 --- /dev/null +++ b/app/src/main/java/org/thoughtcrime/securesms/backup/v2/database/CallLinkTableArchiveExtensions.kt @@ -0,0 +1,18 @@ +/* + * Copyright 2024 Signal Messenger, LLC + * SPDX-License-Identifier: AGPL-3.0-only + */ + +package org.thoughtcrime.securesms.backup.v2.database + +import org.signal.core.util.select +import org.thoughtcrime.securesms.database.CallLinkTable + +fun CallLinkTable.getCallLinksForBackup(): CallLinkArchiveExporter { + val cursor = readableDatabase + .select() + .from(CallLinkTable.TABLE_NAME) + .run() + + return CallLinkArchiveExporter(cursor) +} diff --git a/app/src/main/java/org/thoughtcrime/securesms/backup/v2/database/CallLinkTableBackupExtensions.kt b/app/src/main/java/org/thoughtcrime/securesms/backup/v2/database/CallLinkTableBackupExtensions.kt deleted file mode 100644 index e063e9bb82..0000000000 --- a/app/src/main/java/org/thoughtcrime/securesms/backup/v2/database/CallLinkTableBackupExtensions.kt +++ /dev/null @@ -1,57 +0,0 @@ -/* - * Copyright 2024 Signal Messenger, LLC - * SPDX-License-Identifier: AGPL-3.0-only - */ - -package org.thoughtcrime.securesms.backup.v2.database - -import org.signal.core.util.select -import org.signal.ringrtc.CallLinkRootKey -import org.signal.ringrtc.CallLinkState -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 -import org.thoughtcrime.securesms.service.webrtc.links.CallLinkCredentials -import org.thoughtcrime.securesms.service.webrtc.links.CallLinkRoomId -import org.thoughtcrime.securesms.service.webrtc.links.SignalCallLinkState -import java.time.Instant - -fun CallLinkTable.getCallLinksForBackup(): CallLinkArchiveExportIterator { - val cursor = readableDatabase - .select() - .from(CallLinkTable.TABLE_NAME) - .run() - - return CallLinkArchiveExportIterator(cursor) -} - -fun CallLinkTable.restoreFromBackup(callLink: CallLink): RecipientId? { - val rootKey: CallLinkRootKey - try { - rootKey = CallLinkRootKey(callLink.rootKey.toByteArray()) - } catch (e: Exception) { - return null - } - return SignalDatabase.callLinks.insertCallLink( - CallLinkTable.CallLink( - recipientId = RecipientId.UNKNOWN, - roomId = CallLinkRoomId.fromCallLinkRootKey(rootKey), - credentials = CallLinkCredentials(callLink.rootKey.toByteArray(), callLink.adminKey?.toByteArray()), - state = SignalCallLinkState( - name = callLink.name, - restrictions = callLink.restrictions.toLocal(), - expiration = Instant.ofEpochMilli(callLink.expirationMs) - ), - deletionTimestamp = 0L - ) - ) -} - -private fun CallLink.Restrictions.toLocal(): CallLinkState.Restrictions { - return when (this) { - CallLink.Restrictions.ADMIN_APPROVAL -> CallLinkState.Restrictions.ADMIN_APPROVAL - CallLink.Restrictions.NONE -> CallLinkState.Restrictions.NONE - CallLink.Restrictions.UNKNOWN -> CallLinkState.Restrictions.UNKNOWN - } -} diff --git a/app/src/main/java/org/thoughtcrime/securesms/backup/v2/database/CallTableArchiveExtensions.kt b/app/src/main/java/org/thoughtcrime/securesms/backup/v2/database/CallTableArchiveExtensions.kt new file mode 100644 index 0000000000..1c780545d6 --- /dev/null +++ b/app/src/main/java/org/thoughtcrime/securesms/backup/v2/database/CallTableArchiveExtensions.kt @@ -0,0 +1,19 @@ +/* + * Copyright 2023 Signal Messenger, LLC + * SPDX-License-Identifier: AGPL-3.0-only + */ + +package org.thoughtcrime.securesms.backup.v2.database + +import org.signal.core.util.select +import org.thoughtcrime.securesms.database.CallTable + +fun CallTable.getAdhocCallsForBackup(): AdHocCallArchiveExporter { + return AdHocCallArchiveExporter( + readableDatabase + .select() + .from(CallTable.TABLE_NAME) + .where("${CallTable.TYPE} = ?", CallTable.Type.serialize(CallTable.Type.AD_HOC_CALL)) + .run() + ) +} diff --git a/app/src/main/java/org/thoughtcrime/securesms/backup/v2/database/CallTableBackupExtensions.kt b/app/src/main/java/org/thoughtcrime/securesms/backup/v2/database/CallTableBackupExtensions.kt deleted file mode 100644 index 737f8b2fd1..0000000000 --- a/app/src/main/java/org/thoughtcrime/securesms/backup/v2/database/CallTableBackupExtensions.kt +++ /dev/null @@ -1,41 +0,0 @@ -/* - * Copyright 2023 Signal Messenger, LLC - * SPDX-License-Identifier: AGPL-3.0-only - */ - -package org.thoughtcrime.securesms.backup.v2.database - -import org.signal.core.util.insertInto -import org.signal.core.util.select -import org.thoughtcrime.securesms.backup.v2.ImportState -import org.thoughtcrime.securesms.backup.v2.proto.AdHocCall -import org.thoughtcrime.securesms.database.CallTable - -fun CallTable.getAdhocCallsForBackup(): AdHocCallArchiveExportIterator { - return AdHocCallArchiveExportIterator( - readableDatabase - .select() - .from(CallTable.TABLE_NAME) - .where("${CallTable.TYPE} = ?", CallTable.Type.serialize(CallTable.Type.AD_HOC_CALL)) - .run() - ) -} - -fun CallTable.restoreCallLogFromBackup(call: AdHocCall, importState: ImportState) { - val event = when (call.state) { - AdHocCall.State.GENERIC -> CallTable.Event.GENERIC_GROUP_CALL - AdHocCall.State.UNKNOWN_STATE -> CallTable.Event.GENERIC_GROUP_CALL - } - - writableDatabase - .insertInto(CallTable.TABLE_NAME) - .values( - CallTable.CALL_ID to call.callId, - CallTable.PEER to importState.remoteToLocalRecipientId[call.recipientId]!!.serialize(), - CallTable.TYPE to CallTable.Type.serialize(CallTable.Type.AD_HOC_CALL), - CallTable.DIRECTION to CallTable.Direction.serialize(CallTable.Direction.OUTGOING), - CallTable.EVENT to CallTable.Event.serialize(event), - CallTable.TIMESTAMP to call.callTimestamp - ) - .run() -} diff --git a/app/src/main/java/org/thoughtcrime/securesms/backup/v2/database/ChatColorsTableBackupExtensions.kt b/app/src/main/java/org/thoughtcrime/securesms/backup/v2/database/ChatColorsTableArchiveExtensions.kt similarity index 100% rename from app/src/main/java/org/thoughtcrime/securesms/backup/v2/database/ChatColorsTableBackupExtensions.kt rename to app/src/main/java/org/thoughtcrime/securesms/backup/v2/database/ChatColorsTableArchiveExtensions.kt diff --git a/app/src/main/java/org/thoughtcrime/securesms/backup/v2/database/DatabaseAttachmentBackupExtensions.kt b/app/src/main/java/org/thoughtcrime/securesms/backup/v2/database/DatabaseAttachmentArchiveExtensions.kt similarity index 100% rename from app/src/main/java/org/thoughtcrime/securesms/backup/v2/database/DatabaseAttachmentBackupExtensions.kt rename to app/src/main/java/org/thoughtcrime/securesms/backup/v2/database/DatabaseAttachmentArchiveExtensions.kt diff --git a/app/src/main/java/org/thoughtcrime/securesms/backup/v2/database/DistributionListTablesArchiveExtensions.kt b/app/src/main/java/org/thoughtcrime/securesms/backup/v2/database/DistributionListTablesArchiveExtensions.kt new file mode 100644 index 0000000000..db5232ec1e --- /dev/null +++ b/app/src/main/java/org/thoughtcrime/securesms/backup/v2/database/DistributionListTablesArchiveExtensions.kt @@ -0,0 +1,45 @@ +/* + * Copyright 2023 Signal Messenger, LLC + * SPDX-License-Identifier: AGPL-3.0-only + */ + +package org.thoughtcrime.securesms.backup.v2.database + +import org.signal.core.util.deleteAll +import org.signal.core.util.select +import org.signal.core.util.withinTransaction +import org.thoughtcrime.securesms.backup.v2.exporters.DistributionListArchiveExporter +import org.thoughtcrime.securesms.database.DistributionListTables +import org.thoughtcrime.securesms.database.model.DistributionListId +import org.thoughtcrime.securesms.database.model.DistributionListPrivacyMode +import org.thoughtcrime.securesms.recipients.RecipientId + +fun DistributionListTables.getAllForBackup(): DistributionListArchiveExporter { + val cursor = readableDatabase + .select() + .from(DistributionListTables.ListTable.TABLE_NAME) + .run() + + return DistributionListArchiveExporter(cursor, this) +} + +fun DistributionListTables.getMembersForBackup(id: DistributionListId): List { + lateinit var privacyMode: DistributionListPrivacyMode + lateinit var rawMembers: List + + readableDatabase.withinTransaction { + privacyMode = getPrivacyMode(id) + rawMembers = getRawMembers(id, privacyMode) + } + + return when (privacyMode) { + DistributionListPrivacyMode.ALL -> emptyList() + DistributionListPrivacyMode.ONLY_WITH -> rawMembers + DistributionListPrivacyMode.ALL_EXCEPT -> rawMembers + } +} + +fun DistributionListTables.clearAllDataForBackupRestore() { + writableDatabase.deleteAll(DistributionListTables.ListTable.TABLE_NAME) + writableDatabase.deleteAll(DistributionListTables.MembershipTable.TABLE_NAME) +} diff --git a/app/src/main/java/org/thoughtcrime/securesms/backup/v2/database/DistributionListTablesBackupExtensions.kt b/app/src/main/java/org/thoughtcrime/securesms/backup/v2/database/DistributionListTablesBackupExtensions.kt deleted file mode 100644 index 24045a63ef..0000000000 --- a/app/src/main/java/org/thoughtcrime/securesms/backup/v2/database/DistributionListTablesBackupExtensions.kt +++ /dev/null @@ -1,102 +0,0 @@ -/* - * Copyright 2023 Signal Messenger, LLC - * SPDX-License-Identifier: AGPL-3.0-only - */ - -package org.thoughtcrime.securesms.backup.v2.database - -import org.signal.core.util.deleteAll -import org.signal.core.util.logging.Log -import org.signal.core.util.select -import org.signal.core.util.withinTransaction -import org.thoughtcrime.securesms.backup.v2.ImportState -import org.thoughtcrime.securesms.backup.v2.exporters.DistributionListArchiveExportIterator -import org.thoughtcrime.securesms.backup.v2.proto.DistributionListItem -import org.thoughtcrime.securesms.database.DistributionListTables -import org.thoughtcrime.securesms.database.SignalDatabase -import org.thoughtcrime.securesms.database.model.DistributionListId -import org.thoughtcrime.securesms.database.model.DistributionListPrivacyMode -import org.thoughtcrime.securesms.recipients.RecipientId -import org.whispersystems.signalservice.api.push.DistributionId -import org.whispersystems.signalservice.api.util.UuidUtil -import org.thoughtcrime.securesms.backup.v2.proto.DistributionList as BackupDistributionList - -private val TAG = Log.tag(DistributionListTables::class.java) - -fun DistributionListTables.getAllForBackup(): DistributionListArchiveExportIterator { - val cursor = readableDatabase - .select() - .from(DistributionListTables.ListTable.TABLE_NAME) - .run() - - return DistributionListArchiveExportIterator(cursor, this) -} - -fun DistributionListTables.getMembersForBackup(id: DistributionListId): List { - lateinit var privacyMode: DistributionListPrivacyMode - lateinit var rawMembers: List - - readableDatabase.withinTransaction { - privacyMode = getPrivacyMode(id) - rawMembers = getRawMembers(id, privacyMode) - } - - return when (privacyMode) { - DistributionListPrivacyMode.ALL -> emptyList() - DistributionListPrivacyMode.ONLY_WITH -> rawMembers - DistributionListPrivacyMode.ALL_EXCEPT -> rawMembers - } -} - -fun DistributionListTables.restoreFromBackup(dlistItem: DistributionListItem, importState: ImportState): RecipientId? { - if (dlistItem.deletionTimestamp != null && dlistItem.deletionTimestamp > 0) { - val dlistId = createList( - name = "", - members = emptyList(), - distributionId = DistributionId.from(UuidUtil.fromByteString(dlistItem.distributionId)), - allowsReplies = false, - deletionTimestamp = dlistItem.deletionTimestamp, - storageId = null, - privacyMode = DistributionListPrivacyMode.ONLY_WITH - )!! - - return SignalDatabase.distributionLists.getRecipientId(dlistId)!! - } - - val dlist = dlistItem.distributionList ?: return null - val members: List = dlist.memberRecipientIds - .mapNotNull { importState.remoteToLocalRecipientId[it] } - - if (members.size != dlist.memberRecipientIds.size) { - Log.w(TAG, "Couldn't find some member recipients! Missing backup recipientIds: ${dlist.memberRecipientIds.toSet() - members.toSet()}") - } - - val distributionId = DistributionId.from(UuidUtil.fromByteString(dlistItem.distributionId)) - val privacyMode = dlist.privacyMode.toLocalPrivacyMode() - - val dlistId = createList( - name = dlist.name, - members = members, - distributionId = distributionId, - allowsReplies = dlist.allowReplies, - deletionTimestamp = dlistItem.deletionTimestamp ?: 0, - storageId = null, - privacyMode = privacyMode - )!! - - return SignalDatabase.distributionLists.getRecipientId(dlistId)!! -} - -fun DistributionListTables.clearAllDataForBackupRestore() { - writableDatabase.deleteAll(DistributionListTables.ListTable.TABLE_NAME) - writableDatabase.deleteAll(DistributionListTables.MembershipTable.TABLE_NAME) -} - -private fun BackupDistributionList.PrivacyMode.toLocalPrivacyMode(): DistributionListPrivacyMode { - return when (this) { - BackupDistributionList.PrivacyMode.UNKNOWN -> DistributionListPrivacyMode.ALL - BackupDistributionList.PrivacyMode.ONLY_WITH -> DistributionListPrivacyMode.ONLY_WITH - BackupDistributionList.PrivacyMode.ALL -> DistributionListPrivacyMode.ALL - BackupDistributionList.PrivacyMode.ALL_EXCEPT -> DistributionListPrivacyMode.ALL_EXCEPT - } -} diff --git a/app/src/main/java/org/thoughtcrime/securesms/backup/v2/database/InAppPaymentTableBackupExtensions.kt b/app/src/main/java/org/thoughtcrime/securesms/backup/v2/database/InAppPaymentTableArchiveExtensions.kt similarity index 100% rename from app/src/main/java/org/thoughtcrime/securesms/backup/v2/database/InAppPaymentTableBackupExtensions.kt rename to app/src/main/java/org/thoughtcrime/securesms/backup/v2/database/InAppPaymentTableArchiveExtensions.kt diff --git a/app/src/main/java/org/thoughtcrime/securesms/backup/v2/database/MessageTableBackupExtensions.kt b/app/src/main/java/org/thoughtcrime/securesms/backup/v2/database/MessageTableArchiveExtensions.kt similarity index 88% rename from app/src/main/java/org/thoughtcrime/securesms/backup/v2/database/MessageTableBackupExtensions.kt rename to app/src/main/java/org/thoughtcrime/securesms/backup/v2/database/MessageTableArchiveExtensions.kt index f5e4656ab6..7aa2e6fe0d 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/backup/v2/database/MessageTableBackupExtensions.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/backup/v2/database/MessageTableArchiveExtensions.kt @@ -8,7 +8,8 @@ package org.thoughtcrime.securesms.backup.v2.database import org.signal.core.util.SqlUtil import org.signal.core.util.select import org.thoughtcrime.securesms.backup.v2.ImportState -import org.thoughtcrime.securesms.backup.v2.exporters.ChatItemArchiveExportIterator +import org.thoughtcrime.securesms.backup.v2.exporters.ChatItemArchiveExporter +import org.thoughtcrime.securesms.backup.v2.importer.ChatItemArchiveImporter import org.thoughtcrime.securesms.database.MessageTable import org.thoughtcrime.securesms.database.MessageTypes import org.thoughtcrime.securesms.database.SignalDatabase @@ -16,7 +17,7 @@ import java.util.concurrent.TimeUnit private const val COLUMN_BASE_TYPE = "base_type" -fun MessageTable.getMessagesForBackup(db: SignalDatabase, backupTime: Long, mediaBackupEnabled: Boolean): ChatItemArchiveExportIterator { +fun MessageTable.getMessagesForBackup(db: SignalDatabase, backupTime: Long, mediaBackupEnabled: Boolean): ChatItemArchiveExporter { val cursor = readableDatabase .select( MessageTable.ID, @@ -66,11 +67,11 @@ fun MessageTable.getMessagesForBackup(db: SignalDatabase, backupTime: Long, medi .orderBy("${MessageTable.DATE_RECEIVED} ASC") .run() - return ChatItemArchiveExportIterator(db, cursor, 100, mediaBackupEnabled) + return ChatItemArchiveExporter(db, cursor, 100, mediaBackupEnabled) } -fun MessageTable.createChatItemInserter(importState: ImportState): ChatItemImportInserter { - return ChatItemImportInserter(writableDatabase, importState, 100) +fun MessageTable.createChatItemInserter(importState: ImportState): ChatItemArchiveImporter { + return ChatItemArchiveImporter(writableDatabase, importState, 100) } fun MessageTable.clearAllDataForBackupRestore() { diff --git a/app/src/main/java/org/thoughtcrime/securesms/backup/v2/database/ReactionTableBackupExtensions.kt b/app/src/main/java/org/thoughtcrime/securesms/backup/v2/database/ReactionTableArchiveExtensions.kt similarity index 100% rename from app/src/main/java/org/thoughtcrime/securesms/backup/v2/database/ReactionTableBackupExtensions.kt rename to app/src/main/java/org/thoughtcrime/securesms/backup/v2/database/ReactionTableArchiveExtensions.kt diff --git a/app/src/main/java/org/thoughtcrime/securesms/backup/v2/database/RecipientTableArchiveExtensions.kt b/app/src/main/java/org/thoughtcrime/securesms/backup/v2/database/RecipientTableArchiveExtensions.kt new file mode 100644 index 0000000000..4928136022 --- /dev/null +++ b/app/src/main/java/org/thoughtcrime/securesms/backup/v2/database/RecipientTableArchiveExtensions.kt @@ -0,0 +1,142 @@ +/* + * Copyright 2023 Signal Messenger, LLC + * SPDX-License-Identifier: AGPL-3.0-only + */ + +package org.thoughtcrime.securesms.backup.v2.database + +import android.content.ContentValues +import org.signal.core.util.Base64 +import org.signal.core.util.SqlUtil +import org.signal.core.util.deleteAll +import org.signal.core.util.logging.Log +import org.signal.core.util.nullIfBlank +import org.signal.core.util.select +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.RecipientTable +import org.thoughtcrime.securesms.database.model.databaseprotos.RecipientExtras +import org.thoughtcrime.securesms.dependencies.AppDependencies +import org.thoughtcrime.securesms.keyvalue.SignalStore +import org.thoughtcrime.securesms.profiles.ProfileName +import org.thoughtcrime.securesms.recipients.RecipientId + +/** + * Fetches all individual contacts for backups and returns the result as an iterator. + * It's important to note that the iterator still needs to be closed after it's used. + * It's recommended to use `.use` or a try-with-resources pattern. + */ +fun RecipientTable.getContactsForBackup(selfId: Long): ContactArchiveExporter { + val cursor = readableDatabase + .select( + RecipientTable.ID, + RecipientTable.ACI_COLUMN, + RecipientTable.PNI_COLUMN, + RecipientTable.USERNAME, + RecipientTable.E164, + RecipientTable.BLOCKED, + RecipientTable.HIDDEN, + RecipientTable.REGISTERED, + RecipientTable.UNREGISTERED_TIMESTAMP, + RecipientTable.PROFILE_KEY, + RecipientTable.PROFILE_SHARING, + RecipientTable.PROFILE_GIVEN_NAME, + RecipientTable.PROFILE_FAMILY_NAME, + RecipientTable.PROFILE_JOINED_NAME, + RecipientTable.MUTE_UNTIL, + RecipientTable.CHAT_COLORS, + RecipientTable.CUSTOM_CHAT_COLORS_ID, + RecipientTable.EXTRAS + ) + .from(RecipientTable.TABLE_NAME) + .where( + """ + ${RecipientTable.TYPE} = ? AND ( + ${RecipientTable.ACI_COLUMN} NOT NULL OR + ${RecipientTable.PNI_COLUMN} NOT NULL OR + ${RecipientTable.E164} NOT NULL + ) + """, + RecipientTable.RecipientType.INDIVIDUAL.id + ) + .run() + + return ContactArchiveExporter(cursor, selfId) +} + +fun RecipientTable.getGroupsForBackup(): GroupArchiveExporter { + val cursor = readableDatabase + .select( + "${RecipientTable.TABLE_NAME}.${RecipientTable.ID}", + "${RecipientTable.TABLE_NAME}.${RecipientTable.BLOCKED}", + "${RecipientTable.TABLE_NAME}.${RecipientTable.PROFILE_SHARING}", + "${RecipientTable.TABLE_NAME}.${RecipientTable.MUTE_UNTIL}", + "${RecipientTable.TABLE_NAME}.${RecipientTable.EXTRAS}", + "${GroupTable.TABLE_NAME}.${GroupTable.V2_MASTER_KEY}", + "${GroupTable.TABLE_NAME}.${GroupTable.SHOW_AS_STORY_STATE}", + "${GroupTable.TABLE_NAME}.${GroupTable.TITLE}", + "${GroupTable.TABLE_NAME}.${GroupTable.V2_DECRYPTED_GROUP}" + ) + .from( + """ + ${RecipientTable.TABLE_NAME} + INNER JOIN ${GroupTable.TABLE_NAME} ON ${RecipientTable.TABLE_NAME}.${RecipientTable.ID} = ${GroupTable.TABLE_NAME}.${GroupTable.RECIPIENT_ID} + """ + ) + .where("${GroupTable.TABLE_NAME}.${GroupTable.V2_MASTER_KEY} IS NOT NULL") + .run() + + return GroupArchiveExporter(cursor) +} + +/** + * Given [AccountData], this will insert the necessary data for the local user into the [RecipientTable]. + */ +fun RecipientTable.restoreSelfFromBackup(accountData: AccountData, selfId: RecipientId) { + val values = ContentValues().apply { + put(RecipientTable.PROFILE_GIVEN_NAME, accountData.givenName.nullIfBlank()) + put(RecipientTable.PROFILE_FAMILY_NAME, accountData.familyName.nullIfBlank()) + put(RecipientTable.PROFILE_JOINED_NAME, ProfileName.fromParts(accountData.givenName, accountData.familyName).toString().nullIfBlank()) + put(RecipientTable.PROFILE_AVATAR, accountData.avatarUrlPath.nullIfBlank()) + put(RecipientTable.REGISTERED, RecipientTable.RegisteredState.REGISTERED.id) + put(RecipientTable.PROFILE_SHARING, true) + put(RecipientTable.UNREGISTERED_TIMESTAMP, 0) + put(RecipientTable.EXTRAS, RecipientExtras().encode()) + + try { + put(RecipientTable.PROFILE_KEY, Base64.encodeWithPadding(accountData.profileKey.toByteArray()).nullIfBlank()) + } catch (e: InvalidInputException) { + Log.w(TAG, "Missing profile key during restore") + } + + put(RecipientTable.USERNAME, accountData.username) + } + + writableDatabase + .update(RecipientTable.TABLE_NAME) + .values(values) + .where("${RecipientTable.ID} = ?", selfId) + .run() +} + +fun RecipientTable.clearAllDataForBackupRestore() { + writableDatabase.deleteAll(RecipientTable.TABLE_NAME) + SqlUtil.resetAutoIncrementValue(writableDatabase, RecipientTable.TABLE_NAME) + + RecipientId.clearCache() + AppDependencies.recipientCache.clear() + AppDependencies.recipientCache.clearSelf() +} + +fun RecipientTable.restoreReleaseNotes(): RecipientId { + val releaseChannelId: RecipientId = insertReleaseChannelRecipient() + SignalStore.releaseChannel.setReleaseChannelRecipientId(releaseChannelId) + + setProfileName(releaseChannelId, ProfileName.asGiven("Signal")) + setMuted(releaseChannelId, Long.MAX_VALUE) + return releaseChannelId +} diff --git a/app/src/main/java/org/thoughtcrime/securesms/backup/v2/database/RecipientTableBackupExtensions.kt b/app/src/main/java/org/thoughtcrime/securesms/backup/v2/database/RecipientTableBackupExtensions.kt deleted file mode 100644 index b4a6bd7002..0000000000 --- a/app/src/main/java/org/thoughtcrime/securesms/backup/v2/database/RecipientTableBackupExtensions.kt +++ /dev/null @@ -1,336 +0,0 @@ -/* - * Copyright 2023 Signal Messenger, LLC - * SPDX-License-Identifier: AGPL-3.0-only - */ - -package org.thoughtcrime.securesms.backup.v2.database - -import android.content.ContentValues -import androidx.core.content.contentValuesOf -import org.signal.core.util.Base64 -import org.signal.core.util.SqlUtil -import org.signal.core.util.deleteAll -import org.signal.core.util.logging.Log -import org.signal.core.util.nullIfBlank -import org.signal.core.util.select -import org.signal.core.util.toInt -import org.signal.core.util.update -import org.signal.libsignal.zkgroup.InvalidInputException -import org.signal.libsignal.zkgroup.groups.GroupMasterKey -import org.signal.libsignal.zkgroup.groups.GroupSecretParams -import org.signal.storageservice.protos.groups.AccessControl -import org.signal.storageservice.protos.groups.Member -import org.signal.storageservice.protos.groups.local.DecryptedBannedMember -import org.signal.storageservice.protos.groups.local.DecryptedGroup -import org.signal.storageservice.protos.groups.local.DecryptedMember -import org.signal.storageservice.protos.groups.local.DecryptedPendingMember -import org.signal.storageservice.protos.groups.local.DecryptedRequestingMember -import org.signal.storageservice.protos.groups.local.DecryptedTimer -import org.signal.storageservice.protos.groups.local.EnabledState -import org.thoughtcrime.securesms.backup.v2.exporters.ContactArchiveExportIterator -import org.thoughtcrime.securesms.backup.v2.exporters.GroupArchiveExportIterator -import org.thoughtcrime.securesms.backup.v2.proto.AccountData -import org.thoughtcrime.securesms.backup.v2.proto.Contact -import org.thoughtcrime.securesms.backup.v2.proto.Group -import org.thoughtcrime.securesms.conversation.colors.AvatarColorHash -import org.thoughtcrime.securesms.database.GroupTable -import org.thoughtcrime.securesms.database.RecipientTable -import org.thoughtcrime.securesms.database.SignalDatabase -import org.thoughtcrime.securesms.database.model.databaseprotos.RecipientExtras -import org.thoughtcrime.securesms.dependencies.AppDependencies -import org.thoughtcrime.securesms.groups.GroupId -import org.thoughtcrime.securesms.groups.v2.processing.GroupsV2StateProcessor -import org.thoughtcrime.securesms.keyvalue.SignalStore -import org.thoughtcrime.securesms.phonenumbers.PhoneNumberFormatter -import org.thoughtcrime.securesms.profiles.ProfileName -import org.thoughtcrime.securesms.recipients.Recipient -import org.thoughtcrime.securesms.recipients.RecipientId -import org.thoughtcrime.securesms.storage.StorageSyncHelper -import org.whispersystems.signalservice.api.groupsv2.GroupsV2Operations -import org.whispersystems.signalservice.api.push.ServiceId -import org.whispersystems.signalservice.api.push.ServiceId.ACI -import org.whispersystems.signalservice.api.push.ServiceId.PNI - -private typealias BackupRecipient = org.thoughtcrime.securesms.backup.v2.proto.Recipient - -/** - * Fetches all individual contacts for backups and returns the result as an iterator. - * It's important to note that the iterator still needs to be closed after it's used. - * It's recommended to use `.use` or a try-with-resources pattern. - */ -fun RecipientTable.getContactsForBackup(selfId: Long): ContactArchiveExportIterator { - val cursor = readableDatabase - .select( - RecipientTable.ID, - RecipientTable.ACI_COLUMN, - RecipientTable.PNI_COLUMN, - RecipientTable.USERNAME, - RecipientTable.E164, - RecipientTable.BLOCKED, - RecipientTable.HIDDEN, - RecipientTable.REGISTERED, - RecipientTable.UNREGISTERED_TIMESTAMP, - RecipientTable.PROFILE_KEY, - RecipientTable.PROFILE_SHARING, - RecipientTable.PROFILE_GIVEN_NAME, - RecipientTable.PROFILE_FAMILY_NAME, - RecipientTable.PROFILE_JOINED_NAME, - RecipientTable.MUTE_UNTIL, - RecipientTable.CHAT_COLORS, - RecipientTable.CUSTOM_CHAT_COLORS_ID, - RecipientTable.EXTRAS - ) - .from(RecipientTable.TABLE_NAME) - .where( - """ - ${RecipientTable.TYPE} = ? AND ( - ${RecipientTable.ACI_COLUMN} NOT NULL OR - ${RecipientTable.PNI_COLUMN} NOT NULL OR - ${RecipientTable.E164} NOT NULL - ) - """, - RecipientTable.RecipientType.INDIVIDUAL.id - ) - .run() - - return ContactArchiveExportIterator(cursor, selfId) -} - -fun RecipientTable.getGroupsForBackup(): GroupArchiveExportIterator { - val cursor = readableDatabase - .select( - "${RecipientTable.TABLE_NAME}.${RecipientTable.ID}", - "${RecipientTable.TABLE_NAME}.${RecipientTable.BLOCKED}", - "${RecipientTable.TABLE_NAME}.${RecipientTable.PROFILE_SHARING}", - "${RecipientTable.TABLE_NAME}.${RecipientTable.MUTE_UNTIL}", - "${RecipientTable.TABLE_NAME}.${RecipientTable.EXTRAS}", - "${GroupTable.TABLE_NAME}.${GroupTable.V2_MASTER_KEY}", - "${GroupTable.TABLE_NAME}.${GroupTable.SHOW_AS_STORY_STATE}", - "${GroupTable.TABLE_NAME}.${GroupTable.TITLE}", - "${GroupTable.TABLE_NAME}.${GroupTable.V2_DECRYPTED_GROUP}" - ) - .from( - """ - ${RecipientTable.TABLE_NAME} - INNER JOIN ${GroupTable.TABLE_NAME} ON ${RecipientTable.TABLE_NAME}.${RecipientTable.ID} = ${GroupTable.TABLE_NAME}.${GroupTable.RECIPIENT_ID} - """ - ) - .where("${GroupTable.TABLE_NAME}.${GroupTable.V2_MASTER_KEY} IS NOT NULL") - .run() - - return GroupArchiveExportIterator(cursor) -} - -/** - * Given [AccountData], this will insert the necessary data for the local user into the [RecipientTable]. - */ -fun RecipientTable.restoreSelfFromBackup(accountData: AccountData, selfId: RecipientId) { - val values = ContentValues().apply { - put(RecipientTable.PROFILE_GIVEN_NAME, accountData.givenName.nullIfBlank()) - put(RecipientTable.PROFILE_FAMILY_NAME, accountData.familyName.nullIfBlank()) - put(RecipientTable.PROFILE_JOINED_NAME, ProfileName.fromParts(accountData.givenName, accountData.familyName).toString().nullIfBlank()) - put(RecipientTable.PROFILE_AVATAR, accountData.avatarUrlPath.nullIfBlank()) - put(RecipientTable.REGISTERED, RecipientTable.RegisteredState.REGISTERED.id) - put(RecipientTable.PROFILE_SHARING, true) - put(RecipientTable.UNREGISTERED_TIMESTAMP, 0) - put(RecipientTable.EXTRAS, RecipientExtras().encode()) - - try { - put(RecipientTable.PROFILE_KEY, Base64.encodeWithPadding(accountData.profileKey.toByteArray()).nullIfBlank()) - } catch (e: InvalidInputException) { - Log.w(TAG, "Missing profile key during restore") - } - - put(RecipientTable.USERNAME, accountData.username) - } - - writableDatabase - .update(RecipientTable.TABLE_NAME) - .values(values) - .where("${RecipientTable.ID} = ?", selfId) - .run() -} - -fun RecipientTable.clearAllDataForBackupRestore() { - writableDatabase.deleteAll(RecipientTable.TABLE_NAME) - SqlUtil.resetAutoIncrementValue(writableDatabase, RecipientTable.TABLE_NAME) - - RecipientId.clearCache() - AppDependencies.recipientCache.clear() - AppDependencies.recipientCache.clearSelf() -} - -fun RecipientTable.restoreContactFromBackup(contact: Contact): RecipientId { - val id = getAndPossiblyMergePnpVerified( - aci = ACI.parseOrNull(contact.aci?.toByteArray()), - pni = PNI.parseOrNull(contact.pni?.toByteArray()), - e164 = contact.formattedE164 - ) - - val profileKey = contact.profileKey?.toByteArray() - val values = contentValuesOf( - RecipientTable.BLOCKED to contact.blocked, - RecipientTable.HIDDEN to contact.visibility.toLocal().serialize(), - RecipientTable.TYPE to RecipientTable.RecipientType.INDIVIDUAL.id, - RecipientTable.PROFILE_FAMILY_NAME to contact.profileFamilyName, - RecipientTable.PROFILE_GIVEN_NAME to contact.profileGivenName, - RecipientTable.PROFILE_JOINED_NAME to ProfileName.fromParts(contact.profileGivenName, contact.profileFamilyName).toString(), - RecipientTable.PROFILE_KEY to if (profileKey == null) null else Base64.encodeWithPadding(profileKey), - RecipientTable.PROFILE_SHARING to contact.profileSharing.toInt(), - RecipientTable.USERNAME to contact.username, - RecipientTable.EXTRAS to contact.toLocalExtras().encode() - ) - - 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) - values.put(RecipientTable.REGISTERED, RecipientTable.RegisteredState.NOT_REGISTERED.id) - } - - writableDatabase - .update(RecipientTable.TABLE_NAME) - .values(values) - .where("${RecipientTable.ID} = ?", id) - .run() - - return id -} - -fun RecipientTable.restoreReleaseNotes(): RecipientId { - val releaseChannelId: RecipientId = insertReleaseChannelRecipient() - SignalStore.releaseChannel.setReleaseChannelRecipientId(releaseChannelId) - - setProfileName(releaseChannelId, ProfileName.asGiven("Signal")) - setMuted(releaseChannelId, Long.MAX_VALUE) - return releaseChannelId -} - -fun RecipientTable.restoreGroupFromBackup(group: Group): RecipientId { - val masterKey = GroupMasterKey(group.masterKey.toByteArray()) - val groupId = GroupId.v2(masterKey) - - val operations = AppDependencies.groupsV2Operations.forGroup(GroupSecretParams.deriveFromMasterKey(masterKey)) - val decryptedState = if (group.snapshot == null) { - DecryptedGroup(revision = GroupsV2StateProcessor.RESTORE_PLACEHOLDER_REVISION) - } else { - group.snapshot.toLocal(operations) - } - - val values = ContentValues().apply { - put(RecipientTable.GROUP_ID, groupId.toString()) - put(RecipientTable.AVATAR_COLOR, AvatarColorHash.forGroupId(groupId).serialize()) - put(RecipientTable.PROFILE_SHARING, group.whitelisted) - put(RecipientTable.TYPE, RecipientTable.RecipientType.GV2.id) - put(RecipientTable.STORAGE_SERVICE_ID, Base64.encodeWithPadding(StorageSyncHelper.generateKey())) - if (group.hideStory) { - val extras = RecipientExtras.Builder().hideStory(true).build() - put(RecipientTable.EXTRAS, extras.encode()) - } - } - - val recipientId = writableDatabase.insert(RecipientTable.TABLE_NAME, null, values) - val restoredId = SignalDatabase.groups.create(masterKey, decryptedState, groupSendEndorsements = null) - if (restoredId != null) { - SignalDatabase.groups.setShowAsStoryState(restoredId, group.storySendMode.toLocal()) - } - - return RecipientId.from(recipientId) -} - -private fun Group.StorySendMode.toLocal(): GroupTable.ShowAsStoryState { - return when (this) { - Group.StorySendMode.ENABLED -> GroupTable.ShowAsStoryState.ALWAYS - Group.StorySendMode.DISABLED -> GroupTable.ShowAsStoryState.NEVER - Group.StorySendMode.DEFAULT -> GroupTable.ShowAsStoryState.IF_ACTIVE - } -} - -private fun Group.MemberPendingProfileKey.toLocal(operations: GroupsV2Operations.GroupOperations): DecryptedPendingMember { - return DecryptedPendingMember( - serviceIdBytes = member!!.userId, - role = member.role.toLocal(), - addedByAci = addedByUserId, - timestamp = timestamp, - serviceIdCipherText = operations.encryptServiceId(ServiceId.Companion.parseOrNull(member.userId)) - ) -} - -private fun Contact.Visibility.toLocal(): Recipient.HiddenState { - return when (this) { - Contact.Visibility.VISIBLE -> Recipient.HiddenState.NOT_HIDDEN - Contact.Visibility.HIDDEN -> Recipient.HiddenState.HIDDEN - Contact.Visibility.HIDDEN_MESSAGE_REQUEST -> Recipient.HiddenState.HIDDEN_MESSAGE_REQUEST - } -} - -private fun Group.AccessControl.AccessRequired.toLocal(): AccessControl.AccessRequired { - return when (this) { - Group.AccessControl.AccessRequired.UNKNOWN -> AccessControl.AccessRequired.UNKNOWN - Group.AccessControl.AccessRequired.ANY -> AccessControl.AccessRequired.ANY - Group.AccessControl.AccessRequired.MEMBER -> AccessControl.AccessRequired.MEMBER - Group.AccessControl.AccessRequired.ADMINISTRATOR -> AccessControl.AccessRequired.ADMINISTRATOR - Group.AccessControl.AccessRequired.UNSATISFIABLE -> AccessControl.AccessRequired.UNSATISFIABLE - } -} - -private fun Group.AccessControl.toLocal(): AccessControl { - return AccessControl(members = this.members.toLocal(), attributes = this.attributes.toLocal(), addFromInviteLink = this.addFromInviteLink.toLocal()) -} - -private fun Group.Member.Role.toLocal(): Member.Role { - return when (this) { - Group.Member.Role.UNKNOWN -> Member.Role.UNKNOWN - Group.Member.Role.DEFAULT -> Member.Role.DEFAULT - Group.Member.Role.ADMINISTRATOR -> Member.Role.ADMINISTRATOR - } -} - -private fun Group.Member.toLocal(): DecryptedMember { - return DecryptedMember(aciBytes = userId, role = role.toLocal(), joinedAtRevision = joinedAtVersion) -} - -private fun Group.MemberPendingAdminApproval.toLocal(): DecryptedRequestingMember { - return DecryptedRequestingMember( - aciBytes = this.userId, - timestamp = this.timestamp - ) -} - -private fun Group.MemberBanned.toLocal(): DecryptedBannedMember { - return DecryptedBannedMember( - serviceIdBytes = this.userId, - timestamp = this.timestamp - ) -} - -private fun Group.GroupSnapshot.toLocal(operations: GroupsV2Operations.GroupOperations): DecryptedGroup { - return DecryptedGroup( - title = this.title?.title ?: "", - avatar = this.avatarUrl, - disappearingMessagesTimer = DecryptedTimer(duration = this.disappearingMessagesTimer?.disappearingMessagesDuration ?: 0), - accessControl = this.accessControl?.toLocal(), - revision = this.version, - members = this.members.map { member -> member.toLocal() }, - pendingMembers = this.membersPendingProfileKey.map { pending -> pending.toLocal(operations) }, - requestingMembers = this.membersPendingAdminApproval.map { requesting -> requesting.toLocal() }, - inviteLinkPassword = this.inviteLinkPassword, - description = this.description?.descriptionText ?: "", - isAnnouncementGroup = if (this.announcements_only) EnabledState.ENABLED else EnabledState.DISABLED, - bannedMembers = this.members_banned.map { it.toLocal() } - ) -} - -private fun Contact.toLocalExtras(): RecipientExtras { - return RecipientExtras( - hideStory = this.hideStory - ) -} - -private val Contact.formattedE164: String? - get() { - return e164?.let { - PhoneNumberFormatter.get(AppDependencies.application).format(e164.toString()) - } - } diff --git a/app/src/main/java/org/thoughtcrime/securesms/backup/v2/database/StickerTableBackupExtensions.kt b/app/src/main/java/org/thoughtcrime/securesms/backup/v2/database/StickerTableArchiveExtensions.kt similarity index 100% rename from app/src/main/java/org/thoughtcrime/securesms/backup/v2/database/StickerTableBackupExtensions.kt rename to app/src/main/java/org/thoughtcrime/securesms/backup/v2/database/StickerTableArchiveExtensions.kt diff --git a/app/src/main/java/org/thoughtcrime/securesms/backup/v2/database/ThreadTableArchiveExtensions.kt b/app/src/main/java/org/thoughtcrime/securesms/backup/v2/database/ThreadTableArchiveExtensions.kt new file mode 100644 index 0000000000..6874572bb1 --- /dev/null +++ b/app/src/main/java/org/thoughtcrime/securesms/backup/v2/database/ThreadTableArchiveExtensions.kt @@ -0,0 +1,43 @@ +/* + * Copyright 2023 Signal Messenger, LLC + * SPDX-License-Identifier: AGPL-3.0-only + */ + +package org.thoughtcrime.securesms.backup.v2.database + +import org.signal.core.util.SqlUtil +import org.thoughtcrime.securesms.backup.v2.exporters.ChatArchiveExporter +import org.thoughtcrime.securesms.database.RecipientTable +import org.thoughtcrime.securesms.database.SignalDatabase +import org.thoughtcrime.securesms.database.ThreadTable + +fun ThreadTable.getThreadsForBackup(db: SignalDatabase): ChatArchiveExporter { + //language=sql + val query = """ + SELECT + ${ThreadTable.TABLE_NAME}.${ThreadTable.ID}, + ${ThreadTable.RECIPIENT_ID}, + ${ThreadTable.PINNED}, + ${ThreadTable.READ}, + ${ThreadTable.ARCHIVED}, + ${RecipientTable.TABLE_NAME}.${RecipientTable.MESSAGE_EXPIRATION_TIME}, + ${RecipientTable.TABLE_NAME}.${RecipientTable.MESSAGE_EXPIRATION_TIME_VERSION}, + ${RecipientTable.TABLE_NAME}.${RecipientTable.MUTE_UNTIL}, + ${RecipientTable.TABLE_NAME}.${RecipientTable.MENTION_SETTING}, + ${RecipientTable.TABLE_NAME}.${RecipientTable.CHAT_COLORS}, + ${RecipientTable.TABLE_NAME}.${RecipientTable.CUSTOM_CHAT_COLORS_ID}, + ${RecipientTable.TABLE_NAME}.${RecipientTable.WALLPAPER} + FROM ${ThreadTable.TABLE_NAME} + LEFT OUTER JOIN ${RecipientTable.TABLE_NAME} ON ${ThreadTable.TABLE_NAME}.${ThreadTable.RECIPIENT_ID} = ${RecipientTable.TABLE_NAME}.${RecipientTable.ID} + WHERE ${ThreadTable.ACTIVE} = 1 + """ + val cursor = readableDatabase.query(query) + + return ChatArchiveExporter(cursor, db) +} + +fun ThreadTable.clearAllDataForBackupRestore() { + writableDatabase.delete(ThreadTable.TABLE_NAME, null, null) + SqlUtil.resetAutoIncrementValue(writableDatabase, ThreadTable.TABLE_NAME) + clearCache() +} diff --git a/app/src/main/java/org/thoughtcrime/securesms/backup/v2/database/ThreadTableBackupExtensions.kt b/app/src/main/java/org/thoughtcrime/securesms/backup/v2/database/ThreadTableBackupExtensions.kt deleted file mode 100644 index 6745697556..0000000000 --- a/app/src/main/java/org/thoughtcrime/securesms/backup/v2/database/ThreadTableBackupExtensions.kt +++ /dev/null @@ -1,100 +0,0 @@ -/* - * Copyright 2023 Signal Messenger, LLC - * SPDX-License-Identifier: AGPL-3.0-only - */ - -package org.thoughtcrime.securesms.backup.v2.database - -import androidx.core.content.contentValuesOf -import org.signal.core.util.SqlUtil -import org.signal.core.util.insertInto -import org.signal.core.util.logging.Log -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.exporters.ChatArchiveExportIterator -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 -import org.thoughtcrime.securesms.conversation.colors.ChatColors -import org.thoughtcrime.securesms.database.RecipientTable -import org.thoughtcrime.securesms.database.SignalDatabase -import org.thoughtcrime.securesms.database.ThreadTable -import org.thoughtcrime.securesms.recipients.RecipientId -import org.thoughtcrime.securesms.wallpaper.UriChatWallpaper - -private val TAG = Log.tag(ThreadTable::class.java) - -fun ThreadTable.getThreadsForBackup(db: SignalDatabase): ChatArchiveExportIterator { - //language=sql - val query = """ - SELECT - ${ThreadTable.TABLE_NAME}.${ThreadTable.ID}, - ${ThreadTable.RECIPIENT_ID}, - ${ThreadTable.PINNED}, - ${ThreadTable.READ}, - ${ThreadTable.ARCHIVED}, - ${RecipientTable.TABLE_NAME}.${RecipientTable.MESSAGE_EXPIRATION_TIME}, - ${RecipientTable.TABLE_NAME}.${RecipientTable.MESSAGE_EXPIRATION_TIME_VERSION}, - ${RecipientTable.TABLE_NAME}.${RecipientTable.MUTE_UNTIL}, - ${RecipientTable.TABLE_NAME}.${RecipientTable.MENTION_SETTING}, - ${RecipientTable.TABLE_NAME}.${RecipientTable.CHAT_COLORS}, - ${RecipientTable.TABLE_NAME}.${RecipientTable.CUSTOM_CHAT_COLORS_ID}, - ${RecipientTable.TABLE_NAME}.${RecipientTable.WALLPAPER} - FROM ${ThreadTable.TABLE_NAME} - LEFT OUTER JOIN ${RecipientTable.TABLE_NAME} ON ${ThreadTable.TABLE_NAME}.${ThreadTable.RECIPIENT_ID} = ${RecipientTable.TABLE_NAME}.${RecipientTable.ID} - WHERE ${ThreadTable.ACTIVE} = 1 - """ - val cursor = readableDatabase.query(query) - - return ChatArchiveExportIterator(cursor, db) -} - -fun ThreadTable.clearAllDataForBackupRestore() { - writableDatabase.delete(ThreadTable.TABLE_NAME, null, null) - SqlUtil.resetAutoIncrementValue(writableDatabase, ThreadTable.TABLE_NAME) - clearCache() -} - -fun ThreadTable.restoreFromBackup(chat: Chat, recipientId: RecipientId, importState: ImportState): Long { - val chatColor = chat.style?.toLocal(importState) - - val wallpaperAttachmentId: AttachmentId? = chat.style?.wallpaperPhoto?.let { filePointer -> - filePointer.toLocalAttachment(importState)?.let { - SignalDatabase.attachments.restoreWallpaperAttachment(it) - } - } - - val chatWallpaper = chat.style?.parseChatWallpaper(wallpaperAttachmentId) - - val threadId = writableDatabase - .insertInto(ThreadTable.TABLE_NAME) - .values( - ThreadTable.RECIPIENT_ID to recipientId.serialize(), - ThreadTable.PINNED to chat.pinnedOrder, - ThreadTable.ARCHIVED to chat.archived.toInt(), - ThreadTable.READ to if (chat.markedUnread) ThreadTable.ReadStatus.FORCED_UNREAD.serialize() else ThreadTable.ReadStatus.READ.serialize(), - ThreadTable.ACTIVE to 1 - ) - .run() - - writableDatabase - .update( - RecipientTable.TABLE_NAME, - contentValuesOf( - 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.MESSAGE_EXPIRATION_TIME to chat.expirationTimerMs, - RecipientTable.MESSAGE_EXPIRATION_TIME_VERSION to chat.expireTimerVersion, - RecipientTable.CHAT_COLORS to chatColor?.serialize()?.encode(), - RecipientTable.CUSTOM_CHAT_COLORS_ID to (chatColor?.id ?: ChatColors.Id.NotSet).longValue, - RecipientTable.WALLPAPER_URI to if (chatWallpaper is UriChatWallpaper) chatWallpaper.uri.toString() else null, - RecipientTable.WALLPAPER to chatWallpaper?.serialize()?.encode() - ), - "${RecipientTable.ID} = ?", - SqlUtil.buildArgs(recipientId.toLong()) - ) - - return threadId -} diff --git a/app/src/main/java/org/thoughtcrime/securesms/backup/v2/exporters/AdHocCallArchiveExportIterator.kt b/app/src/main/java/org/thoughtcrime/securesms/backup/v2/exporters/AdHocCallArchiveExporter.kt similarity index 91% rename from app/src/main/java/org/thoughtcrime/securesms/backup/v2/exporters/AdHocCallArchiveExportIterator.kt rename to app/src/main/java/org/thoughtcrime/securesms/backup/v2/exporters/AdHocCallArchiveExporter.kt index ecd965514c..a946a3423d 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/backup/v2/exporters/AdHocCallArchiveExportIterator.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/backup/v2/exporters/AdHocCallArchiveExporter.kt @@ -15,7 +15,7 @@ import java.io.Closeable * Provides a nice iterable interface over a [RecipientTable] cursor, converting rows to [BackupRecipient]s. * Important: Because this is backed by a cursor, you must close it. It's recommended to use `.use()` or try-with-resources. */ -class AdHocCallArchiveExportIterator(private val cursor: Cursor) : Iterator, Closeable { +class AdHocCallArchiveExporter(private val cursor: Cursor) : Iterator, Closeable { override fun hasNext(): Boolean { return cursor.count > 0 && !cursor.isLast } diff --git a/app/src/main/java/org/thoughtcrime/securesms/backup/v2/exporters/CallLinkArchiveExportIterator.kt b/app/src/main/java/org/thoughtcrime/securesms/backup/v2/exporters/CallLinkArchiveExporter.kt similarity index 94% rename from app/src/main/java/org/thoughtcrime/securesms/backup/v2/exporters/CallLinkArchiveExportIterator.kt rename to app/src/main/java/org/thoughtcrime/securesms/backup/v2/exporters/CallLinkArchiveExporter.kt index ee948cebd5..90c0fb4da3 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/backup/v2/exporters/CallLinkArchiveExportIterator.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/backup/v2/exporters/CallLinkArchiveExporter.kt @@ -18,7 +18,7 @@ import java.io.Closeable * Provides a nice iterable interface over a [RecipientTable] cursor, converting rows to [BackupRecipient]s. * Important: Because this is backed by a cursor, you must close it. It's recommended to use `.use()` or try-with-resources. */ -class CallLinkArchiveExportIterator(private val cursor: Cursor) : Iterator, Closeable { +class CallLinkArchiveExporter(private val cursor: Cursor) : Iterator, Closeable { override fun hasNext(): Boolean { return cursor.count > 0 && !cursor.isLast } diff --git a/app/src/main/java/org/thoughtcrime/securesms/backup/v2/exporters/ChatArchiveExportIterator.kt b/app/src/main/java/org/thoughtcrime/securesms/backup/v2/exporters/ChatArchiveExporter.kt similarity index 95% rename from app/src/main/java/org/thoughtcrime/securesms/backup/v2/exporters/ChatArchiveExportIterator.kt rename to app/src/main/java/org/thoughtcrime/securesms/backup/v2/exporters/ChatArchiveExporter.kt index e4c13bb988..4c4bbc82da 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/backup/v2/exporters/ChatArchiveExportIterator.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/backup/v2/exporters/ChatArchiveExporter.kt @@ -21,7 +21,7 @@ import org.thoughtcrime.securesms.database.model.databaseprotos.ChatColor import org.thoughtcrime.securesms.database.model.databaseprotos.Wallpaper import java.io.Closeable -class ChatArchiveExportIterator(private val cursor: Cursor, private val db: SignalDatabase) : Iterator, Closeable { +class ChatArchiveExporter(private val cursor: Cursor, private val db: SignalDatabase) : Iterator, Closeable { override fun hasNext(): Boolean { return cursor.count > 0 && !cursor.isLast } diff --git a/app/src/main/java/org/thoughtcrime/securesms/backup/v2/exporters/ChatItemArchiveExportIterator.kt b/app/src/main/java/org/thoughtcrime/securesms/backup/v2/exporters/ChatItemArchiveExporter.kt similarity index 99% rename from app/src/main/java/org/thoughtcrime/securesms/backup/v2/exporters/ChatItemArchiveExportIterator.kt rename to app/src/main/java/org/thoughtcrime/securesms/backup/v2/exporters/ChatItemArchiveExporter.kt index e2271d965f..1763385b76 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/backup/v2/exporters/ChatItemArchiveExportIterator.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/backup/v2/exporters/ChatItemArchiveExporter.kt @@ -85,7 +85,7 @@ import kotlin.jvm.optionals.getOrNull import org.thoughtcrime.securesms.backup.v2.proto.BodyRange as BackupBodyRange import org.thoughtcrime.securesms.backup.v2.proto.GiftBadge as BackupGiftBadge -private val TAG = Log.tag(ChatItemArchiveExportIterator::class.java) +private val TAG = Log.tag(ChatItemArchiveExporter::class.java) private const val COLUMN_BASE_TYPE = "base_type" /** @@ -95,7 +95,7 @@ private const val COLUMN_BASE_TYPE = "base_type" * * All of this complexity is hidden from the user -- they just get a normal iterator interface. */ -class ChatItemArchiveExportIterator( +class ChatItemArchiveExporter( private val db: SignalDatabase, private val cursor: Cursor, private val batchSize: Int, diff --git a/app/src/main/java/org/thoughtcrime/securesms/backup/v2/exporters/ContactArchiveExportIterator.kt b/app/src/main/java/org/thoughtcrime/securesms/backup/v2/exporters/ContactArchiveExporter.kt similarity index 96% rename from app/src/main/java/org/thoughtcrime/securesms/backup/v2/exporters/ContactArchiveExportIterator.kt rename to app/src/main/java/org/thoughtcrime/securesms/backup/v2/exporters/ContactArchiveExporter.kt index 051bb19e88..bec86d97e0 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/backup/v2/exporters/ContactArchiveExportIterator.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/backup/v2/exporters/ContactArchiveExporter.kt @@ -26,7 +26,7 @@ import java.io.Closeable * Provides a nice iterable interface over a [RecipientTable] cursor, converting rows to [ArchiveRecipient]s. * Important: Because this is backed by a cursor, you must close it. It's recommended to use `.use()` or try-with-resources. */ -class ContactArchiveExportIterator(private val cursor: Cursor, private val selfId: Long) : Iterator, Closeable { +class ContactArchiveExporter(private val cursor: Cursor, private val selfId: Long) : Iterator, Closeable { override fun hasNext(): Boolean { return cursor.count > 0 && !cursor.isLast } diff --git a/app/src/main/java/org/thoughtcrime/securesms/backup/v2/exporters/DistributionListArchiveExportIterator.kt b/app/src/main/java/org/thoughtcrime/securesms/backup/v2/exporters/DistributionListArchiveExporter.kt similarity index 98% rename from app/src/main/java/org/thoughtcrime/securesms/backup/v2/exporters/DistributionListArchiveExportIterator.kt rename to app/src/main/java/org/thoughtcrime/securesms/backup/v2/exporters/DistributionListArchiveExporter.kt index c33308ccad..4e29525946 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/backup/v2/exporters/DistributionListArchiveExportIterator.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/backup/v2/exporters/DistributionListArchiveExporter.kt @@ -24,7 +24,7 @@ import org.whispersystems.signalservice.api.push.DistributionId import org.whispersystems.signalservice.api.util.toByteArray import java.io.Closeable -class DistributionListArchiveExportIterator( +class DistributionListArchiveExporter( private val cursor: Cursor, private val distributionListTables: DistributionListTables ) : Iterator, Closeable { diff --git a/app/src/main/java/org/thoughtcrime/securesms/backup/v2/exporters/GroupArchiveExportIterator.kt b/app/src/main/java/org/thoughtcrime/securesms/backup/v2/exporters/GroupArchiveExporter.kt similarity index 98% rename from app/src/main/java/org/thoughtcrime/securesms/backup/v2/exporters/GroupArchiveExportIterator.kt rename to app/src/main/java/org/thoughtcrime/securesms/backup/v2/exporters/GroupArchiveExporter.kt index 30d930c9f2..889c555f69 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/backup/v2/exporters/GroupArchiveExportIterator.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/backup/v2/exporters/GroupArchiveExporter.kt @@ -33,7 +33,7 @@ import java.io.Closeable * Provides a nice iterable interface over a [RecipientTable] cursor, converting rows to [ArchiveRecipient]s. * Important: Because this is backed by a cursor, you must close it. It's recommended to use `.use()` or try-with-resources. */ -class GroupArchiveExportIterator(private val cursor: Cursor) : Iterator, Closeable { +class GroupArchiveExporter(private val cursor: Cursor) : Iterator, Closeable { override fun hasNext(): Boolean { return cursor.count > 0 && !cursor.isLast diff --git a/app/src/main/java/org/thoughtcrime/securesms/backup/v2/importer/AdHodCallArchiveImporter.kt b/app/src/main/java/org/thoughtcrime/securesms/backup/v2/importer/AdHodCallArchiveImporter.kt new file mode 100644 index 0000000000..22b7dc1c40 --- /dev/null +++ b/app/src/main/java/org/thoughtcrime/securesms/backup/v2/importer/AdHodCallArchiveImporter.kt @@ -0,0 +1,36 @@ +/* + * Copyright 2024 Signal Messenger, LLC + * SPDX-License-Identifier: AGPL-3.0-only + */ + +package org.thoughtcrime.securesms.backup.v2.importer + +import org.signal.core.util.insertInto +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 + +/** + * Handles the importing of [AdHocCall] models into the local database. + */ +object AdHodCallArchiveImporter { + fun import(call: AdHocCall, importState: ImportState) { + val event = when (call.state) { + AdHocCall.State.GENERIC -> CallTable.Event.GENERIC_GROUP_CALL + AdHocCall.State.UNKNOWN_STATE -> CallTable.Event.GENERIC_GROUP_CALL + } + + SignalDatabase.writableDatabase + .insertInto(CallTable.TABLE_NAME) + .values( + CallTable.CALL_ID to call.callId, + CallTable.PEER to importState.remoteToLocalRecipientId[call.recipientId]!!.serialize(), + CallTable.TYPE to CallTable.Type.serialize(CallTable.Type.AD_HOC_CALL), + CallTable.DIRECTION to CallTable.Direction.serialize(CallTable.Direction.OUTGOING), + CallTable.EVENT to CallTable.Event.serialize(event), + CallTable.TIMESTAMP to call.callTimestamp + ) + .run() + } +} diff --git a/app/src/main/java/org/thoughtcrime/securesms/backup/v2/importer/CallLinkArchiveImporter.kt b/app/src/main/java/org/thoughtcrime/securesms/backup/v2/importer/CallLinkArchiveImporter.kt new file mode 100644 index 0000000000..c1d7d1bad5 --- /dev/null +++ b/app/src/main/java/org/thoughtcrime/securesms/backup/v2/importer/CallLinkArchiveImporter.kt @@ -0,0 +1,53 @@ +/* + * Copyright 2024 Signal Messenger, LLC + * SPDX-License-Identifier: AGPL-3.0-only + */ + +package org.thoughtcrime.securesms.backup.v2.importer + +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 +import org.thoughtcrime.securesms.service.webrtc.links.CallLinkCredentials +import org.thoughtcrime.securesms.service.webrtc.links.CallLinkRoomId +import org.thoughtcrime.securesms.service.webrtc.links.SignalCallLinkState +import java.time.Instant + +/** + * Handles the importing of [ArchiveCallLink] models into the local database. + */ +object CallLinkArchiveImporter { + fun import(callLink: ArchiveCallLink): RecipientId? { + val rootKey: CallLinkRootKey + try { + rootKey = CallLinkRootKey(callLink.rootKey.toByteArray()) + } catch (e: Exception) { + return null + } + return SignalDatabase.callLinks.insertCallLink( + CallLinkTable.CallLink( + recipientId = RecipientId.UNKNOWN, + roomId = CallLinkRoomId.fromCallLinkRootKey(rootKey), + credentials = CallLinkCredentials(callLink.rootKey.toByteArray(), callLink.adminKey?.toByteArray()), + state = SignalCallLinkState( + name = callLink.name, + restrictions = callLink.restrictions.toLocal(), + expiration = Instant.ofEpochMilli(callLink.expirationMs) + ), + deletionTimestamp = 0L + ) + ) + } +} + +private fun CallLink.Restrictions.toLocal(): CallLinkState.Restrictions { + return when (this) { + CallLink.Restrictions.ADMIN_APPROVAL -> CallLinkState.Restrictions.ADMIN_APPROVAL + CallLink.Restrictions.NONE -> CallLinkState.Restrictions.NONE + CallLink.Restrictions.UNKNOWN -> CallLinkState.Restrictions.UNKNOWN + } +} diff --git a/app/src/main/java/org/thoughtcrime/securesms/backup/v2/importer/ChatArchiveImporter.kt b/app/src/main/java/org/thoughtcrime/securesms/backup/v2/importer/ChatArchiveImporter.kt new file mode 100644 index 0000000000..ae59e47d37 --- /dev/null +++ b/app/src/main/java/org/thoughtcrime/securesms/backup/v2/importer/ChatArchiveImporter.kt @@ -0,0 +1,71 @@ +/* + * Copyright 2024 Signal Messenger, LLC + * SPDX-License-Identifier: AGPL-3.0-only + */ + +package org.thoughtcrime.securesms.backup.v2.importer + +import androidx.core.content.contentValuesOf +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 +import org.thoughtcrime.securesms.conversation.colors.ChatColors +import org.thoughtcrime.securesms.database.RecipientTable +import org.thoughtcrime.securesms.database.SignalDatabase +import org.thoughtcrime.securesms.database.ThreadTable +import org.thoughtcrime.securesms.recipients.RecipientId +import org.thoughtcrime.securesms.wallpaper.UriChatWallpaper + +/** + * Handles the importing of [Chat] models into the local database. + */ +object ChatArchiveImporter { + fun import(chat: Chat, recipientId: RecipientId, importState: ImportState): Long { + val chatColor = chat.style?.toLocal(importState) + + val wallpaperAttachmentId: AttachmentId? = chat.style?.wallpaperPhoto?.let { filePointer -> + filePointer.toLocalAttachment(importState)?.let { + SignalDatabase.attachments.restoreWallpaperAttachment(it) + } + } + + val chatWallpaper = chat.style?.parseChatWallpaper(wallpaperAttachmentId) + + val threadId = SignalDatabase.writableDatabase + .insertInto(ThreadTable.TABLE_NAME) + .values( + ThreadTable.RECIPIENT_ID to recipientId.serialize(), + ThreadTable.PINNED to chat.pinnedOrder, + ThreadTable.ARCHIVED to chat.archived.toInt(), + ThreadTable.READ to if (chat.markedUnread) ThreadTable.ReadStatus.FORCED_UNREAD.serialize() else ThreadTable.ReadStatus.READ.serialize(), + ThreadTable.ACTIVE to 1 + ) + .run() + + SignalDatabase.writableDatabase + .update( + RecipientTable.TABLE_NAME, + contentValuesOf( + 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.MESSAGE_EXPIRATION_TIME to chat.expirationTimerMs, + RecipientTable.MESSAGE_EXPIRATION_TIME_VERSION to chat.expireTimerVersion, + RecipientTable.CHAT_COLORS to chatColor?.serialize()?.encode(), + RecipientTable.CUSTOM_CHAT_COLORS_ID to (chatColor?.id ?: ChatColors.Id.NotSet).longValue, + RecipientTable.WALLPAPER_URI to if (chatWallpaper is UriChatWallpaper) chatWallpaper.uri.toString() else null, + RecipientTable.WALLPAPER to chatWallpaper?.serialize()?.encode() + ), + "${RecipientTable.ID} = ?", + SqlUtil.buildArgs(recipientId.toLong()) + ) + + return threadId + } +} diff --git a/app/src/main/java/org/thoughtcrime/securesms/backup/v2/database/ChatItemImportInserter.kt b/app/src/main/java/org/thoughtcrime/securesms/backup/v2/importer/ChatItemArchiveImporter.kt similarity index 99% rename from app/src/main/java/org/thoughtcrime/securesms/backup/v2/database/ChatItemImportInserter.kt rename to app/src/main/java/org/thoughtcrime/securesms/backup/v2/importer/ChatItemArchiveImporter.kt index 119f96510c..b32a19ecfa 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/backup/v2/database/ChatItemImportInserter.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/backup/v2/importer/ChatItemArchiveImporter.kt @@ -1,9 +1,9 @@ /* - * Copyright 2023 Signal Messenger, LLC + * Copyright 2024 Signal Messenger, LLC * SPDX-License-Identifier: AGPL-3.0-only */ -package org.thoughtcrime.securesms.backup.v2.database +package org.thoughtcrime.securesms.backup.v2.importer import android.content.ContentValues import androidx.core.content.contentValuesOf @@ -82,13 +82,13 @@ import org.thoughtcrime.securesms.backup.v2.proto.GiftBadge as BackupGiftBadge * An object that will ingest all fo the [ChatItem]s you want to write, buffer them until hitting a specified batch size, and then batch insert them * for fast throughput. */ -class ChatItemImportInserter( +class ChatItemArchiveImporter( private val db: SQLiteDatabase, private val importState: ImportState, private val batchSize: Int ) { companion object { - private val TAG = Log.tag(ChatItemImportInserter::class.java) + private val TAG = Log.tag(ChatItemArchiveImporter::class.java) private val MESSAGE_COLUMNS = arrayOf( MessageTable.DATE_SENT, @@ -150,7 +150,7 @@ class ChatItemImportInserter( * Indicate that you want to insert the [ChatItem] into the database. * 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 import(chatItem: ChatItem) { val fromLocalRecipientId: RecipientId? = importState.remoteToLocalRecipientId[chatItem.authorId] if (fromLocalRecipientId == null) { Log.w(TAG, "[insert] Could not find a local recipient for backup recipient ID ${chatItem.authorId}! Skipping.") diff --git a/app/src/main/java/org/thoughtcrime/securesms/backup/v2/importer/ContactArchiveImporter.kt b/app/src/main/java/org/thoughtcrime/securesms/backup/v2/importer/ContactArchiveImporter.kt new file mode 100644 index 0000000000..dd13ebcb95 --- /dev/null +++ b/app/src/main/java/org/thoughtcrime/securesms/backup/v2/importer/ContactArchiveImporter.kt @@ -0,0 +1,86 @@ +/* + * Copyright 2024 Signal Messenger, LLC + * SPDX-License-Identifier: AGPL-3.0-only + */ + +package org.thoughtcrime.securesms.backup.v2.importer + +import androidx.core.content.contentValuesOf +import org.signal.core.util.Base64 +import org.signal.core.util.toInt +import org.signal.core.util.update +import org.thoughtcrime.securesms.backup.v2.proto.Contact +import org.thoughtcrime.securesms.database.RecipientTable +import org.thoughtcrime.securesms.database.SignalDatabase +import org.thoughtcrime.securesms.database.model.databaseprotos.RecipientExtras +import org.thoughtcrime.securesms.dependencies.AppDependencies +import org.thoughtcrime.securesms.phonenumbers.PhoneNumberFormatter +import org.thoughtcrime.securesms.profiles.ProfileName +import org.thoughtcrime.securesms.recipients.Recipient +import org.thoughtcrime.securesms.recipients.RecipientId +import org.whispersystems.signalservice.api.push.ServiceId.ACI +import org.whispersystems.signalservice.api.push.ServiceId.PNI + +/** + * Handles the importing of [Contact] models into the local database. + */ +object ContactArchiveImporter { + fun import(contact: Contact): RecipientId { + val id = SignalDatabase.recipients.getAndPossiblyMergePnpVerified( + aci = ACI.parseOrNull(contact.aci?.toByteArray()), + pni = PNI.parseOrNull(contact.pni?.toByteArray()), + e164 = contact.formattedE164 + ) + + val profileKey = contact.profileKey?.toByteArray() + val values = contentValuesOf( + RecipientTable.BLOCKED to contact.blocked, + RecipientTable.HIDDEN to contact.visibility.toLocal().serialize(), + RecipientTable.TYPE to RecipientTable.RecipientType.INDIVIDUAL.id, + RecipientTable.PROFILE_FAMILY_NAME to contact.profileFamilyName, + RecipientTable.PROFILE_GIVEN_NAME to contact.profileGivenName, + RecipientTable.PROFILE_JOINED_NAME to ProfileName.fromParts(contact.profileGivenName, contact.profileFamilyName).toString(), + RecipientTable.PROFILE_KEY to if (profileKey == null) null else Base64.encodeWithPadding(profileKey), + RecipientTable.PROFILE_SHARING to contact.profileSharing.toInt(), + RecipientTable.USERNAME to contact.username, + RecipientTable.EXTRAS to contact.toLocalExtras().encode() + ) + + 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) + values.put(RecipientTable.REGISTERED, RecipientTable.RegisteredState.NOT_REGISTERED.id) + } + + SignalDatabase.writableDatabase + .update(RecipientTable.TABLE_NAME) + .values(values) + .where("${RecipientTable.ID} = ?", id) + .run() + + return id + } +} + +private fun Contact.Visibility.toLocal(): Recipient.HiddenState { + return when (this) { + Contact.Visibility.VISIBLE -> Recipient.HiddenState.NOT_HIDDEN + Contact.Visibility.HIDDEN -> Recipient.HiddenState.HIDDEN + Contact.Visibility.HIDDEN_MESSAGE_REQUEST -> Recipient.HiddenState.HIDDEN_MESSAGE_REQUEST + } +} + +private fun Contact.toLocalExtras(): RecipientExtras { + return RecipientExtras( + hideStory = this.hideStory + ) +} + +private val Contact.formattedE164: String? + get() { + return e164?.let { + PhoneNumberFormatter.get(AppDependencies.application).format(e164.toString()) + } + } diff --git a/app/src/main/java/org/thoughtcrime/securesms/backup/v2/importer/DistributionListArchiveImporter.kt b/app/src/main/java/org/thoughtcrime/securesms/backup/v2/importer/DistributionListArchiveImporter.kt new file mode 100644 index 0000000000..8efcb8fb90 --- /dev/null +++ b/app/src/main/java/org/thoughtcrime/securesms/backup/v2/importer/DistributionListArchiveImporter.kt @@ -0,0 +1,72 @@ +/* + * Copyright 2024 Signal Messenger, LLC + * SPDX-License-Identifier: AGPL-3.0-only + */ + +package org.thoughtcrime.securesms.backup.v2.importer + +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 +import org.whispersystems.signalservice.api.push.DistributionId +import org.whispersystems.signalservice.api.util.UuidUtil + +/** + * Handles the importing of [DistributionListItem] models into the local database. + */ +object DistributionListArchiveImporter { + + private val TAG = Log.tag(DistributionListArchiveImporter.javaClass) + + fun import(dlistItem: DistributionListItem, importState: ImportState): RecipientId? { + if (dlistItem.deletionTimestamp != null && dlistItem.deletionTimestamp > 0) { + val dlistId = SignalDatabase.distributionLists.createList( + name = "", + members = emptyList(), + distributionId = DistributionId.from(UuidUtil.fromByteString(dlistItem.distributionId)), + allowsReplies = false, + deletionTimestamp = dlistItem.deletionTimestamp, + storageId = null, + privacyMode = DistributionListPrivacyMode.ONLY_WITH + )!! + + return SignalDatabase.distributionLists.getRecipientId(dlistId)!! + } + + val dlist = dlistItem.distributionList ?: return null + val members: List = dlist.memberRecipientIds + .mapNotNull { importState.remoteToLocalRecipientId[it] } + + if (members.size != dlist.memberRecipientIds.size) { + Log.w(TAG, "Couldn't find some member recipients! Missing backup recipientIds: ${dlist.memberRecipientIds.toSet() - members.toSet()}") + } + + val distributionId = DistributionId.from(UuidUtil.fromByteString(dlistItem.distributionId)) + val privacyMode = dlist.privacyMode.toLocalPrivacyMode() + + val dlistId = SignalDatabase.distributionLists.createList( + name = dlist.name, + members = members, + distributionId = distributionId, + allowsReplies = dlist.allowReplies, + deletionTimestamp = dlistItem.deletionTimestamp ?: 0, + storageId = null, + privacyMode = privacyMode + )!! + + return SignalDatabase.distributionLists.getRecipientId(dlistId)!! + } +} + +private fun DistributionList.PrivacyMode.toLocalPrivacyMode(): DistributionListPrivacyMode { + return when (this) { + DistributionList.PrivacyMode.UNKNOWN -> DistributionListPrivacyMode.ALL + DistributionList.PrivacyMode.ONLY_WITH -> DistributionListPrivacyMode.ONLY_WITH + DistributionList.PrivacyMode.ALL -> DistributionListPrivacyMode.ALL + DistributionList.PrivacyMode.ALL_EXCEPT -> DistributionListPrivacyMode.ALL_EXCEPT + } +} diff --git a/app/src/main/java/org/thoughtcrime/securesms/backup/v2/importer/GroupArchiveImporter.kt b/app/src/main/java/org/thoughtcrime/securesms/backup/v2/importer/GroupArchiveImporter.kt new file mode 100644 index 0000000000..ea3597045b --- /dev/null +++ b/app/src/main/java/org/thoughtcrime/securesms/backup/v2/importer/GroupArchiveImporter.kt @@ -0,0 +1,146 @@ +/* + * Copyright 2024 Signal Messenger, LLC + * SPDX-License-Identifier: AGPL-3.0-only + */ + +package org.thoughtcrime.securesms.backup.v2.importer + +import android.content.ContentValues +import org.signal.core.util.Base64 +import org.signal.libsignal.zkgroup.groups.GroupMasterKey +import org.signal.libsignal.zkgroup.groups.GroupSecretParams +import org.signal.storageservice.protos.groups.AccessControl +import org.signal.storageservice.protos.groups.Member +import org.signal.storageservice.protos.groups.local.DecryptedBannedMember +import org.signal.storageservice.protos.groups.local.DecryptedGroup +import org.signal.storageservice.protos.groups.local.DecryptedMember +import org.signal.storageservice.protos.groups.local.DecryptedPendingMember +import org.signal.storageservice.protos.groups.local.DecryptedRequestingMember +import org.signal.storageservice.protos.groups.local.DecryptedTimer +import org.signal.storageservice.protos.groups.local.EnabledState +import org.thoughtcrime.securesms.backup.v2.ArchiveGroup +import org.thoughtcrime.securesms.backup.v2.proto.Group +import org.thoughtcrime.securesms.conversation.colors.AvatarColorHash +import org.thoughtcrime.securesms.database.GroupTable +import org.thoughtcrime.securesms.database.RecipientTable +import org.thoughtcrime.securesms.database.SignalDatabase +import org.thoughtcrime.securesms.database.model.databaseprotos.RecipientExtras +import org.thoughtcrime.securesms.dependencies.AppDependencies +import org.thoughtcrime.securesms.groups.GroupId +import org.thoughtcrime.securesms.groups.v2.processing.GroupsV2StateProcessor +import org.thoughtcrime.securesms.recipients.RecipientId +import org.thoughtcrime.securesms.storage.StorageSyncHelper +import org.whispersystems.signalservice.api.groupsv2.GroupsV2Operations +import org.whispersystems.signalservice.api.push.ServiceId + +/** + * Handles the importing of [ArchiveGroup] models into the local database. + */ +object GroupArchiveImporter { + fun import(group: ArchiveGroup): RecipientId { + val masterKey = GroupMasterKey(group.masterKey.toByteArray()) + val groupId = GroupId.v2(masterKey) + + val operations = AppDependencies.groupsV2Operations.forGroup(GroupSecretParams.deriveFromMasterKey(masterKey)) + val decryptedState = if (group.snapshot == null) { + DecryptedGroup(revision = GroupsV2StateProcessor.RESTORE_PLACEHOLDER_REVISION) + } else { + group.snapshot.toLocal(operations) + } + + val values = ContentValues().apply { + put(RecipientTable.GROUP_ID, groupId.toString()) + put(RecipientTable.AVATAR_COLOR, AvatarColorHash.forGroupId(groupId).serialize()) + put(RecipientTable.PROFILE_SHARING, group.whitelisted) + put(RecipientTable.TYPE, RecipientTable.RecipientType.GV2.id) + put(RecipientTable.STORAGE_SERVICE_ID, Base64.encodeWithPadding(StorageSyncHelper.generateKey())) + if (group.hideStory) { + val extras = RecipientExtras.Builder().hideStory(true).build() + put(RecipientTable.EXTRAS, extras.encode()) + } + } + + val recipientId = SignalDatabase.writableDatabase.insert(RecipientTable.TABLE_NAME, null, values) + val restoredId = SignalDatabase.groups.create(masterKey, decryptedState, groupSendEndorsements = null) + if (restoredId != null) { + SignalDatabase.groups.setShowAsStoryState(restoredId, group.storySendMode.toLocal()) + } + + return RecipientId.from(recipientId) + } +} + +private fun Group.StorySendMode.toLocal(): GroupTable.ShowAsStoryState { + return when (this) { + Group.StorySendMode.ENABLED -> GroupTable.ShowAsStoryState.ALWAYS + Group.StorySendMode.DISABLED -> GroupTable.ShowAsStoryState.NEVER + Group.StorySendMode.DEFAULT -> GroupTable.ShowAsStoryState.IF_ACTIVE + } +} + +private fun Group.MemberPendingProfileKey.toLocal(operations: GroupsV2Operations.GroupOperations): DecryptedPendingMember { + return DecryptedPendingMember( + serviceIdBytes = member!!.userId, + role = member.role.toLocal(), + addedByAci = addedByUserId, + timestamp = timestamp, + serviceIdCipherText = operations.encryptServiceId(ServiceId.Companion.parseOrNull(member.userId)) + ) +} + +private fun Group.AccessControl.AccessRequired.toLocal(): AccessControl.AccessRequired { + return when (this) { + Group.AccessControl.AccessRequired.UNKNOWN -> AccessControl.AccessRequired.UNKNOWN + Group.AccessControl.AccessRequired.ANY -> AccessControl.AccessRequired.ANY + Group.AccessControl.AccessRequired.MEMBER -> AccessControl.AccessRequired.MEMBER + Group.AccessControl.AccessRequired.ADMINISTRATOR -> AccessControl.AccessRequired.ADMINISTRATOR + Group.AccessControl.AccessRequired.UNSATISFIABLE -> AccessControl.AccessRequired.UNSATISFIABLE + } +} + +private fun Group.AccessControl.toLocal(): AccessControl { + return AccessControl(members = this.members.toLocal(), attributes = this.attributes.toLocal(), addFromInviteLink = this.addFromInviteLink.toLocal()) +} + +private fun Group.Member.Role.toLocal(): Member.Role { + return when (this) { + Group.Member.Role.UNKNOWN -> Member.Role.UNKNOWN + Group.Member.Role.DEFAULT -> Member.Role.DEFAULT + Group.Member.Role.ADMINISTRATOR -> Member.Role.ADMINISTRATOR + } +} + +private fun Group.Member.toLocal(): DecryptedMember { + return DecryptedMember(aciBytes = userId, role = role.toLocal(), joinedAtRevision = joinedAtVersion) +} + +private fun Group.MemberPendingAdminApproval.toLocal(): DecryptedRequestingMember { + return DecryptedRequestingMember( + aciBytes = this.userId, + timestamp = this.timestamp + ) +} + +private fun Group.MemberBanned.toLocal(): DecryptedBannedMember { + return DecryptedBannedMember( + serviceIdBytes = this.userId, + timestamp = this.timestamp + ) +} + +private fun Group.GroupSnapshot.toLocal(operations: GroupsV2Operations.GroupOperations): DecryptedGroup { + return DecryptedGroup( + title = this.title?.title ?: "", + avatar = this.avatarUrl, + disappearingMessagesTimer = DecryptedTimer(duration = this.disappearingMessagesTimer?.disappearingMessagesDuration ?: 0), + accessControl = this.accessControl?.toLocal(), + revision = this.version, + members = this.members.map { member -> member.toLocal() }, + pendingMembers = this.membersPendingProfileKey.map { pending -> pending.toLocal(operations) }, + requestingMembers = this.membersPendingAdminApproval.map { requesting -> requesting.toLocal() }, + inviteLinkPassword = this.inviteLinkPassword, + description = this.description?.descriptionText ?: "", + isAnnouncementGroup = if (this.announcements_only) EnabledState.ENABLED else EnabledState.DISABLED, + bannedMembers = this.members_banned.map { it.toLocal() } + ) +} diff --git a/app/src/main/java/org/thoughtcrime/securesms/backup/v2/processor/AccountDataBackupProcessor.kt b/app/src/main/java/org/thoughtcrime/securesms/backup/v2/processor/AccountDataArchiveProcessor.kt similarity index 99% rename from app/src/main/java/org/thoughtcrime/securesms/backup/v2/processor/AccountDataBackupProcessor.kt rename to app/src/main/java/org/thoughtcrime/securesms/backup/v2/processor/AccountDataArchiveProcessor.kt index 7f38528e1c..bf61d7b9dc 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/backup/v2/processor/AccountDataBackupProcessor.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/backup/v2/processor/AccountDataArchiveProcessor.kt @@ -44,9 +44,9 @@ import java.util.Currency /** * Handles importing/exporting [AccountData] frames for an archive. */ -object AccountDataBackupProcessor { +object AccountDataArchiveProcessor { - private val TAG = Log.tag(AccountDataBackupProcessor::class) + private val TAG = Log.tag(AccountDataArchiveProcessor::class) fun export(db: SignalDatabase, signalStore: SignalStore, emitter: BackupFrameEmitter) { val context = AppDependencies.application diff --git a/app/src/main/java/org/thoughtcrime/securesms/backup/v2/processor/AdHocCallBackupProcessor.kt b/app/src/main/java/org/thoughtcrime/securesms/backup/v2/processor/AdHocCallArchiveProcessor.kt similarity index 79% rename from app/src/main/java/org/thoughtcrime/securesms/backup/v2/processor/AdHocCallBackupProcessor.kt rename to app/src/main/java/org/thoughtcrime/securesms/backup/v2/processor/AdHocCallArchiveProcessor.kt index d5f341a7c3..2e55bbf9f5 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/backup/v2/processor/AdHocCallBackupProcessor.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/backup/v2/processor/AdHocCallArchiveProcessor.kt @@ -8,7 +8,7 @@ package org.thoughtcrime.securesms.backup.v2.processor import org.signal.core.util.logging.Log import org.thoughtcrime.securesms.backup.v2.ImportState import org.thoughtcrime.securesms.backup.v2.database.getAdhocCallsForBackup -import org.thoughtcrime.securesms.backup.v2.database.restoreCallLogFromBackup +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 @@ -17,9 +17,9 @@ import org.thoughtcrime.securesms.database.SignalDatabase /** * Handles importing/exporting [AdHocCall] frames for an archive. */ -object AdHocCallBackupProcessor { +object AdHocCallArchiveProcessor { - val TAG = Log.tag(AdHocCallBackupProcessor::class.java) + val TAG = Log.tag(AdHocCallArchiveProcessor::class.java) fun export(db: SignalDatabase, emitter: BackupFrameEmitter) { db.callTable.getAdhocCallsForBackup().use { reader -> @@ -30,6 +30,6 @@ object AdHocCallBackupProcessor { } fun import(call: AdHocCall, importState: ImportState) { - SignalDatabase.calls.restoreCallLogFromBackup(call, importState) + AdHodCallArchiveImporter.import(call, importState) } } diff --git a/app/src/main/java/org/thoughtcrime/securesms/backup/v2/processor/ChatBackupProcessor.kt b/app/src/main/java/org/thoughtcrime/securesms/backup/v2/processor/ChatArchiveProcessor.kt similarity index 76% rename from app/src/main/java/org/thoughtcrime/securesms/backup/v2/processor/ChatBackupProcessor.kt rename to app/src/main/java/org/thoughtcrime/securesms/backup/v2/processor/ChatArchiveProcessor.kt index a4d77b5154..274dc47e4f 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/backup/v2/processor/ChatBackupProcessor.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/backup/v2/processor/ChatArchiveProcessor.kt @@ -9,7 +9,7 @@ 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.getThreadsForBackup -import org.thoughtcrime.securesms.backup.v2.database.restoreFromBackup +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 @@ -19,8 +19,8 @@ import org.thoughtcrime.securesms.recipients.RecipientId /** * Handles importing/exporting [Chat] frames for an archive. */ -object ChatBackupProcessor { - val TAG = Log.tag(ChatBackupProcessor::class.java) +object ChatArchiveProcessor { + val TAG = Log.tag(ChatArchiveProcessor::class.java) fun export(db: SignalDatabase, exportState: ExportState, emitter: BackupFrameEmitter) { db.threadTable.getThreadsForBackup(db).use { reader -> @@ -42,10 +42,9 @@ object ChatBackupProcessor { return } - SignalDatabase.threads.restoreFromBackup(chat, recipientId, importState).let { threadId -> - importState.chatIdToLocalRecipientId[chat.id] = recipientId - importState.chatIdToLocalThreadId[chat.id] = threadId - importState.chatIdToBackupRecipientId[chat.id] = chat.recipientId - } + val threadId = ChatArchiveImporter.import(chat, recipientId, importState) + importState.chatIdToLocalRecipientId[chat.id] = recipientId + importState.chatIdToLocalThreadId[chat.id] = threadId + importState.chatIdToBackupRecipientId[chat.id] = chat.recipientId } } diff --git a/app/src/main/java/org/thoughtcrime/securesms/backup/v2/processor/ChatItemBackupProcessor.kt b/app/src/main/java/org/thoughtcrime/securesms/backup/v2/processor/ChatItemArchiveProcessor.kt similarity index 84% rename from app/src/main/java/org/thoughtcrime/securesms/backup/v2/processor/ChatItemBackupProcessor.kt rename to app/src/main/java/org/thoughtcrime/securesms/backup/v2/processor/ChatItemArchiveProcessor.kt index 1bec7dda35..157a2b9bf2 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/backup/v2/processor/ChatItemBackupProcessor.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/backup/v2/processor/ChatItemArchiveProcessor.kt @@ -8,9 +8,9 @@ package org.thoughtcrime.securesms.backup.v2.processor 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.ChatItemImportInserter 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 @@ -19,8 +19,8 @@ import org.thoughtcrime.securesms.database.SignalDatabase /** * Handles importing/exporting [ChatItem] frames for an archive. */ -object ChatItemBackupProcessor { - val TAG = Log.tag(ChatItemBackupProcessor::class.java) +object ChatItemArchiveProcessor { + val TAG = Log.tag(ChatItemArchiveProcessor::class.java) fun export(db: SignalDatabase, exportState: ExportState, emitter: BackupFrameEmitter) { db.messageTable.getMessagesForBackup(db, exportState.backupTime, exportState.mediaBackupEnabled).use { chatItems -> @@ -35,7 +35,7 @@ object ChatItemBackupProcessor { } } - fun beginImport(importState: ImportState): ChatItemImportInserter { + fun beginImport(importState: ImportState): ChatItemArchiveImporter { return SignalDatabase.messages.createChatItemInserter(importState) } } diff --git a/app/src/main/java/org/thoughtcrime/securesms/backup/v2/processor/RecipientBackupProcessor.kt b/app/src/main/java/org/thoughtcrime/securesms/backup/v2/processor/RecipientArchiveProcessor.kt similarity index 79% rename from app/src/main/java/org/thoughtcrime/securesms/backup/v2/processor/RecipientBackupProcessor.kt rename to app/src/main/java/org/thoughtcrime/securesms/backup/v2/processor/RecipientArchiveProcessor.kt index 16f5170077..40ef804b6d 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/backup/v2/processor/RecipientBackupProcessor.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/backup/v2/processor/RecipientArchiveProcessor.kt @@ -13,10 +13,11 @@ import org.thoughtcrime.securesms.backup.v2.database.getAllForBackup import org.thoughtcrime.securesms.backup.v2.database.getCallLinksForBackup import org.thoughtcrime.securesms.backup.v2.database.getContactsForBackup import org.thoughtcrime.securesms.backup.v2.database.getGroupsForBackup -import org.thoughtcrime.securesms.backup.v2.database.restoreContactFromBackup -import org.thoughtcrime.securesms.backup.v2.database.restoreFromBackup -import org.thoughtcrime.securesms.backup.v2.database.restoreGroupFromBackup import org.thoughtcrime.securesms.backup.v2.database.restoreReleaseNotes +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 @@ -27,9 +28,9 @@ import org.thoughtcrime.securesms.recipients.Recipient /** * Handles importing/exporting [ArchiveRecipient] frames for an archive. */ -object RecipientBackupProcessor { +object RecipientArchiveProcessor { - val TAG = Log.tag(RecipientBackupProcessor::class.java) + val TAG = Log.tag(RecipientArchiveProcessor::class.java) fun export(db: SignalDatabase, signalStore: SignalStore, exportState: ExportState, emitter: BackupFrameEmitter) { val selfId = db.recipientTable.getByAci(signalStore.accountValues.aci!!).get().toLong() @@ -79,12 +80,12 @@ object RecipientBackupProcessor { fun import(recipient: ArchiveRecipient, importState: ImportState) { val newId = when { - recipient.contact != null -> SignalDatabase.recipients.restoreContactFromBackup(recipient.contact) - recipient.group != null -> SignalDatabase.recipients.restoreGroupFromBackup(recipient.group) - recipient.distributionList != null -> SignalDatabase.distributionLists.restoreFromBackup(recipient.distributionList, importState) + recipient.contact != null -> ContactArchiveImporter.import(recipient.contact) + recipient.group != null -> GroupArchiveImporter.import(recipient.group) + recipient.distributionList != null -> DistributionListArchiveImporter.import(recipient.distributionList, importState) recipient.self != null -> Recipient.self().id recipient.releaseNotes != null -> SignalDatabase.recipients.restoreReleaseNotes() - recipient.callLink != null -> SignalDatabase.callLinks.restoreFromBackup(recipient.callLink) + recipient.callLink != null -> CallLinkArchiveImporter.import(recipient.callLink) else -> { Log.w(TAG, "Unrecognized recipient type!") null diff --git a/app/src/main/java/org/thoughtcrime/securesms/backup/v2/processor/StickerBackupProcessor.kt b/app/src/main/java/org/thoughtcrime/securesms/backup/v2/processor/StickerArchiveProcessor.kt similarity index 98% rename from app/src/main/java/org/thoughtcrime/securesms/backup/v2/processor/StickerBackupProcessor.kt rename to app/src/main/java/org/thoughtcrime/securesms/backup/v2/processor/StickerArchiveProcessor.kt index ff96b5d2cd..cf835f234c 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/backup/v2/processor/StickerBackupProcessor.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/backup/v2/processor/StickerArchiveProcessor.kt @@ -22,7 +22,7 @@ import org.thoughtcrime.securesms.jobs.StickerPackDownloadJob /** * Handles importing/exporting [StickerPack] frames for an archive. */ -object StickerBackupProcessor { +object StickerArchiveProcessor { fun export(db: SignalDatabase, emitter: BackupFrameEmitter) { StickerPackRecordReader(db.stickerTable.allStickerPacks).use { reader -> var record: StickerPackRecord? = reader.next diff --git a/app/src/main/java/org/thoughtcrime/securesms/components/webrtc/v2/CallInfoCallbacks.kt b/app/src/main/java/org/thoughtcrime/securesms/components/webrtc/v2/CallInfoCallbacks.kt index d91703ea57..3349df9a75 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/components/webrtc/v2/CallInfoCallbacks.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/components/webrtc/v2/CallInfoCallbacks.kt @@ -10,8 +10,6 @@ import android.content.Intent import android.widget.Toast import androidx.core.app.ShareCompat import com.google.android.material.dialog.MaterialAlertDialogBuilder -import io.reactivex.rxjava3.disposables.CompositeDisposable -import org.signal.core.util.logging.Log import org.thoughtcrime.securesms.BaseActivity import org.thoughtcrime.securesms.R import org.thoughtcrime.securesms.calls.links.CallLinks @@ -27,7 +25,7 @@ import org.thoughtcrime.securesms.events.CallParticipant */ class CallInfoCallbacks( private val activity: BaseActivity, - private val controlsAndInfoViewModel: ControlsAndInfoViewModel, + private val controlsAndInfoViewModel: ControlsAndInfoViewModel ) : CallInfoView.Callbacks { override fun onShareLinkClicked() { diff --git a/app/src/main/java/org/thoughtcrime/securesms/database/SignalDatabase.kt b/app/src/main/java/org/thoughtcrime/securesms/database/SignalDatabase.kt index dea60f8cbb..d8b6e717cd 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/database/SignalDatabase.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/database/SignalDatabase.kt @@ -247,6 +247,14 @@ open class SignalDatabase(private val context: Application, databaseSecret: Data val rawDatabase: net.zetetic.database.sqlcipher.SQLiteDatabase get() = instance!!.rawWritableDatabase + @JvmStatic + val readableDatabase: SQLiteDatabase + get() = instance!!.signalReadableDatabase + + @JvmStatic + val writableDatabase: SQLiteDatabase + get() = instance!!.signalWritableDatabase + @JvmStatic val backupDatabase: net.zetetic.database.sqlcipher.SQLiteDatabase get() = instance!!.rawReadableDatabase