mirror of
https://github.com/signalapp/Signal-Android.git
synced 2026-02-25 12:17:22 +00:00
Update to latest backup protos; Bump to libsignal v0.73.1
This commit is contained in:
@@ -15,8 +15,12 @@ import java.util.UUID
|
||||
|
||||
class ArchivedAttachment : Attachment {
|
||||
|
||||
companion object {
|
||||
private const val NO_ARCHIVE_CDN = -404
|
||||
}
|
||||
|
||||
@JvmField
|
||||
val archiveCdn: Int
|
||||
val archiveCdn: Int?
|
||||
|
||||
@JvmField
|
||||
val archiveMediaName: String
|
||||
@@ -31,6 +35,7 @@ class ArchivedAttachment : Attachment {
|
||||
contentType: String?,
|
||||
size: Long,
|
||||
cdn: Int,
|
||||
uploadTimestamp: Long?,
|
||||
key: ByteArray,
|
||||
iv: ByteArray?,
|
||||
cdnKey: String?,
|
||||
@@ -71,7 +76,7 @@ class ArchivedAttachment : Attachment {
|
||||
width = width ?: 0,
|
||||
height = height ?: 0,
|
||||
incrementalMacChunkSize = incrementalMacChunkSize ?: 0,
|
||||
uploadTimestamp = 0,
|
||||
uploadTimestamp = uploadTimestamp ?: 0,
|
||||
caption = caption,
|
||||
stickerLocator = stickerLocator,
|
||||
blurHash = BlurHash.parseOrNull(blurHash),
|
||||
@@ -79,14 +84,14 @@ class ArchivedAttachment : Attachment {
|
||||
transformProperties = null,
|
||||
uuid = uuid
|
||||
) {
|
||||
this.archiveCdn = archiveCdn ?: Cdn.CDN_3.cdnNumber
|
||||
this.archiveCdn = archiveCdn
|
||||
this.archiveMediaName = archiveMediaName
|
||||
this.archiveMediaId = archiveMediaId
|
||||
this.archiveThumbnailMediaId = archiveThumbnailMediaId
|
||||
}
|
||||
|
||||
constructor(parcel: Parcel) : super(parcel) {
|
||||
archiveCdn = parcel.readInt()
|
||||
archiveCdn = parcel.readInt().takeIf { it != NO_ARCHIVE_CDN }
|
||||
archiveMediaName = parcel.readString()!!
|
||||
archiveMediaId = parcel.readString()!!
|
||||
archiveThumbnailMediaId = parcel.readString()!!
|
||||
@@ -94,7 +99,7 @@ class ArchivedAttachment : Attachment {
|
||||
|
||||
override fun writeToParcel(dest: Parcel, flags: Int) {
|
||||
super.writeToParcel(dest, flags)
|
||||
dest.writeInt(archiveCdn)
|
||||
dest.writeInt(archiveCdn ?: NO_ARCHIVE_CDN)
|
||||
dest.writeString(archiveMediaName)
|
||||
dest.writeString(archiveMediaId)
|
||||
dest.writeString(archiveThumbnailMediaId)
|
||||
|
||||
@@ -14,6 +14,10 @@ import java.util.UUID
|
||||
|
||||
class DatabaseAttachment : Attachment {
|
||||
|
||||
companion object {
|
||||
private const val NO_ARCHIVE_CDN = -404
|
||||
}
|
||||
|
||||
@JvmField
|
||||
val attachmentId: AttachmentId
|
||||
|
||||
@@ -27,7 +31,7 @@ class DatabaseAttachment : Attachment {
|
||||
val dataHash: String?
|
||||
|
||||
@JvmField
|
||||
val archiveCdn: Int
|
||||
val archiveCdn: Int?
|
||||
|
||||
@JvmField
|
||||
val thumbnailRestoreState: AttachmentTable.ThumbnailRestoreState
|
||||
@@ -69,7 +73,7 @@ class DatabaseAttachment : Attachment {
|
||||
displayOrder: Int,
|
||||
uploadTimestamp: Long,
|
||||
dataHash: String?,
|
||||
archiveCdn: Int,
|
||||
archiveCdn: Int?,
|
||||
thumbnailRestoreState: AttachmentTable.ThumbnailRestoreState,
|
||||
archiveTransferState: AttachmentTable.ArchiveTransferState,
|
||||
uuid: UUID?
|
||||
@@ -117,7 +121,7 @@ class DatabaseAttachment : Attachment {
|
||||
hasThumbnail = ParcelUtil.readBoolean(parcel)
|
||||
mmsId = parcel.readLong()
|
||||
displayOrder = parcel.readInt()
|
||||
archiveCdn = parcel.readInt()
|
||||
archiveCdn = parcel.readInt().takeIf { it != NO_ARCHIVE_CDN }
|
||||
thumbnailRestoreState = AttachmentTable.ThumbnailRestoreState.deserialize(parcel.readInt())
|
||||
archiveTransferState = AttachmentTable.ArchiveTransferState.deserialize(parcel.readInt())
|
||||
}
|
||||
@@ -130,7 +134,7 @@ class DatabaseAttachment : Attachment {
|
||||
ParcelUtil.writeBoolean(dest, hasThumbnail)
|
||||
dest.writeLong(mmsId)
|
||||
dest.writeInt(displayOrder)
|
||||
dest.writeInt(archiveCdn)
|
||||
dest.writeInt(archiveCdn ?: NO_ARCHIVE_CDN)
|
||||
dest.writeInt(thumbnailRestoreState.value)
|
||||
dest.writeInt(archiveTransferState.value)
|
||||
}
|
||||
|
||||
@@ -26,7 +26,7 @@ import org.signal.core.util.getAllTableDefinitions
|
||||
import org.signal.core.util.getAllTriggerDefinitions
|
||||
import org.signal.core.util.getForeignKeyViolations
|
||||
import org.signal.core.util.logging.Log
|
||||
import org.signal.core.util.requireInt
|
||||
import org.signal.core.util.requireIntOrNull
|
||||
import org.signal.core.util.requireNonNullBlob
|
||||
import org.signal.core.util.stream.NonClosingOutputStream
|
||||
import org.signal.core.util.urlEncode
|
||||
@@ -1637,12 +1637,10 @@ sealed class ImportResult {
|
||||
}
|
||||
|
||||
/**
|
||||
* Iterator that reads values from the given cursor. Expects that ARCHIVE_MEDIA_ID and ARCHIVE_CDN are both
|
||||
* present and non-null in the cursor.
|
||||
* Iterator that reads values from the given cursor. Expects that REMOTE_DIGEST is present and non-null, and ARCHIVE_CDN is present.
|
||||
*
|
||||
* This class does not assume ownership of the cursor. Recommended usage is within a use statement:
|
||||
*
|
||||
*
|
||||
* ```
|
||||
* databaseCall().use { cursor ->
|
||||
* val iterator = ArchivedMediaObjectIterator(cursor)
|
||||
@@ -1661,7 +1659,7 @@ class ArchiveMediaItemIterator(private val cursor: Cursor) : Iterator<ArchiveMed
|
||||
|
||||
override fun next(): ArchiveMediaItem {
|
||||
val digest = cursor.requireNonNullBlob(AttachmentTable.REMOTE_DIGEST)
|
||||
val cdn = cursor.requireInt(AttachmentTable.ARCHIVE_CDN)
|
||||
val cdn = cursor.requireIntOrNull(AttachmentTable.ARCHIVE_CDN)
|
||||
|
||||
val mediaId = MediaName.fromDigest(digest).toMediaId(SignalStore.backup.mediaRootBackupKey).encode()
|
||||
val thumbnailMediaId = MediaName.fromDigestForThumbnail(digest).toMediaId(SignalStore.backup.mediaRootBackupKey).encode()
|
||||
|
||||
@@ -9,7 +9,9 @@ import android.text.TextUtils
|
||||
import org.signal.core.util.Base64
|
||||
import org.thoughtcrime.securesms.attachments.DatabaseAttachment
|
||||
import org.thoughtcrime.securesms.attachments.InvalidAttachmentException
|
||||
import org.thoughtcrime.securesms.database.AttachmentTable
|
||||
import org.thoughtcrime.securesms.keyvalue.SignalStore
|
||||
import org.thoughtcrime.securesms.util.RemoteConfig
|
||||
import org.thoughtcrime.securesms.util.Util
|
||||
import org.whispersystems.signalservice.api.backup.MediaName
|
||||
import org.whispersystems.signalservice.api.messages.SignalServiceAttachmentPointer
|
||||
@@ -20,6 +22,7 @@ import java.util.Optional
|
||||
object DatabaseAttachmentArchiveUtil {
|
||||
@JvmStatic
|
||||
fun requireMediaName(attachment: DatabaseAttachment): MediaName {
|
||||
require(isDigestValidated(attachment))
|
||||
return MediaName.fromDigest(attachment.remoteDigest!!)
|
||||
}
|
||||
|
||||
@@ -28,18 +31,35 @@ object DatabaseAttachmentArchiveUtil {
|
||||
*/
|
||||
@JvmStatic
|
||||
fun requireMediaNameAsString(attachment: DatabaseAttachment): String {
|
||||
require(isDigestValidated(attachment))
|
||||
return MediaName.fromDigest(attachment.remoteDigest!!).name
|
||||
}
|
||||
|
||||
@JvmStatic
|
||||
fun getMediaName(attachment: DatabaseAttachment): MediaName? {
|
||||
return attachment.remoteDigest?.let { MediaName.fromDigest(it) }
|
||||
return if (isDigestValidated(attachment)) {
|
||||
attachment.remoteDigest?.let { MediaName.fromDigest(it) }
|
||||
} else {
|
||||
null
|
||||
}
|
||||
}
|
||||
|
||||
@JvmStatic
|
||||
fun requireThumbnailMediaName(attachment: DatabaseAttachment): MediaName {
|
||||
require(isDigestValidated(attachment))
|
||||
return MediaName.fromDigestForThumbnail(attachment.remoteDigest!!)
|
||||
}
|
||||
|
||||
private fun isDigestValidated(attachment: DatabaseAttachment): Boolean {
|
||||
return when (attachment.transferState) {
|
||||
AttachmentTable.TRANSFER_PROGRESS_DONE,
|
||||
AttachmentTable.TRANSFER_NEEDS_RESTORE,
|
||||
AttachmentTable.TRANSFER_RESTORE_IN_PROGRESS,
|
||||
AttachmentTable.TRANSFER_RESTORE_OFFLOADED -> true
|
||||
|
||||
else -> false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun DatabaseAttachment.requireMediaName(): MediaName {
|
||||
@@ -77,7 +97,7 @@ fun DatabaseAttachment.createArchiveAttachmentPointer(useArchiveCdn: Boolean): S
|
||||
mediaId = this.requireMediaName().toMediaId(mediaRootBackupKey).encode()
|
||||
)
|
||||
|
||||
id to archiveCdn
|
||||
id to (archiveCdn ?: RemoteConfig.backupFallbackArchiveCdn)
|
||||
} else {
|
||||
if (remoteLocation.isNullOrEmpty()) {
|
||||
throw InvalidAttachmentException("empty content id")
|
||||
@@ -131,7 +151,7 @@ fun DatabaseAttachment.createArchiveThumbnailPointer(): SignalServiceAttachmentP
|
||||
val key = mediaRootBackupKey.deriveThumbnailTransitKey(requireThumbnailMediaName())
|
||||
val mediaId = mediaRootBackupKey.deriveMediaId(requireThumbnailMediaName()).encode()
|
||||
SignalServiceAttachmentPointer(
|
||||
cdnNumber = archiveCdn,
|
||||
cdnNumber = archiveCdn ?: RemoteConfig.backupFallbackArchiveCdn,
|
||||
remoteId = SignalServiceAttachmentRemoteId.Backup(
|
||||
mediaCdnPath = mediaCdnPath,
|
||||
mediaId = mediaId
|
||||
|
||||
@@ -27,6 +27,7 @@ import org.thoughtcrime.securesms.components.settings.app.usernamelinks.Username
|
||||
import org.thoughtcrime.securesms.conversation.colors.ChatColors
|
||||
import org.thoughtcrime.securesms.database.SignalDatabase
|
||||
import org.thoughtcrime.securesms.database.model.InAppPaymentSubscriberRecord
|
||||
import org.thoughtcrime.securesms.database.model.databaseprotos.InAppPaymentData
|
||||
import org.thoughtcrime.securesms.dependencies.AppDependencies
|
||||
import org.thoughtcrime.securesms.jobs.RetrieveProfileAvatarJob
|
||||
import org.thoughtcrime.securesms.keyvalue.PhoneNumberPrivacyValues
|
||||
@@ -37,6 +38,8 @@ import org.thoughtcrime.securesms.recipients.RecipientId
|
||||
import org.thoughtcrime.securesms.util.ProfileUtil
|
||||
import org.thoughtcrime.securesms.util.TextSecurePreferences
|
||||
import org.whispersystems.signalservice.api.push.UsernameLinkComponents
|
||||
import org.whispersystems.signalservice.api.storage.IAPSubscriptionId.AppleIAPOriginalTransactionId
|
||||
import org.whispersystems.signalservice.api.storage.IAPSubscriptionId.GooglePlayBillingPurchaseToken
|
||||
import org.whispersystems.signalservice.api.subscriptions.SubscriberId
|
||||
import org.whispersystems.signalservice.api.util.UuidUtil
|
||||
import org.whispersystems.signalservice.api.util.toByteArray
|
||||
@@ -61,6 +64,8 @@ object AccountDataArchiveProcessor {
|
||||
val chatColors = SignalStore.chatColors.chatColors
|
||||
val chatWallpaper = SignalStore.wallpaper.currentRawWallpaper
|
||||
|
||||
val backupSubscriberRecord = db.inAppPaymentSubscriberTable.getBackupsSubscriber()
|
||||
|
||||
emitter.emit(
|
||||
Frame(
|
||||
account = AccountData(
|
||||
@@ -98,6 +103,7 @@ object AccountDataArchiveProcessor {
|
||||
hasSeenGroupStoryEducationSheet = signalStore.storyValues.userHasSeenGroupStoryEducationSheet,
|
||||
hasCompletedUsernameOnboarding = signalStore.uiHintValues.hasCompletedUsernameOnboarding(),
|
||||
customChatColors = db.chatColorsTable.getSavedChatColors().toRemoteChatColors(),
|
||||
optimizeOnDeviceStorage = signalStore.backupValues.optimizeStorage,
|
||||
defaultChatStyle = ChatStyleConverter.constructRemoteChatStyle(
|
||||
db = db,
|
||||
chatColors = chatColors,
|
||||
@@ -105,7 +111,8 @@ object AccountDataArchiveProcessor {
|
||||
chatWallpaper = chatWallpaper
|
||||
)
|
||||
),
|
||||
donationSubscriberData = donationSubscriber?.toSubscriberData(signalStore.inAppPaymentValues.isDonationSubscriptionManuallyCancelled())
|
||||
donationSubscriberData = donationSubscriber?.toSubscriberData(signalStore.inAppPaymentValues.isDonationSubscriptionManuallyCancelled()),
|
||||
backupsSubscriberData = backupSubscriberRecord?.toIAPSubscriberData()
|
||||
)
|
||||
)
|
||||
)
|
||||
@@ -148,6 +155,26 @@ object AccountDataArchiveProcessor {
|
||||
}
|
||||
}
|
||||
|
||||
if (accountData.backupsSubscriberData != null && accountData.backupsSubscriberData.subscriberId.size > 0 && (accountData.backupsSubscriberData.purchaseToken != null || accountData.backupsSubscriberData.originalTransactionId != null)) {
|
||||
val remoteSubscriberId = SubscriberId.fromBytes(accountData.backupsSubscriberData.subscriberId.toByteArray())
|
||||
val localSubscriber = InAppPaymentsRepository.getSubscriber(InAppPaymentSubscriberRecord.Type.BACKUP)
|
||||
|
||||
val subscriber = InAppPaymentSubscriberRecord(
|
||||
subscriberId = remoteSubscriberId,
|
||||
currency = localSubscriber?.currency,
|
||||
type = InAppPaymentSubscriberRecord.Type.BACKUP,
|
||||
requiresCancel = localSubscriber?.requiresCancel ?: false,
|
||||
paymentMethodType = InAppPaymentData.PaymentMethodType.UNKNOWN,
|
||||
iapSubscriptionId = if (accountData.backupsSubscriberData.purchaseToken != null) {
|
||||
GooglePlayBillingPurchaseToken(accountData.backupsSubscriberData.purchaseToken)
|
||||
} else {
|
||||
AppleIAPOriginalTransactionId(accountData.backupsSubscriberData.originalTransactionId!!)
|
||||
}
|
||||
)
|
||||
|
||||
InAppPaymentsRepository.setSubscriber(subscriber)
|
||||
}
|
||||
|
||||
if (accountData.avatarUrlPath.isNotEmpty()) {
|
||||
AppDependencies.jobManager.add(RetrieveProfileAvatarJob(Recipient.self().fresh(), accountData.avatarUrlPath))
|
||||
}
|
||||
@@ -184,6 +211,7 @@ object AccountDataArchiveProcessor {
|
||||
SignalStore.story.isFeatureDisabled = settings.storiesDisabled
|
||||
SignalStore.story.userHasSeenGroupStoryEducationSheet = settings.hasSeenGroupStoryEducationSheet
|
||||
SignalStore.story.viewedReceiptsEnabled = settings.storyViewReceiptsEnabled ?: settings.readReceipts
|
||||
SignalStore.backup.optimizeStorage = settings.optimizeOnDeviceStorage
|
||||
|
||||
settings.customChatColors
|
||||
.mapNotNull { chatColor ->
|
||||
@@ -288,6 +316,23 @@ object AccountDataArchiveProcessor {
|
||||
return AccountData.SubscriberData(subscriberId = subscriberId, currencyCode = currencyCode, manuallyCancelled = manuallyCancelled)
|
||||
}
|
||||
|
||||
private fun InAppPaymentSubscriberRecord?.toIAPSubscriberData(): AccountData.IAPSubscriberData? {
|
||||
if (this == null) {
|
||||
return null
|
||||
}
|
||||
|
||||
val builder = AccountData.IAPSubscriberData.Builder()
|
||||
.subscriberId(this.subscriberId.bytes.toByteString())
|
||||
|
||||
if (this.iapSubscriptionId?.purchaseToken != null) {
|
||||
builder.purchaseToken(this.iapSubscriptionId.purchaseToken)
|
||||
} else if (this.iapSubscriptionId?.originalTransactionId != null) {
|
||||
builder.originalTransactionId(this.iapSubscriptionId.originalTransactionId)
|
||||
}
|
||||
|
||||
return builder.build()
|
||||
}
|
||||
|
||||
private fun List<ChatColors>.toRemoteChatColors(): List<ChatStyle.CustomChatColor> {
|
||||
return this
|
||||
.mapNotNull { local ->
|
||||
|
||||
@@ -9,6 +9,7 @@ import okio.ByteString
|
||||
import okio.ByteString.Companion.toByteString
|
||||
import org.signal.core.util.Base64
|
||||
import org.signal.core.util.emptyIfNull
|
||||
import org.signal.core.util.isNotNullOrBlank
|
||||
import org.signal.core.util.nullIfBlank
|
||||
import org.signal.core.util.orNull
|
||||
import org.thoughtcrime.securesms.attachments.ArchivedAttachment
|
||||
@@ -45,19 +46,22 @@ fun FilePointer?.toLocalAttachment(
|
||||
uuid: ByteString? = null,
|
||||
quote: Boolean = false
|
||||
): Attachment? {
|
||||
if (this == null) return null
|
||||
if (this == null || this.locatorInfo == null) return null
|
||||
|
||||
if (this.attachmentLocator != null) {
|
||||
val hasMediaName = this.locatorInfo.mediaName.isNotEmpty()
|
||||
val hasTransitInfo = this.locatorInfo.transitCdnKey != null
|
||||
|
||||
if (hasTransitInfo && !hasMediaName) {
|
||||
val signalAttachmentPointer = SignalServiceAttachmentPointer(
|
||||
cdnNumber = this.attachmentLocator.cdnNumber,
|
||||
remoteId = SignalServiceAttachmentRemoteId.from(attachmentLocator.cdnKey),
|
||||
cdnNumber = this.locatorInfo.transitCdnNumber ?: Cdn.CDN_0.cdnNumber,
|
||||
remoteId = SignalServiceAttachmentRemoteId.from(locatorInfo.transitCdnKey),
|
||||
contentType = contentType,
|
||||
key = this.attachmentLocator.key.toByteArray(),
|
||||
size = Optional.ofNullable(attachmentLocator.size),
|
||||
key = this.locatorInfo.key.toByteArray(),
|
||||
size = Optional.ofNullable(locatorInfo.size),
|
||||
preview = Optional.empty(),
|
||||
width = this.width ?: 0,
|
||||
height = this.height ?: 0,
|
||||
digest = Optional.ofNullable(this.attachmentLocator.digest.toByteArray()),
|
||||
digest = Optional.ofNullable(this.locatorInfo.digest.toByteArray()),
|
||||
incrementalDigest = Optional.ofNullable(this.incrementalMac?.toByteArray()),
|
||||
incrementalMacChunkSize = this.incrementalMacChunkSize ?: 0,
|
||||
fileName = Optional.ofNullable(fileName),
|
||||
@@ -66,7 +70,7 @@ fun FilePointer?.toLocalAttachment(
|
||||
isGif = gif,
|
||||
caption = Optional.ofNullable(this.caption),
|
||||
blurHash = Optional.ofNullable(this.blurHash),
|
||||
uploadTimestamp = this.attachmentLocator.uploadTimestamp?.clampToValidBackupRange() ?: 0,
|
||||
uploadTimestamp = this.locatorInfo.transitTierUploadTimestamp?.clampToValidBackupRange() ?: 0,
|
||||
uuid = UuidUtil.fromByteStringOrNull(uuid)
|
||||
)
|
||||
return PointerAttachment.forPointer(
|
||||
@@ -74,7 +78,7 @@ fun FilePointer?.toLocalAttachment(
|
||||
stickerLocator = stickerLocator,
|
||||
transferState = if (wasDownloaded) AttachmentTable.TRANSFER_NEEDS_RESTORE else AttachmentTable.TRANSFER_PROGRESS_PENDING
|
||||
).orNull()
|
||||
} else if (this.invalidAttachmentLocator != null) {
|
||||
} else if (!hasMediaName) {
|
||||
return TombstoneAttachment(
|
||||
contentType = contentType,
|
||||
incrementalMac = this.incrementalMac?.toByteArray(),
|
||||
@@ -91,19 +95,20 @@ fun FilePointer?.toLocalAttachment(
|
||||
stickerLocator = stickerLocator,
|
||||
uuid = UuidUtil.fromByteStringOrNull(uuid)
|
||||
)
|
||||
} else if (this.backupLocator != null) {
|
||||
} else {
|
||||
return ArchivedAttachment(
|
||||
contentType = contentType,
|
||||
size = this.backupLocator.size.toLong(),
|
||||
cdn = this.backupLocator.transitCdnNumber ?: Cdn.CDN_0.cdnNumber,
|
||||
key = this.backupLocator.key.toByteArray(),
|
||||
size = this.locatorInfo.size.toLong(),
|
||||
cdn = this.locatorInfo.transitCdnNumber ?: Cdn.CDN_0.cdnNumber,
|
||||
uploadTimestamp = this.locatorInfo.transitTierUploadTimestamp ?: 0,
|
||||
key = this.locatorInfo.key.toByteArray(),
|
||||
iv = null,
|
||||
cdnKey = this.backupLocator.transitCdnKey?.nullIfBlank(),
|
||||
archiveCdn = this.backupLocator.cdnNumber,
|
||||
archiveMediaName = this.backupLocator.mediaName,
|
||||
archiveMediaId = importState.mediaRootBackupKey.deriveMediaId(MediaName(this.backupLocator.mediaName)).encode(),
|
||||
archiveThumbnailMediaId = importState.mediaRootBackupKey.deriveMediaId(MediaName.forThumbnailFromMediaName(this.backupLocator.mediaName)).encode(),
|
||||
digest = this.backupLocator.digest.toByteArray(),
|
||||
cdnKey = this.locatorInfo.transitCdnKey?.nullIfBlank(),
|
||||
archiveCdn = this.locatorInfo.mediaTierCdnNumber,
|
||||
archiveMediaName = this.locatorInfo.mediaName,
|
||||
archiveMediaId = importState.mediaRootBackupKey.deriveMediaId(MediaName(this.locatorInfo.mediaName)).encode(),
|
||||
archiveThumbnailMediaId = importState.mediaRootBackupKey.deriveMediaId(MediaName.forThumbnailFromMediaName(this.locatorInfo.mediaName)).encode(),
|
||||
digest = this.locatorInfo.digest.toByteArray(),
|
||||
incrementalMac = this.incrementalMac?.toByteArray(),
|
||||
incrementalMacChunkSize = this.incrementalMacChunkSize,
|
||||
width = this.width,
|
||||
@@ -119,7 +124,6 @@ fun FilePointer?.toLocalAttachment(
|
||||
fileName = fileName
|
||||
)
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -136,49 +140,85 @@ fun DatabaseAttachment.toRemoteFilePointer(mediaArchiveEnabled: Boolean, content
|
||||
builder.caption = this.caption
|
||||
builder.blurHash = this.blurHash?.hash
|
||||
|
||||
if (this.remoteKey.isNullOrBlank() || this.remoteDigest == null || this.size == 0L) {
|
||||
builder.invalidAttachmentLocator = FilePointer.InvalidAttachmentLocator()
|
||||
return builder.build()
|
||||
builder.setLegacyLocators(this, mediaArchiveEnabled)
|
||||
builder.locatorInfo = this.toLocatorInfo()
|
||||
|
||||
return builder.build()
|
||||
}
|
||||
|
||||
fun FilePointer.Builder.setLegacyLocators(attachment: DatabaseAttachment, mediaArchiveEnabled: Boolean) {
|
||||
if (attachment.remoteKey.isNullOrBlank() || attachment.remoteDigest == null || attachment.size == 0L) {
|
||||
this.invalidAttachmentLocator = FilePointer.InvalidAttachmentLocator()
|
||||
return
|
||||
}
|
||||
|
||||
if (this.transferState == AttachmentTable.TRANSFER_PROGRESS_PERMANENT_FAILURE && this.archiveTransferState != AttachmentTable.ArchiveTransferState.FINISHED) {
|
||||
builder.invalidAttachmentLocator = FilePointer.InvalidAttachmentLocator()
|
||||
return builder.build()
|
||||
if (attachment.transferState == AttachmentTable.TRANSFER_PROGRESS_PERMANENT_FAILURE && attachment.archiveTransferState != AttachmentTable.ArchiveTransferState.FINISHED) {
|
||||
this.invalidAttachmentLocator = FilePointer.InvalidAttachmentLocator()
|
||||
return
|
||||
}
|
||||
|
||||
val pending = this.archiveTransferState != AttachmentTable.ArchiveTransferState.FINISHED && (this.transferState != AttachmentTable.TRANSFER_PROGRESS_DONE && this.transferState != AttachmentTable.TRANSFER_RESTORE_OFFLOADED)
|
||||
val pending = attachment.archiveTransferState != AttachmentTable.ArchiveTransferState.FINISHED && (attachment.transferState != AttachmentTable.TRANSFER_PROGRESS_DONE && attachment.transferState != AttachmentTable.TRANSFER_RESTORE_OFFLOADED)
|
||||
|
||||
if (mediaArchiveEnabled && !pending) {
|
||||
val transitCdnKey = this.remoteLocation?.nullIfBlank()
|
||||
val transitCdnNumber = this.cdn.cdnNumber.takeIf { transitCdnKey != null }
|
||||
val archiveMediaName = this.getMediaName()?.toString()
|
||||
val transitCdnKey = attachment.remoteLocation?.nullIfBlank()
|
||||
val transitCdnNumber = attachment.cdn.cdnNumber.takeIf { transitCdnKey != null }
|
||||
val archiveMediaName = attachment.getMediaName()?.toString()
|
||||
|
||||
builder.backupLocator = FilePointer.BackupLocator(
|
||||
this.backupLocator = FilePointer.BackupLocator(
|
||||
mediaName = archiveMediaName.emptyIfNull(),
|
||||
cdnNumber = this.archiveCdn.takeIf { archiveMediaName != null },
|
||||
key = Base64.decode(remoteKey).toByteString(),
|
||||
size = this.size.toInt(),
|
||||
digest = this.remoteDigest.toByteString(),
|
||||
cdnNumber = attachment.archiveCdn.takeIf { archiveMediaName != null },
|
||||
key = Base64.decode(attachment.remoteKey).toByteString(),
|
||||
size = attachment.size.toInt(),
|
||||
digest = attachment.remoteDigest.toByteString(),
|
||||
transitCdnNumber = transitCdnNumber,
|
||||
transitCdnKey = transitCdnKey
|
||||
)
|
||||
return builder.build()
|
||||
return
|
||||
}
|
||||
|
||||
if (this.remoteLocation.isNullOrBlank()) {
|
||||
builder.invalidAttachmentLocator = FilePointer.InvalidAttachmentLocator()
|
||||
return builder.build()
|
||||
if (attachment.remoteLocation.isNullOrBlank()) {
|
||||
this.invalidAttachmentLocator = FilePointer.InvalidAttachmentLocator()
|
||||
return
|
||||
}
|
||||
|
||||
builder.attachmentLocator = FilePointer.AttachmentLocator(
|
||||
cdnKey = this.remoteLocation,
|
||||
cdnNumber = this.cdn.cdnNumber,
|
||||
uploadTimestamp = this.uploadTimestamp.takeIf { it > 0 }?.clampToValidBackupRange(),
|
||||
key = Base64.decode(remoteKey).toByteString(),
|
||||
size = this.size.toInt(),
|
||||
digest = this.remoteDigest.toByteString()
|
||||
this.attachmentLocator = FilePointer.AttachmentLocator(
|
||||
cdnKey = attachment.remoteLocation,
|
||||
cdnNumber = attachment.cdn.cdnNumber,
|
||||
uploadTimestamp = attachment.uploadTimestamp.takeIf { it > 0 }?.clampToValidBackupRange(),
|
||||
key = Base64.decode(attachment.remoteKey).toByteString(),
|
||||
size = attachment.size.toInt(),
|
||||
digest = attachment.remoteDigest.toByteString()
|
||||
)
|
||||
return builder.build()
|
||||
}
|
||||
|
||||
fun DatabaseAttachment.toLocatorInfo(): FilePointer.LocatorInfo {
|
||||
if (this.remoteKey.isNullOrBlank() || this.remoteDigest == null || this.size == 0L) {
|
||||
return FilePointer.LocatorInfo()
|
||||
}
|
||||
|
||||
if (this.transferState == AttachmentTable.TRANSFER_PROGRESS_PERMANENT_FAILURE && this.archiveTransferState != AttachmentTable.ArchiveTransferState.FINISHED) {
|
||||
return FilePointer.LocatorInfo()
|
||||
}
|
||||
|
||||
val locatorBuilder = FilePointer.LocatorInfo.Builder()
|
||||
|
||||
val remoteKey = Base64.decode(this.remoteKey).toByteString()
|
||||
val archiveMediaName = this.getMediaName()?.toString()
|
||||
|
||||
locatorBuilder.key = remoteKey
|
||||
locatorBuilder.digest = this.remoteDigest.toByteString()
|
||||
locatorBuilder.size = this.size.toInt()
|
||||
|
||||
if (this.remoteLocation.isNotNullOrBlank()) {
|
||||
locatorBuilder.transitCdnKey = this.remoteLocation
|
||||
locatorBuilder.transitCdnNumber = this.cdn.cdnNumber
|
||||
locatorBuilder.transitTierUploadTimestamp = this.uploadTimestamp.takeIf { it > 0 }?.clampToValidBackupRange()
|
||||
}
|
||||
|
||||
locatorBuilder.mediaTierCdnNumber = this.archiveCdn?.takeIf { archiveMediaName != null }
|
||||
locatorBuilder.mediaName = archiveMediaName.emptyIfNull()
|
||||
|
||||
return locatorBuilder.build()
|
||||
}
|
||||
|
||||
fun Long.clampToValidBackupRange(): Long {
|
||||
|
||||
@@ -54,6 +54,7 @@ import org.signal.core.util.readToSingleObject
|
||||
import org.signal.core.util.requireBlob
|
||||
import org.signal.core.util.requireBoolean
|
||||
import org.signal.core.util.requireInt
|
||||
import org.signal.core.util.requireIntOrNull
|
||||
import org.signal.core.util.requireLong
|
||||
import org.signal.core.util.requireNonNullBlob
|
||||
import org.signal.core.util.requireNonNullString
|
||||
@@ -264,7 +265,7 @@ class AttachmentTable(
|
||||
$UPLOAD_TIMESTAMP INTEGER DEFAULT 0,
|
||||
$DATA_HASH_START TEXT DEFAULT NULL,
|
||||
$DATA_HASH_END TEXT DEFAULT NULL,
|
||||
$ARCHIVE_CDN INTEGER DEFAULT 0,
|
||||
$ARCHIVE_CDN INTEGER DEFAULT NULL,
|
||||
$ARCHIVE_TRANSFER_FILE TEXT DEFAULT NULL,
|
||||
$ARCHIVE_TRANSFER_STATE INTEGER DEFAULT ${ArchiveTransferState.NONE.value},
|
||||
$THUMBNAIL_FILE TEXT DEFAULT NULL,
|
||||
@@ -705,7 +706,7 @@ class AttachmentTable(
|
||||
.update(TABLE_NAME)
|
||||
.values(
|
||||
ARCHIVE_TRANSFER_STATE to ArchiveTransferState.NONE.value,
|
||||
ARCHIVE_CDN to 0
|
||||
ARCHIVE_CDN to null
|
||||
)
|
||||
.where("$REMOTE_DIGEST = ?", digest)
|
||||
.run()
|
||||
@@ -1977,7 +1978,7 @@ class AttachmentTable(
|
||||
displayOrder = jsonObject.getInt(DISPLAY_ORDER),
|
||||
uploadTimestamp = jsonObject.getLong(UPLOAD_TIMESTAMP),
|
||||
dataHash = jsonObject.getString(DATA_HASH_END),
|
||||
archiveCdn = jsonObject.getInt(ARCHIVE_CDN),
|
||||
archiveCdn = if (jsonObject.isNull(ARCHIVE_CDN)) null else jsonObject.getInt(ARCHIVE_CDN),
|
||||
thumbnailRestoreState = ThumbnailRestoreState.deserialize(jsonObject.getInt(THUMBNAIL_RESTORE_STATE)),
|
||||
archiveTransferState = ArchiveTransferState.deserialize(jsonObject.getInt(ARCHIVE_TRANSFER_STATE)),
|
||||
uuid = UuidUtil.parseOrNull(jsonObject.getString(ATTACHMENT_UUID))
|
||||
@@ -2064,7 +2065,7 @@ class AttachmentTable(
|
||||
writableDatabase
|
||||
.update(TABLE_NAME)
|
||||
.values(
|
||||
ARCHIVE_CDN to 0
|
||||
ARCHIVE_CDN to null
|
||||
)
|
||||
.where(query.where, query.whereArgs)
|
||||
.run()
|
||||
@@ -2075,7 +2076,7 @@ class AttachmentTable(
|
||||
writableDatabase
|
||||
.updateAll(TABLE_NAME)
|
||||
.values(
|
||||
ARCHIVE_CDN to 0,
|
||||
ARCHIVE_CDN to null,
|
||||
ARCHIVE_TRANSFER_STATE to ArchiveTransferState.NONE.value
|
||||
)
|
||||
.run()
|
||||
@@ -2630,7 +2631,7 @@ class AttachmentTable(
|
||||
displayOrder = cursor.requireInt(DISPLAY_ORDER),
|
||||
uploadTimestamp = cursor.requireLong(UPLOAD_TIMESTAMP),
|
||||
dataHash = cursor.requireString(DATA_HASH_END),
|
||||
archiveCdn = cursor.requireInt(ARCHIVE_CDN),
|
||||
archiveCdn = cursor.requireIntOrNull(ARCHIVE_CDN),
|
||||
thumbnailRestoreState = ThumbnailRestoreState.deserialize(cursor.requireInt(THUMBNAIL_RESTORE_STATE)),
|
||||
archiveTransferState = ArchiveTransferState.deserialize(cursor.requireInt(ARCHIVE_TRANSFER_STATE)),
|
||||
uuid = UuidUtil.parseOrNull(cursor.requireString(ATTACHMENT_UUID))
|
||||
@@ -2658,7 +2659,7 @@ class AttachmentTable(
|
||||
hashEnd = this.requireString(DATA_HASH_END),
|
||||
transformProperties = TransformProperties.parse(this.requireString(TRANSFORM_PROPERTIES)),
|
||||
uploadTimestamp = this.requireLong(UPLOAD_TIMESTAMP),
|
||||
archiveCdn = this.requireInt(ARCHIVE_CDN),
|
||||
archiveCdn = this.requireIntOrNull(ARCHIVE_CDN),
|
||||
archiveTransferState = this.requireInt(ARCHIVE_TRANSFER_STATE),
|
||||
thumbnailFile = this.requireString(THUMBNAIL_FILE),
|
||||
thumbnailRandom = this.requireBlob(THUMBNAIL_RANDOM),
|
||||
@@ -2726,7 +2727,7 @@ class AttachmentTable(
|
||||
val hashEnd: String?,
|
||||
val transformProperties: TransformProperties,
|
||||
val uploadTimestamp: Long,
|
||||
val archiveCdn: Int,
|
||||
val archiveCdn: Int?,
|
||||
val archiveTransferState: Int,
|
||||
val thumbnailFile: String?,
|
||||
val thumbnailRandom: ByteArray?,
|
||||
|
||||
@@ -15,6 +15,7 @@ import org.signal.core.util.readToList
|
||||
import org.signal.core.util.readToSet
|
||||
import org.signal.core.util.requireBoolean
|
||||
import org.signal.core.util.requireInt
|
||||
import org.signal.core.util.requireIntOrNull
|
||||
import org.signal.core.util.requireNonNullBlob
|
||||
import org.signal.core.util.requireNonNullString
|
||||
import org.signal.core.util.select
|
||||
@@ -257,7 +258,7 @@ class BackupMediaSnapshotTable(context: Context, database: SignalDatabase) : Dat
|
||||
class ArchiveMediaItem(
|
||||
val mediaId: String,
|
||||
val thumbnailMediaId: String,
|
||||
val cdn: Int,
|
||||
val cdn: Int?,
|
||||
val digest: ByteArray
|
||||
)
|
||||
|
||||
@@ -268,7 +269,7 @@ class BackupMediaSnapshotTable(context: Context, database: SignalDatabase) : Dat
|
||||
|
||||
class MediaEntry(
|
||||
val mediaId: String,
|
||||
val cdn: Int,
|
||||
val cdn: Int?,
|
||||
val digest: ByteArray,
|
||||
val isThumbnail: Boolean
|
||||
) {
|
||||
@@ -276,7 +277,7 @@ class BackupMediaSnapshotTable(context: Context, database: SignalDatabase) : Dat
|
||||
fun fromCursor(cursor: Cursor): MediaEntry {
|
||||
return MediaEntry(
|
||||
mediaId = cursor.requireNonNullString(MEDIA_ID),
|
||||
cdn = cursor.requireInt(CDN),
|
||||
cdn = cursor.requireIntOrNull(CDN),
|
||||
digest = cursor.requireNonNullBlob(REMOTE_DIGEST),
|
||||
isThumbnail = cursor.requireBoolean(IS_THUMBNAIL)
|
||||
)
|
||||
|
||||
@@ -130,6 +130,7 @@ import org.thoughtcrime.securesms.database.helpers.migration.V272_UpdateUnreadCo
|
||||
import org.thoughtcrime.securesms.database.helpers.migration.V273_FixUnreadOriginalMessages
|
||||
import org.thoughtcrime.securesms.database.helpers.migration.V274_BackupMediaSnapshotLastSeenOnRemote
|
||||
import org.thoughtcrime.securesms.database.helpers.migration.V275_EnsureDefaultAllChatsFolder
|
||||
import org.thoughtcrime.securesms.database.helpers.migration.V276_AttachmentCdnDefaultValueMigration
|
||||
import org.thoughtcrime.securesms.database.SQLiteDatabase as SignalSqliteDatabase
|
||||
|
||||
/**
|
||||
@@ -265,10 +266,11 @@ object SignalDatabaseMigrations {
|
||||
272 to V272_UpdateUnreadCountIndices,
|
||||
273 to V273_FixUnreadOriginalMessages,
|
||||
274 to V274_BackupMediaSnapshotLastSeenOnRemote,
|
||||
275 to V275_EnsureDefaultAllChatsFolder
|
||||
275 to V275_EnsureDefaultAllChatsFolder,
|
||||
276 to V276_AttachmentCdnDefaultValueMigration
|
||||
)
|
||||
|
||||
const val DATABASE_VERSION = 275
|
||||
const val DATABASE_VERSION = 276
|
||||
|
||||
@JvmStatic
|
||||
fun migrate(context: Application, db: SignalSqliteDatabase, oldVersion: Int, newVersion: Int) {
|
||||
|
||||
@@ -0,0 +1,108 @@
|
||||
/*
|
||||
* Copyright 2025 Signal Messenger, LLC
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
package org.thoughtcrime.securesms.database.helpers.migration
|
||||
|
||||
import android.app.Application
|
||||
import org.signal.core.util.Stopwatch
|
||||
import org.signal.core.util.logging.Log
|
||||
import org.thoughtcrime.securesms.database.SQLiteDatabase
|
||||
|
||||
/**
|
||||
* We want to be able to distinguish between an unset CDN (null) and CDN 0. But we default the current CDN values to zero.
|
||||
* This migration updates things so that the CDN columns default to null. We also consider all current CDN 0's to actually be unset values.
|
||||
*/
|
||||
object V276_AttachmentCdnDefaultValueMigration : SignalDatabaseMigration {
|
||||
|
||||
private val TAG = Log.tag(V276_AttachmentCdnDefaultValueMigration::class)
|
||||
|
||||
override fun migrate(context: Application, db: SQLiteDatabase, oldVersion: Int, newVersion: Int) {
|
||||
val stopwatch = Stopwatch("v276")
|
||||
|
||||
db.execSQL("UPDATE attachment SET archive_cdn = NULL WHERE archive_cdn = 0")
|
||||
stopwatch.split("fix-old-data")
|
||||
|
||||
db.execSQL(
|
||||
"""
|
||||
CREATE TABLE attachment_tmp (
|
||||
_id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
message_id INTEGER,
|
||||
content_type TEXT,
|
||||
remote_key TEXT,
|
||||
remote_location TEXT,
|
||||
remote_digest BLOB,
|
||||
remote_incremental_digest BLOB,
|
||||
remote_incremental_digest_chunk_size INTEGER DEFAULT 0,
|
||||
cdn_number INTEGER DEFAULT 0,
|
||||
transfer_state INTEGER,
|
||||
transfer_file TEXT DEFAULT NULL,
|
||||
data_file TEXT,
|
||||
data_size INTEGER,
|
||||
data_random BLOB,
|
||||
file_name TEXT,
|
||||
fast_preflight_id TEXT,
|
||||
voice_note INTEGER DEFAULT 0,
|
||||
borderless INTEGER DEFAULT 0,
|
||||
video_gif INTEGER DEFAULT 0,
|
||||
quote INTEGER DEFAULT 0,
|
||||
width INTEGER DEFAULT 0,
|
||||
height INTEGER DEFAULT 0,
|
||||
caption TEXT DEFAULT NULL,
|
||||
sticker_pack_id TEXT DEFAULT NULL,
|
||||
sticker_pack_key DEFAULT NULL,
|
||||
sticker_id INTEGER DEFAULT -1,
|
||||
sticker_emoji STRING DEFAULT NULL,
|
||||
blur_hash TEXT DEFAULT NULL,
|
||||
transform_properties TEXT DEFAULT NULL,
|
||||
display_order INTEGER DEFAULT 0,
|
||||
upload_timestamp INTEGER DEFAULT 0,
|
||||
data_hash_start TEXT DEFAULT NULL,
|
||||
data_hash_end TEXT DEFAULT NULL,
|
||||
archive_cdn INTEGER DEFAULT NULL,
|
||||
archive_transfer_file TEXT DEFAULT NULL,
|
||||
archive_transfer_state INTEGER DEFAULT 0,
|
||||
thumbnail_file TEXT DEFAULT NULL,
|
||||
thumbnail_random BLOB DEFAULT NULL,
|
||||
thumbnail_restore_state INTEGER DEFAULT 0,
|
||||
attachment_uuid TEXT DEFAULT NULL,
|
||||
remote_iv BLOB DEFAULT NULL,
|
||||
offload_restored_at INTEGER DEFAULT 0
|
||||
)
|
||||
"""
|
||||
)
|
||||
stopwatch.split("create-new-table")
|
||||
|
||||
db.execSQL("INSERT INTO attachment_tmp SELECT * FROM attachment")
|
||||
stopwatch.split("copy-data")
|
||||
|
||||
db.execSQL("DROP TABLE attachment")
|
||||
stopwatch.split("drop-table")
|
||||
|
||||
db.execSQL("ALTER TABLE attachment_tmp RENAME TO attachment")
|
||||
stopwatch.split("rename-table")
|
||||
|
||||
db.execSQL("CREATE INDEX IF NOT EXISTS attachment_message_id_index ON attachment (message_id);")
|
||||
db.execSQL("CREATE INDEX IF NOT EXISTS attachment_transfer_state_index ON attachment (transfer_state);")
|
||||
db.execSQL("CREATE INDEX IF NOT EXISTS attachment_sticker_pack_id_index ON attachment (sticker_pack_id);")
|
||||
db.execSQL("CREATE INDEX IF NOT EXISTS attachment_data_hash_start_index ON attachment (data_hash_start);")
|
||||
db.execSQL("CREATE INDEX IF NOT EXISTS attachment_data_hash_end_index ON attachment (data_hash_end);")
|
||||
db.execSQL("CREATE INDEX IF NOT EXISTS attachment_data_index ON attachment (data_file);")
|
||||
db.execSQL("CREATE INDEX IF NOT EXISTS attachment_archive_transfer_state ON attachment (archive_transfer_state);")
|
||||
db.execSQL("CREATE INDEX IF NOT EXISTS attachment_remote_digest_index ON attachment (remote_digest);")
|
||||
stopwatch.split("create-indexes")
|
||||
|
||||
db.execSQL(
|
||||
"""
|
||||
CREATE TRIGGER msl_attachment_delete AFTER DELETE ON attachment
|
||||
BEGIN
|
||||
DELETE FROM msl_payload WHERE _id IN (SELECT payload_id FROM msl_message WHERE msl_message.message_id = old.message_id);
|
||||
END
|
||||
"""
|
||||
)
|
||||
stopwatch.split("create-triggers")
|
||||
|
||||
stopwatch.stop(TAG)
|
||||
}
|
||||
}
|
||||
@@ -132,8 +132,6 @@ class ArchiveThumbnailUploadJob private constructor(
|
||||
return Result.retry(defaultBackoff())
|
||||
}
|
||||
|
||||
val mediaSecrets = mediaRootBackupKey.deriveMediaSecrets(attachment.requireThumbnailMediaName())
|
||||
|
||||
return when (val result = BackupRepository.copyThumbnailToArchive(attachmentPointer, attachment)) {
|
||||
is NetworkResult.Success -> {
|
||||
// save attachment thumbnail
|
||||
|
||||
@@ -241,7 +241,7 @@ class RestoreAttachmentJob private constructor(
|
||||
|
||||
val downloadResult = if (useArchiveCdn) {
|
||||
archiveFile = SignalDatabase.attachments.getOrCreateArchiveTransferFile(attachmentId)
|
||||
val cdnCredentials = BackupRepository.getCdnReadCredentials(BackupRepository.CredentialType.MEDIA, attachment.archiveCdn).successOrThrow().headers
|
||||
val cdnCredentials = BackupRepository.getCdnReadCredentials(BackupRepository.CredentialType.MEDIA, attachment.archiveCdn ?: RemoteConfig.backupFallbackArchiveCdn).successOrThrow().headers
|
||||
|
||||
messageReceiver
|
||||
.retrieveArchivedAttachment(
|
||||
|
||||
@@ -126,7 +126,7 @@ class RestoreAttachmentThumbnailJob private constructor(
|
||||
override fun shouldCancel(): Boolean = this@RestoreAttachmentThumbnailJob.isCanceled
|
||||
}
|
||||
|
||||
val cdnCredentials = BackupRepository.getCdnReadCredentials(BackupRepository.CredentialType.MEDIA, attachment.archiveCdn).successOrThrow().headers
|
||||
val cdnCredentials = BackupRepository.getCdnReadCredentials(BackupRepository.CredentialType.MEDIA, attachment.archiveCdn ?: RemoteConfig.backupFallbackArchiveCdn).successOrThrow().headers
|
||||
val pointer = attachment.createArchiveThumbnailPointer()
|
||||
|
||||
Log.i(TAG, "Downloading thumbnail for $attachmentId")
|
||||
@@ -142,7 +142,7 @@ class RestoreAttachmentThumbnailJob private constructor(
|
||||
progressListener
|
||||
)
|
||||
|
||||
SignalDatabase.attachments.finalizeAttachmentThumbnailAfterDownload(attachmentId, attachment.remoteDigest!!, downloadResult.dataStream, thumbnailTransferFile)
|
||||
SignalDatabase.attachments.finalizeAttachmentThumbnailAfterDownload(attachmentId, attachment.remoteDigest, downloadResult.dataStream, thumbnailTransferFile)
|
||||
|
||||
if (!SignalDatabase.messages.isStory(messageId)) {
|
||||
AppDependencies.messageNotifier.updateNotification(context)
|
||||
|
||||
@@ -982,6 +982,13 @@ object RemoteConfig {
|
||||
BuildConfig.MESSAGE_BACKUP_RESTORE_ENABLED || value.asBoolean(false)
|
||||
}
|
||||
|
||||
val backupFallbackArchiveCdn: Int by remoteInt(
|
||||
key = "global.backups.mediaTierFallbackCdnNumber",
|
||||
hotSwappable = true,
|
||||
active = true,
|
||||
defaultValue = 3
|
||||
)
|
||||
|
||||
/** Whether unauthenticated chat web socket is backed by libsignal-net */
|
||||
@JvmStatic
|
||||
@get:JvmName("libSignalWebSocketEnabled")
|
||||
|
||||
Reference in New Issue
Block a user