Improve link device good citizenship with backups.

This commit is contained in:
Cody Henthorne
2025-08-12 14:33:47 -04:00
committed by GitHub
parent 2190a4a58d
commit 0bbbee645d
12 changed files with 65 additions and 10 deletions

View File

@@ -1919,7 +1919,7 @@ object BackupRepository {
private fun initBackupAndFetchAuth(): NetworkResult<ArchiveServiceAccessPair> {
return if (!RemoteConfig.messageBackups) {
NetworkResult.StatusCodeError(555, null, null, emptyMap(), NonSuccessfulResponseCodeException(555, "Backups disabled!"))
} else if (SignalStore.backup.backupsInitialized) {
} else if (SignalStore.backup.backupsInitialized || SignalStore.account.isLinkedDevice) {
getArchiveServiceAccessPair()
.runOnStatusCodeError(resetInitializedStateErrorAction)
.runOnApplicationError(clearAuthCredentials)

View File

@@ -239,6 +239,10 @@ class AttachmentDownloadJob private constructor(
Log.i(TAG, "[$attachmentId] Message will expire within 24hrs. Skipping.")
}
SignalStore.account.isLinkedDevice -> {
Log.i(TAG, "[$attachmentId] Linked device. Skipping.")
}
else -> {
Log.i(TAG, "[$attachmentId] Enqueuing job to copy to archive.")
AppDependencies.jobManager.add(CopyAttachmentToArchiveJob(attachmentId))
@@ -302,7 +306,9 @@ class AttachmentDownloadJob private constructor(
progressListener
)
SignalDatabase.attachments.finalizeAttachmentAfterDownload(messageId, attachmentId, decryptingStream)
decryptingStream.use { input ->
SignalDatabase.attachments.finalizeAttachmentAfterDownload(messageId, attachmentId, input)
}
} catch (e: RangeException) {
Log.w(TAG, "Range exception, file size " + attachmentFile.length(), e)
if (attachmentFile.delete()) {

View File

@@ -148,7 +148,7 @@ class AttachmentUploadJob private constructor(
if (timeSinceUpload < UPLOAD_REUSE_THRESHOLD && !TextUtils.isEmpty(databaseAttachment.remoteLocation)) {
Log.i(TAG, "We can re-use an already-uploaded file. It was uploaded $timeSinceUpload ms (${timeSinceUpload.milliseconds.inRoundedDays()} days) ago. Skipping.")
SignalDatabase.attachments.setTransferState(databaseAttachment.mmsId, attachmentId, AttachmentTable.TRANSFER_PROGRESS_DONE)
if (BackupRepository.shouldCopyAttachmentToArchive(databaseAttachment.attachmentId, databaseAttachment.mmsId)) {
if (SignalStore.account.isPrimaryDevice && BackupRepository.shouldCopyAttachmentToArchive(databaseAttachment.attachmentId, databaseAttachment.mmsId)) {
Log.i(TAG, "[$attachmentId] The re-used file was not copied to the archive. Copying now.")
AppDependencies.jobManager.add(CopyAttachmentToArchiveJob(attachmentId))
}
@@ -204,6 +204,9 @@ class AttachmentUploadJob private constructor(
databaseAttachment.contentType == MediaUtil.LONG_TEXT -> {
Log.i(TAG, "[$attachmentId] Long text attachment. Skipping.")
}
SignalStore.account.isLinkedDevice -> {
Log.i(TAG, "[$attachmentId] Linked device. Skipping archive.")
}
else -> {
Log.i(TAG, "[$attachmentId] Enqueuing job to copy to archive.")
AppDependencies.jobManager.add(CopyAttachmentToArchiveJob(attachmentId))

View File

@@ -69,6 +69,12 @@ class CopyAttachmentToArchiveJob private constructor(private val attachmentId: A
}
override fun run(): Result {
if (SignalStore.account.isLinkedDevice) {
Log.w(TAG, "[$attachmentId] Linked devices don't backup media. Skipping.")
SignalDatabase.attachments.setArchiveTransferState(attachmentId, AttachmentTable.ArchiveTransferState.NONE)
return Result.success()
}
if (!SignalStore.backup.backsUpMedia) {
Log.w(TAG, "[$attachmentId] This user does not back up media. Skipping.")
return Result.success()

View File

@@ -5,6 +5,7 @@
package org.thoughtcrime.securesms.jobs
import androidx.annotation.VisibleForTesting
import okio.ByteString.Companion.toByteString
import org.signal.core.util.logging.Log
import org.signal.core.util.money.FiatMoney
@@ -51,6 +52,7 @@ class InAppPaymentKeepAliveJob private constructor(
const val KEEP_ALIVE = "keep-alive"
private const val DATA_TYPE = "type"
@VisibleForTesting
fun create(type: InAppPaymentSubscriberRecord.Type): Job {
return InAppPaymentKeepAliveJob(
parameters = Parameters.Builder()
@@ -66,6 +68,11 @@ class InAppPaymentKeepAliveJob private constructor(
@JvmStatic
fun enqueueAndTrackTimeIfNecessary() {
if (SignalStore.account.isLinkedDevice) {
Log.i(TAG, "Linked device. Skipping.")
return
}
// TODO -- This should only be enqueued if we are completely drained of old subscription jobs. (No pending, no runnning)
val lastKeepAliveTime = SignalStore.inAppPayments.getLastKeepAliveLaunchTime().milliseconds
val now = System.currentTimeMillis().milliseconds
@@ -83,6 +90,11 @@ class InAppPaymentKeepAliveJob private constructor(
@JvmStatic
fun enqueueAndTrackTime(now: Duration) {
if (SignalStore.account.isLinkedDevice) {
Log.i(TAG, "Linked device. Skipping.")
return
}
AppDependencies.jobManager.add(create(InAppPaymentSubscriberRecord.Type.DONATION))
AppDependencies.jobManager.add(create(InAppPaymentSubscriberRecord.Type.BACKUP))
SignalStore.inAppPayments.setLastKeepAliveLaunchTime(now.inWholeMilliseconds)

View File

@@ -123,7 +123,7 @@ class RestoreAttachmentJob private constructor(
attachmentId = attachmentId,
messageId = messageId,
manual = false,
queue = Queues.INITIAL_RESTORE.random(),
queue = Queues.OFFLOAD_RESTORE.random(),
priority = Parameters.PRIORITY_LOW
)
}
@@ -323,7 +323,9 @@ class RestoreAttachmentJob private constructor(
)
}
SignalDatabase.attachments.finalizeAttachmentAfterDownload(messageId, attachmentId, decryptingStream, if (manual) System.currentTimeMillis().milliseconds else null)
decryptingStream.use { input ->
SignalDatabase.attachments.finalizeAttachmentAfterDownload(messageId, attachmentId, input, if (manual) System.currentTimeMillis().milliseconds else null)
}
} catch (e: RangeException) {
Log.w(TAG, "[$attachmentId] Range exception, file size " + attachmentFile.length(), e)
if (attachmentFile.delete()) {

View File

@@ -140,7 +140,9 @@ class RestoreAttachmentThumbnailJob private constructor(
progressListener
)
SignalDatabase.attachments.finalizeAttachmentThumbnailAfterDownload(attachmentId, attachment.dataHash, attachment.remoteKey, decryptingStream, thumbnailTransferFile)
decryptingStream.use { input ->
SignalDatabase.attachments.finalizeAttachmentThumbnailAfterDownload(attachmentId, attachment.dataHash, attachment.remoteKey, input, thumbnailTransferFile)
}
if (!SignalDatabase.messages.isStory(messageId)) {
AppDependencies.messageNotifier.updateNotification(context)

View File

@@ -93,6 +93,12 @@ class UploadAttachmentToArchiveJob private constructor(
}
override fun run(): Result {
if (SignalStore.account.isLinkedDevice) {
Log.w(TAG, "[$attachmentId] Linked devices don't backup media. Skipping.")
SignalDatabase.attachments.setArchiveTransferState(attachmentId, AttachmentTable.ArchiveTransferState.NONE)
return Result.success()
}
if (!SignalStore.backup.backsUpMedia) {
Log.w(TAG, "[$attachmentId] This user does not back up media. Skipping.")
SignalDatabase.attachments.setArchiveTransferState(attachmentId, AttachmentTable.ArchiveTransferState.NONE)

View File

@@ -199,6 +199,15 @@ class BackupValues(store: KeyValueStore) : SignalStoreValues(store) {
}
set(value) {
lock.withLock {
val currentValue: ByteArray? = getBlob(KEY_MEDIA_ROOT_BACKUP_KEY, null)
if (currentValue != null) {
val current = MediaRootBackupKey(currentValue)
if (current == value) {
Log.i(TAG, "MediaRootBackupKey the same, skipping.")
return
}
}
Log.i(TAG, "Setting MediaRootBackupKey...", Throwable(), true)
store.beginWrite().putBlob(KEY_MEDIA_ROOT_BACKUP_KEY, value.value).commit()
mediaCredentials.clearAll()

View File

@@ -256,9 +256,6 @@ object RegistrationRepository {
DirectoryRefreshListener.schedule(context)
RotateSignedPreKeyListener.schedule(context)
} else {
// TODO [linked-device] May want to have a different opt out mechanism for linked devices
SvrRepository.optOutOfPin()
SignalStore.account.isMultiDevice = true
SignalStore.registration.hasUploadedProfile = true
jobManager.runJobBlocking(RefreshOwnProfileJob(), 30.seconds)

View File

@@ -1145,7 +1145,6 @@ class RegistrationViewModel : ViewModel() {
}
// TODO [linked-device] Reapply opt-out, backup restore sets pin, may want to have a different opt out mechanism for linked devices
SvrRepository.optOutOfPin()
}
for (type in SyncMessage.Request.Type.entries) {

View File

@@ -66,6 +66,19 @@ class MediaRootBackupKey(override val value: ByteArray) : BackupKey {
)
}
override fun equals(other: Any?): Boolean {
if (this === other) return true
if (javaClass != other?.javaClass) return false
other as MediaRootBackupKey
return value.contentEquals(other.value)
}
override fun hashCode(): Int {
return value.contentHashCode()
}
class MediaKeyMaterial(
val id: MediaId,
val macKey: ByteArray,