mirror of
https://github.com/signalapp/Signal-Android.git
synced 2025-12-24 04:58:45 +00:00
Add import/export for stickers and sticker packs.
This commit is contained in:
@@ -10,6 +10,7 @@ import android.os.Parcel
|
||||
import org.signal.core.util.Base64
|
||||
import org.thoughtcrime.securesms.blurhash.BlurHash
|
||||
import org.thoughtcrime.securesms.database.AttachmentTable
|
||||
import org.thoughtcrime.securesms.stickers.StickerLocator
|
||||
|
||||
class ArchivedAttachment : Attachment {
|
||||
|
||||
@@ -44,6 +45,7 @@ class ArchivedAttachment : Attachment {
|
||||
blurHash: String?,
|
||||
voiceNote: Boolean,
|
||||
borderless: Boolean,
|
||||
stickerLocator: StickerLocator?,
|
||||
gif: Boolean,
|
||||
quote: Boolean
|
||||
) : super(
|
||||
@@ -66,7 +68,7 @@ class ArchivedAttachment : Attachment {
|
||||
incrementalMacChunkSize = incrementalMacChunkSize ?: 0,
|
||||
uploadTimestamp = 0,
|
||||
caption = caption,
|
||||
stickerLocator = null,
|
||||
stickerLocator = stickerLocator,
|
||||
blurHash = BlurHash.parseOrNull(blurHash),
|
||||
audioHash = null,
|
||||
transformProperties = null
|
||||
|
||||
@@ -28,6 +28,7 @@ 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.proto.BackupInfo
|
||||
import org.thoughtcrime.securesms.backup.v2.stream.BackupExportWriter
|
||||
import org.thoughtcrime.securesms.backup.v2.stream.EncryptedBackupReader
|
||||
@@ -129,6 +130,11 @@ object BackupRepository {
|
||||
eventTimer.emit("call")
|
||||
}
|
||||
|
||||
StickerBackupProcessor.export { frame ->
|
||||
writer.write(frame)
|
||||
eventTimer.emit("sticker-pack")
|
||||
}
|
||||
|
||||
ChatItemBackupProcessor.export(exportState) { frame ->
|
||||
writer.write(frame)
|
||||
eventTimer.emit("message")
|
||||
@@ -186,6 +192,7 @@ object BackupRepository {
|
||||
SignalDatabase.threads.clearAllDataForBackupRestore()
|
||||
SignalDatabase.messages.clearAllDataForBackupRestore()
|
||||
SignalDatabase.attachments.clearAllDataForBackupRestore()
|
||||
SignalDatabase.stickers.clearAllDataForBackupRestore()
|
||||
|
||||
// Add back self after clearing data
|
||||
val selfId: RecipientId = SignalDatabase.recipients.getAndPossiblyMerge(selfData.aci, selfData.pni, selfData.e164, pniVerified = true, changeSelf = true)
|
||||
@@ -222,6 +229,11 @@ object BackupRepository {
|
||||
eventTimer.emit("call")
|
||||
}
|
||||
|
||||
frame.stickerPack != null -> {
|
||||
StickerBackupProcessor.import(frame.stickerPack)
|
||||
eventTimer.emit("sticker-pack")
|
||||
}
|
||||
|
||||
frame.chatItem != null -> {
|
||||
chatItemInserter.insert(frame.chatItem)
|
||||
eventTimer.emit("chatItem")
|
||||
|
||||
@@ -10,6 +10,7 @@ import okio.ByteString.Companion.toByteString
|
||||
import org.signal.core.util.Base64
|
||||
import org.signal.core.util.Base64.decode
|
||||
import org.signal.core.util.Base64.decodeOrThrow
|
||||
import org.signal.core.util.Hex
|
||||
import org.signal.core.util.logging.Log
|
||||
import org.signal.core.util.requireBlob
|
||||
import org.signal.core.util.requireBoolean
|
||||
@@ -37,6 +38,8 @@ import org.thoughtcrime.securesms.backup.v2.proto.SendStatus
|
||||
import org.thoughtcrime.securesms.backup.v2.proto.SessionSwitchoverChatUpdate
|
||||
import org.thoughtcrime.securesms.backup.v2.proto.SimpleChatUpdate
|
||||
import org.thoughtcrime.securesms.backup.v2.proto.StandardMessage
|
||||
import org.thoughtcrime.securesms.backup.v2.proto.Sticker
|
||||
import org.thoughtcrime.securesms.backup.v2.proto.StickerMessage
|
||||
import org.thoughtcrime.securesms.backup.v2.proto.Text
|
||||
import org.thoughtcrime.securesms.backup.v2.proto.ThreadMergeChatUpdate
|
||||
import org.thoughtcrime.securesms.database.AttachmentTable
|
||||
@@ -84,7 +87,7 @@ import org.thoughtcrime.securesms.backup.v2.proto.BodyRange as BackupBodyRange
|
||||
*
|
||||
* All of this complexity is hidden from the user -- they just get a normal iterator interface.
|
||||
*/
|
||||
class ChatItemExportIterator(private val cursor: Cursor, private val batchSize: Int, private val archiveMedia: Boolean) : Iterator<ChatItem>, Closeable {
|
||||
class ChatItemExportIterator(private val cursor: Cursor, private val batchSize: Int, private val archiveMedia: Boolean) : Iterator<ChatItem?>, Closeable {
|
||||
|
||||
companion object {
|
||||
private val TAG = Log.tag(ChatItemExportIterator::class.java)
|
||||
@@ -104,7 +107,7 @@ class ChatItemExportIterator(private val cursor: Cursor, private val batchSize:
|
||||
return buffer.isNotEmpty() || (cursor.count > 0 && !cursor.isLast && !cursor.isAfterLast)
|
||||
}
|
||||
|
||||
override fun next(): ChatItem {
|
||||
override fun next(): ChatItem? {
|
||||
if (buffer.isNotEmpty()) {
|
||||
return buffer.remove()
|
||||
}
|
||||
@@ -344,11 +347,31 @@ class ChatItemExportIterator(private val cursor: Cursor, private val batchSize:
|
||||
)
|
||||
}
|
||||
}
|
||||
record.body == null && !attachmentsById.containsKey(record.id) -> {
|
||||
Log.w(TAG, "Record missing a body and doesnt have attachments, skipping")
|
||||
continue
|
||||
else -> {
|
||||
if (record.body == null && !attachmentsById.containsKey(record.id)) {
|
||||
Log.w(TAG, "Record missing a body and doesnt have attachments, skipping")
|
||||
continue
|
||||
}
|
||||
val attachments = attachmentsById[record.id]
|
||||
val sticker = attachments?.firstOrNull { dbAttachment ->
|
||||
dbAttachment.isSticker
|
||||
}
|
||||
if (sticker != null) {
|
||||
val stickerLocator = sticker.stickerLocator!!
|
||||
builder.stickerMessage = StickerMessage(
|
||||
sticker = Sticker(
|
||||
packId = Hex.fromStringCondensed(stickerLocator.packId).toByteString(),
|
||||
packKey = Hex.fromStringCondensed(stickerLocator.packKey).toByteString(),
|
||||
stickerId = stickerLocator.stickerId,
|
||||
emoji = stickerLocator.emoji,
|
||||
data_ = sticker.toBackupAttachment().pointer
|
||||
),
|
||||
reactions = reactionsById[id].toBackupReactions()
|
||||
)
|
||||
} else {
|
||||
builder.standardMessage = record.toStandardMessage(reactionsById[id], mentions = mentionsById[id], attachments = attachmentsById[record.id])
|
||||
}
|
||||
}
|
||||
else -> builder.standardMessage = record.toStandardMessage(reactionsById[id], mentions = mentionsById[id], attachments = attachmentsById[record.id])
|
||||
}
|
||||
if (record.latestRevisionId == null) {
|
||||
val previousEdits = revisionMap.remove(record.id)
|
||||
@@ -369,7 +392,7 @@ class ChatItemExportIterator(private val cursor: Cursor, private val batchSize:
|
||||
return if (buffer.isNotEmpty()) {
|
||||
buffer.remove()
|
||||
} else {
|
||||
throw NoSuchElementException()
|
||||
null
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -8,6 +8,7 @@ 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.Hex
|
||||
import org.signal.core.util.SqlUtil
|
||||
import org.signal.core.util.logging.Log
|
||||
import org.signal.core.util.orNull
|
||||
@@ -22,6 +23,7 @@ import org.thoughtcrime.securesms.backup.v2.BackupState
|
||||
import org.thoughtcrime.securesms.backup.v2.proto.BodyRange
|
||||
import org.thoughtcrime.securesms.backup.v2.proto.ChatItem
|
||||
import org.thoughtcrime.securesms.backup.v2.proto.ChatUpdateMessage
|
||||
import org.thoughtcrime.securesms.backup.v2.proto.FilePointer
|
||||
import org.thoughtcrime.securesms.backup.v2.proto.GroupCall
|
||||
import org.thoughtcrime.securesms.backup.v2.proto.IndividualCall
|
||||
import org.thoughtcrime.securesms.backup.v2.proto.MessageAttachment
|
||||
@@ -31,6 +33,7 @@ import org.thoughtcrime.securesms.backup.v2.proto.Reaction
|
||||
import org.thoughtcrime.securesms.backup.v2.proto.SendStatus
|
||||
import org.thoughtcrime.securesms.backup.v2.proto.SimpleChatUpdate
|
||||
import org.thoughtcrime.securesms.backup.v2.proto.StandardMessage
|
||||
import org.thoughtcrime.securesms.backup.v2.proto.Sticker
|
||||
import org.thoughtcrime.securesms.database.AttachmentTable
|
||||
import org.thoughtcrime.securesms.database.CallTable
|
||||
import org.thoughtcrime.securesms.database.GroupReceiptTable
|
||||
@@ -61,6 +64,7 @@ import org.thoughtcrime.securesms.payments.State
|
||||
import org.thoughtcrime.securesms.payments.proto.PaymentMetaData
|
||||
import org.thoughtcrime.securesms.recipients.Recipient
|
||||
import org.thoughtcrime.securesms.recipients.RecipientId
|
||||
import org.thoughtcrime.securesms.stickers.StickerLocator
|
||||
import org.thoughtcrime.securesms.util.JsonUtils
|
||||
import org.whispersystems.signalservice.api.backup.MediaName
|
||||
import org.whispersystems.signalservice.api.messages.SignalServiceAttachmentPointer
|
||||
@@ -340,6 +344,15 @@ class ChatItemImportInserter(
|
||||
}
|
||||
}
|
||||
}
|
||||
if (this.stickerMessage != null) {
|
||||
val sticker = this.stickerMessage.sticker
|
||||
val attachment = sticker.toLocalAttachment()
|
||||
if (attachment != null) {
|
||||
followUp = { messageRowId ->
|
||||
SignalDatabase.attachments.insertAttachmentsForMessage(messageRowId, listOf(attachment), emptyList())
|
||||
}
|
||||
}
|
||||
}
|
||||
return MessageInsert(contentValues, followUp)
|
||||
}
|
||||
|
||||
@@ -804,74 +817,114 @@ class ChatItemImportInserter(
|
||||
}
|
||||
}
|
||||
|
||||
private fun MessageAttachment.toLocalAttachment(contentType: String? = pointer?.contentType, fileName: String? = pointer?.fileName): Attachment? {
|
||||
if (pointer == null) return null
|
||||
if (pointer.attachmentLocator != null) {
|
||||
private fun FilePointer?.toLocalAttachment(voiceNote: Boolean, borderless: Boolean, gif: Boolean, wasDownloaded: Boolean, stickerLocator: StickerLocator? = null, contentType: String? = this?.contentType, fileName: String? = this?.fileName): Attachment? {
|
||||
if (this == null) return null
|
||||
|
||||
if (attachmentLocator != null) {
|
||||
val signalAttachmentPointer = SignalServiceAttachmentPointer(
|
||||
pointer.attachmentLocator.cdnNumber,
|
||||
SignalServiceAttachmentRemoteId.from(pointer.attachmentLocator.cdnKey),
|
||||
attachmentLocator.cdnNumber,
|
||||
SignalServiceAttachmentRemoteId.from(attachmentLocator.cdnKey),
|
||||
contentType,
|
||||
pointer.attachmentLocator.key.toByteArray(),
|
||||
Optional.ofNullable(pointer.attachmentLocator.size),
|
||||
attachmentLocator.key.toByteArray(),
|
||||
Optional.ofNullable(attachmentLocator.size),
|
||||
Optional.empty(),
|
||||
pointer.width ?: 0,
|
||||
pointer.height ?: 0,
|
||||
Optional.ofNullable(pointer.attachmentLocator.digest.toByteArray()),
|
||||
Optional.ofNullable(pointer.incrementalMac?.toByteArray()),
|
||||
pointer.incrementalMacChunkSize ?: 0,
|
||||
width ?: 0,
|
||||
height ?: 0,
|
||||
Optional.ofNullable(attachmentLocator.digest.toByteArray()),
|
||||
Optional.ofNullable(incrementalMac?.toByteArray()),
|
||||
incrementalMacChunkSize ?: 0,
|
||||
Optional.ofNullable(fileName),
|
||||
flag == MessageAttachment.Flag.VOICE_MESSAGE,
|
||||
flag == MessageAttachment.Flag.BORDERLESS,
|
||||
flag == MessageAttachment.Flag.GIF,
|
||||
Optional.ofNullable(pointer.caption),
|
||||
Optional.ofNullable(pointer.blurHash),
|
||||
pointer.attachmentLocator.uploadTimestamp
|
||||
voiceNote,
|
||||
borderless,
|
||||
gif,
|
||||
Optional.ofNullable(caption),
|
||||
Optional.ofNullable(blurHash),
|
||||
attachmentLocator.uploadTimestamp
|
||||
)
|
||||
return PointerAttachment.forPointer(
|
||||
pointer = Optional.of(signalAttachmentPointer),
|
||||
stickerLocator = stickerLocator,
|
||||
transferState = if (wasDownloaded) AttachmentTable.TRANSFER_NEEDS_RESTORE else AttachmentTable.TRANSFER_PROGRESS_PENDING
|
||||
).orNull()
|
||||
} else if (pointer.invalidAttachmentLocator != null) {
|
||||
} else if (invalidAttachmentLocator != null) {
|
||||
return TombstoneAttachment(
|
||||
contentType = contentType,
|
||||
incrementalMac = pointer.incrementalMac?.toByteArray(),
|
||||
incrementalMacChunkSize = pointer.incrementalMacChunkSize,
|
||||
width = pointer.width,
|
||||
height = pointer.height,
|
||||
caption = pointer.caption,
|
||||
blurHash = pointer.blurHash,
|
||||
voiceNote = flag == MessageAttachment.Flag.VOICE_MESSAGE,
|
||||
borderless = flag == MessageAttachment.Flag.BORDERLESS,
|
||||
gif = flag == MessageAttachment.Flag.GIF,
|
||||
incrementalMac = incrementalMac?.toByteArray(),
|
||||
incrementalMacChunkSize = incrementalMacChunkSize,
|
||||
width = width,
|
||||
height = height,
|
||||
caption = caption,
|
||||
blurHash = blurHash,
|
||||
voiceNote = voiceNote,
|
||||
borderless = borderless,
|
||||
gif = gif,
|
||||
quote = false
|
||||
)
|
||||
} else if (pointer.backupLocator != null) {
|
||||
} else if (backupLocator != null) {
|
||||
return ArchivedAttachment(
|
||||
contentType = contentType,
|
||||
size = pointer.backupLocator.size.toLong(),
|
||||
cdn = pointer.backupLocator.transitCdnNumber ?: Cdn.CDN_0.cdnNumber,
|
||||
key = pointer.backupLocator.key.toByteArray(),
|
||||
cdnKey = pointer.backupLocator.transitCdnKey,
|
||||
archiveCdn = pointer.backupLocator.cdnNumber,
|
||||
archiveMediaName = pointer.backupLocator.mediaName,
|
||||
archiveMediaId = backupState.backupKey.deriveMediaId(MediaName(pointer.backupLocator.mediaName)).encode(),
|
||||
archiveThumbnailMediaId = backupState.backupKey.deriveMediaId(MediaName.forThumbnailFromMediaName(pointer.backupLocator.mediaName)).encode(),
|
||||
digest = pointer.backupLocator.digest.toByteArray(),
|
||||
incrementalMac = pointer.incrementalMac?.toByteArray(),
|
||||
incrementalMacChunkSize = pointer.incrementalMacChunkSize,
|
||||
width = pointer.width,
|
||||
height = pointer.height,
|
||||
caption = pointer.caption,
|
||||
blurHash = pointer.blurHash,
|
||||
voiceNote = flag == MessageAttachment.Flag.VOICE_MESSAGE,
|
||||
borderless = flag == MessageAttachment.Flag.BORDERLESS,
|
||||
gif = flag == MessageAttachment.Flag.GIF,
|
||||
quote = false
|
||||
size = backupLocator.size.toLong(),
|
||||
cdn = backupLocator.transitCdnNumber ?: Cdn.CDN_0.cdnNumber,
|
||||
key = backupLocator.key.toByteArray(),
|
||||
cdnKey = backupLocator.transitCdnKey,
|
||||
archiveCdn = backupLocator.cdnNumber,
|
||||
archiveMediaName = backupLocator.mediaName,
|
||||
archiveMediaId = backupState.backupKey.deriveMediaId(MediaName(backupLocator.mediaName)).encode(),
|
||||
archiveThumbnailMediaId = backupState.backupKey.deriveMediaId(MediaName.forThumbnailFromMediaName(backupLocator.mediaName)).encode(),
|
||||
digest = backupLocator.digest.toByteArray(),
|
||||
incrementalMac = incrementalMac?.toByteArray(),
|
||||
incrementalMacChunkSize = incrementalMacChunkSize,
|
||||
width = width,
|
||||
height = height,
|
||||
caption = caption,
|
||||
blurHash = blurHash,
|
||||
voiceNote = voiceNote,
|
||||
borderless = borderless,
|
||||
gif = gif,
|
||||
quote = false,
|
||||
stickerLocator = stickerLocator
|
||||
)
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
private fun Sticker?.toLocalAttachment(): Attachment? {
|
||||
if (this == null) return null
|
||||
|
||||
return data_.toLocalAttachment(
|
||||
voiceNote = false,
|
||||
gif = false,
|
||||
borderless = false,
|
||||
wasDownloaded = true,
|
||||
stickerLocator = StickerLocator(
|
||||
packId = Hex.toStringCondensed(packId.toByteArray()),
|
||||
packKey = Hex.toStringCondensed(packKey.toByteArray()),
|
||||
stickerId = stickerId,
|
||||
emoji = emoji
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
private fun MessageAttachment.toLocalAttachment(): Attachment? {
|
||||
return pointer?.toLocalAttachment(
|
||||
voiceNote = flag == MessageAttachment.Flag.VOICE_MESSAGE,
|
||||
gif = flag == MessageAttachment.Flag.GIF,
|
||||
borderless = flag == MessageAttachment.Flag.BORDERLESS,
|
||||
wasDownloaded = wasDownloaded
|
||||
)
|
||||
}
|
||||
|
||||
private fun MessageAttachment.toLocalAttachment(contentType: String?, fileName: String?): Attachment? {
|
||||
return pointer?.toLocalAttachment(
|
||||
voiceNote = flag == MessageAttachment.Flag.VOICE_MESSAGE,
|
||||
gif = flag == MessageAttachment.Flag.GIF,
|
||||
borderless = flag == MessageAttachment.Flag.BORDERLESS,
|
||||
wasDownloaded = wasDownloaded,
|
||||
contentType = contentType,
|
||||
fileName = fileName
|
||||
)
|
||||
}
|
||||
|
||||
private fun Quote.QuotedAttachment.toLocalAttachment(): Attachment? {
|
||||
return thumbnail?.toLocalAttachment(this.contentType, this.fileName)
|
||||
?: if (this.contentType == null) null else PointerAttachment.forPointer(quotedAttachment = DataMessage.Quote.QuotedAttachment(contentType = this.contentType, fileName = this.fileName, thumbnail = null)).orNull()
|
||||
|
||||
@@ -0,0 +1,13 @@
|
||||
/*
|
||||
* Copyright 2023 Signal Messenger, LLC
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
package org.thoughtcrime.securesms.backup.v2.database
|
||||
|
||||
import org.signal.core.util.deleteAll
|
||||
import org.thoughtcrime.securesms.database.StickerTable
|
||||
|
||||
fun StickerTable.clearAllDataForBackupRestore() {
|
||||
writableDatabase.deleteAll(StickerTable.TABLE_NAME)
|
||||
}
|
||||
@@ -20,9 +20,12 @@ object ChatItemBackupProcessor {
|
||||
|
||||
fun export(exportState: ExportState, emitter: BackupFrameEmitter) {
|
||||
SignalDatabase.messages.getMessagesForBackup(exportState.backupTime, exportState.allowMediaBackup).use { chatItems ->
|
||||
for (chatItem in chatItems) {
|
||||
if (exportState.threadIds.contains(chatItem.chatId)) {
|
||||
emitter.emit(Frame(chatItem = chatItem))
|
||||
while (chatItems.hasNext()) {
|
||||
val chatItem = chatItems.next()
|
||||
if (chatItem != null) {
|
||||
if (exportState.threadIds.contains(chatItem.chatId)) {
|
||||
emitter.emit(Frame(chatItem = chatItem))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,76 @@
|
||||
/*
|
||||
* Copyright 2024 Signal Messenger, LLC
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
package org.thoughtcrime.securesms.backup.v2.processor
|
||||
|
||||
import androidx.annotation.WorkerThread
|
||||
import okio.ByteString.Companion.toByteString
|
||||
import org.signal.core.util.Hex
|
||||
import org.thoughtcrime.securesms.backup.v2.proto.Frame
|
||||
import org.thoughtcrime.securesms.backup.v2.proto.StickerPack
|
||||
import org.thoughtcrime.securesms.backup.v2.proto.StickerPackSticker
|
||||
import org.thoughtcrime.securesms.backup.v2.stream.BackupFrameEmitter
|
||||
import org.thoughtcrime.securesms.database.SignalDatabase
|
||||
import org.thoughtcrime.securesms.database.StickerTable.StickerPackRecordReader
|
||||
import org.thoughtcrime.securesms.database.StickerTable.StickerRecordReader
|
||||
import org.thoughtcrime.securesms.database.model.StickerPackRecord
|
||||
import org.thoughtcrime.securesms.database.model.StickerRecord
|
||||
import org.thoughtcrime.securesms.dependencies.AppDependencies
|
||||
import org.thoughtcrime.securesms.jobs.StickerPackDownloadJob
|
||||
|
||||
object StickerBackupProcessor {
|
||||
fun export(emitter: BackupFrameEmitter) {
|
||||
StickerPackRecordReader(SignalDatabase.stickers.allStickerPacks).use { reader ->
|
||||
var record: StickerPackRecord? = reader.next
|
||||
while (record != null) {
|
||||
if (record.isInstalled) {
|
||||
val frame = record.toBackupFrame()
|
||||
emitter.emit(frame)
|
||||
}
|
||||
record = reader.next
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun import(stickerPack: StickerPack) {
|
||||
AppDependencies.jobManager.add(
|
||||
StickerPackDownloadJob.forInstall(Hex.toStringCondensed(stickerPack.packId.toByteArray()), Hex.toStringCondensed(stickerPack.packKey.toByteArray()), false)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@WorkerThread
|
||||
private fun getStickersFromDatabase(packId: String): List<StickerPackSticker> {
|
||||
val stickers: MutableList<StickerPackSticker> = java.util.ArrayList()
|
||||
|
||||
SignalDatabase.stickers.getStickersForPack(packId).use { cursor ->
|
||||
val reader = StickerRecordReader(cursor)
|
||||
var record: StickerRecord? = reader.next
|
||||
while (record != null) {
|
||||
stickers.add(
|
||||
StickerPackSticker(
|
||||
emoji = record.emoji,
|
||||
id = record.stickerId
|
||||
)
|
||||
)
|
||||
record = reader.next
|
||||
}
|
||||
}
|
||||
return stickers
|
||||
}
|
||||
|
||||
private fun StickerPackRecord.toBackupFrame(): Frame {
|
||||
val packIdBytes = Hex.fromStringCondensed(packId)
|
||||
val packKey = Hex.fromStringCondensed(packKey)
|
||||
val stickers = getStickersFromDatabase(packId)
|
||||
val pack = StickerPack(
|
||||
packId = packIdBytes.toByteString(),
|
||||
packKey = packKey.toByteString(),
|
||||
title = title.orElse(""),
|
||||
author = author.orElse(""),
|
||||
stickers = stickers
|
||||
)
|
||||
return Frame(stickerPack = pack)
|
||||
}
|
||||
Reference in New Issue
Block a user