mirror of
https://github.com/signalapp/Signal-Android.git
synced 2026-09-15 21:27:28 +01:00
Do not do a new SVRB store operation when reusing backup file.
This commit is contained in:
@@ -14,6 +14,8 @@ import androidx.core.app.NotificationCompat
|
||||
import androidx.core.app.NotificationManagerCompat
|
||||
import androidx.core.content.ContextCompat
|
||||
import arrow.core.Either
|
||||
import okio.ByteString
|
||||
import okio.ByteString.Companion.toByteString
|
||||
import okio.IOException
|
||||
import org.signal.core.models.backup.MediaRootBackupKey
|
||||
import org.signal.core.util.PendingIntentFlags
|
||||
@@ -72,6 +74,8 @@ import kotlin.time.Duration.Companion.milliseconds
|
||||
class BackupMessagesJob private constructor(
|
||||
private var syncTime: Long,
|
||||
private var dataFile: String,
|
||||
private var pendingNextBackupSecretData: ByteArray?,
|
||||
private var pendingMessageCutoffTime: Long,
|
||||
private var resumableMessagesBackupUploadSpec: ResumableMessagesBackupUploadSpec?,
|
||||
parameters: Parameters
|
||||
) : CoroutineJob(parameters) {
|
||||
@@ -124,6 +128,8 @@ class BackupMessagesJob private constructor(
|
||||
constructor() : this(
|
||||
syncTime = 0L,
|
||||
dataFile = "",
|
||||
pendingNextBackupSecretData = null,
|
||||
pendingMessageCutoffTime = 0L,
|
||||
resumableMessagesBackupUploadSpec = null,
|
||||
parameters = Parameters.Builder()
|
||||
.addConstraint(BackupMessagesConstraint.KEY)
|
||||
@@ -135,6 +141,8 @@ class BackupMessagesJob private constructor(
|
||||
override fun serialize(): ByteArray = BackupMessagesJobData(
|
||||
syncTime = syncTime,
|
||||
dataFile = dataFile,
|
||||
pendingNextBackupSecretData = pendingNextBackupSecretData?.toByteString() ?: ByteString.EMPTY,
|
||||
pendingMessageCutoffTime = pendingMessageCutoffTime,
|
||||
resumableUri = resumableMessagesBackupUploadSpec?.resumableUri ?: "",
|
||||
uploadSpec = resumableMessagesBackupUploadSpec?.attachmentUploadForm?.toUploadSpec()
|
||||
).encode()
|
||||
@@ -168,115 +176,6 @@ class BackupMessagesJob private constructor(
|
||||
|
||||
val stopwatch = Stopwatch("BackupMessagesJob")
|
||||
|
||||
val auth = when (val result = AppDependencies.archiveService.getSvrBAuth()) {
|
||||
is Either.Right -> result.value
|
||||
is Either.Left -> when (val error = result.value) {
|
||||
is ArchiveError.CredentialError.RateLimited -> {
|
||||
return Result.retry(error.retryAfter?.inWholeMilliseconds ?: defaultBackoff()).logW(TAG, "Rate limited when getting SVRB auth.", error.cause, true)
|
||||
}
|
||||
is ArchiveError.NetworkError,
|
||||
is ArchiveError.CredentialError.Unauthorized,
|
||||
is ArchiveError.CredentialError.NotFound,
|
||||
is ArchiveError.CredentialError.InvalidRequest,
|
||||
is ArchiveError.CredentialError.ZkVerificationFailed -> {
|
||||
return Result.retry(defaultBackoff()).logW(TAG, "Failed to get SVRB auth: ${error::class.simpleName}", error.cause, true)
|
||||
}
|
||||
is ArchiveError.ApplicationError -> {
|
||||
throw error.exception
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (SignalStore.backup.backupSecretRestoreRequired) {
|
||||
Log.i(TAG, "[svrb-restore] First backup of re-registered account without remote restore, read remote data if available to re-init")
|
||||
|
||||
val forwardSecrecyMetadata: ByteArray? = when (val result = BackupRepository.getRemoteBackupForwardSecrecyMetadata()) {
|
||||
is Either.Right -> result.value
|
||||
is Either.Left -> when (val error = result.value) {
|
||||
is ArchiveError.CredentialError.Unauthorized,
|
||||
is ArchiveError.EntitlementError.NotEntitled,
|
||||
is ArchiveError.CredentialError.NotFound -> {
|
||||
Log.i(TAG, "[svrb-restore] No backup data found, continuing.", true)
|
||||
null
|
||||
}
|
||||
is ArchiveError.CredentialError.ZkVerificationFailed -> {
|
||||
Log.w(TAG, "[svrb-restore] zkverification failed getting backup info, continuing.", true)
|
||||
null
|
||||
}
|
||||
is ArchiveError.CredentialError.RateLimited -> {
|
||||
return Result.retry(error.retryAfter?.inWholeMilliseconds ?: defaultBackoff()).logW(TAG, "[svrb-restore] Rate limited when getting remote forward secrecy metadata.", error.cause, true)
|
||||
}
|
||||
is ArchiveError.NetworkError,
|
||||
is ArchiveError.CredentialError.InvalidRequest,
|
||||
is ArchiveError.BackupFileError.UnexpectedResponse -> {
|
||||
return Result.retry(defaultBackoff()).logW(TAG, "[svrb-restore] Failed to get remote forward secrecy metadata: ${error::class.simpleName}", error.cause, true)
|
||||
}
|
||||
is ArchiveError.ApplicationError -> {
|
||||
throw error.exception
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (forwardSecrecyMetadata != null) {
|
||||
when (val result = SignalNetwork.svrB.restore(auth, SignalStore.backup.messageBackupKey, forwardSecrecyMetadata)) {
|
||||
is SvrBApi.RestoreResult.Success -> {
|
||||
Log.i(TAG, "[svrb-restore] Remote secrecy data restored successfully.")
|
||||
SignalStore.backup.nextBackupSecretData = result.data.nextBackupSecretData
|
||||
}
|
||||
|
||||
is SvrBApi.RestoreResult.NetworkError -> {
|
||||
Log.w(TAG, "[svrb-restore] Network error during SVRB.", result.exception)
|
||||
return Result.retry(defaultBackoff())
|
||||
}
|
||||
|
||||
is SvrBApi.RestoreResult.RestoreFailedError,
|
||||
SvrBApi.RestoreResult.InvalidDataError -> {
|
||||
Log.i(TAG, "[svrb-restore] Permanent SVRB error! Continuing $result")
|
||||
SignalStore.backup.nextBackupSecretData = null
|
||||
}
|
||||
|
||||
SvrBApi.RestoreResult.DataMissingError -> {
|
||||
Log.i(TAG, "[svrb-restore] No SVRB data found, resetting local secret data: $result")
|
||||
SignalStore.backup.nextBackupSecretData = null
|
||||
}
|
||||
|
||||
is SvrBApi.RestoreResult.SvrError -> {
|
||||
Log.w(TAG, "[svrb-restore] SVR enclave error, blocking backup until restore succeeds.", result.throwable, true)
|
||||
return Result.retry(defaultBackoff())
|
||||
}
|
||||
|
||||
is SvrBApi.RestoreResult.UnknownError -> {
|
||||
Log.e(TAG, "[svrb-restore] Unknown SVRB result! Crashing.", result.throwable)
|
||||
return Result.fatalFailure(RuntimeException(result.throwable))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
SignalStore.backup.backupSecretRestoreRequired = false
|
||||
}
|
||||
|
||||
val backupSecretData = SignalStore.backup.nextBackupSecretData ?: run {
|
||||
Log.i(TAG, "First SVRB backup! Creating new backup chain.", true)
|
||||
val secretData = SignalNetwork.svrB.createNewBackupChain(auth, SignalStore.backup.messageBackupKey)
|
||||
SignalStore.backup.nextBackupSecretData = secretData
|
||||
secretData
|
||||
}
|
||||
|
||||
val svrBMetadata: SvrBStoreResponse = when (val result = SignalNetwork.svrB.store(auth, SignalStore.backup.messageBackupKey, backupSecretData)) {
|
||||
is SvrBApi.StoreResult.Success -> result.data
|
||||
is SvrBApi.StoreResult.NetworkError -> return Result.retry(result.retryAfter?.inWholeMilliseconds ?: defaultBackoff()).logW(TAG, "SVRB transient network error.", result.exception, true)
|
||||
is SvrBApi.StoreResult.SvrError -> return Result.retry(defaultBackoff()).logW(TAG, "SVRB error.", result.throwable, true)
|
||||
SvrBApi.StoreResult.InvalidDataError -> {
|
||||
Log.w(TAG, "Invalid SVRB data on the server! Clearing backup secret data and retrying.", true)
|
||||
SignalStore.backup.nextBackupSecretData = null
|
||||
return Result.retry(defaultBackoff())
|
||||
}
|
||||
is SvrBApi.StoreResult.UnknownError -> return Result.fatalFailure(RuntimeException(result.throwable))
|
||||
}
|
||||
|
||||
Log.i(TAG, "Successfully stored data on SVRB.", true)
|
||||
stopwatch.split("svrb")
|
||||
|
||||
val createKeyResult = SignalDatabase.attachments.createRemoteKeyForAttachmentsThatNeedArchiveUpload()
|
||||
if (createKeyResult.totalCount > 0) {
|
||||
if (createKeyResult.unexpectedKeyCreation) {
|
||||
@@ -295,17 +194,159 @@ class BackupMessagesJob private constructor(
|
||||
return Result.failure()
|
||||
}
|
||||
|
||||
val (tempBackupFile, currentTime, messageCutoffTime) = when (val generateBackupFileResult = getOrCreateBackupFile(stopwatch, svrBMetadata.forwardSecrecyToken, svrBMetadata.metadata)) {
|
||||
is BackupFileResult.Success -> generateBackupFileResult
|
||||
BackupFileResult.Failure -> return Result.failure()
|
||||
BackupFileResult.Retry -> return Result.retry(defaultBackoff())
|
||||
val tempBackupFile: File
|
||||
val currentTime: Long
|
||||
val messageCutoffTime: Long
|
||||
val nextBackupSecretData: ByteArray
|
||||
|
||||
val reusableSecretData = pendingNextBackupSecretData
|
||||
val reusableBackupFile = if (reusableSecretData != null) findReusableBackupFile() else null
|
||||
|
||||
if (reusableBackupFile != null && reusableSecretData != null) {
|
||||
Log.i(TAG, "Reusing the backup file from a previous attempt, along with the SVRB state it was built with.", true)
|
||||
tempBackupFile = reusableBackupFile
|
||||
currentTime = syncTime
|
||||
messageCutoffTime = pendingMessageCutoffTime
|
||||
nextBackupSecretData = reusableSecretData
|
||||
} else {
|
||||
if (resumableMessagesBackupUploadSpec != null) {
|
||||
Log.w(TAG, "Not reusing the previous backup file, but an upload spec is still present. Clearing it so we don't resume onto a stale upload.", true)
|
||||
resumableMessagesBackupUploadSpec = null
|
||||
}
|
||||
|
||||
// We're building a new file, so whatever the previous attempt left behind is dead, including any SVRB state we may advance past below.
|
||||
clearPendingBackupFile()
|
||||
|
||||
val auth = when (val result = AppDependencies.archiveService.getSvrBAuth()) {
|
||||
is Either.Right -> result.value
|
||||
is Either.Left -> when (val error = result.value) {
|
||||
is ArchiveError.CredentialError.RateLimited -> {
|
||||
return Result.retry(error.retryAfter?.inWholeMilliseconds ?: defaultBackoff()).logW(TAG, "Rate limited when getting SVRB auth.", error.cause, true)
|
||||
}
|
||||
is ArchiveError.NetworkError,
|
||||
is ArchiveError.CredentialError.Unauthorized,
|
||||
is ArchiveError.CredentialError.NotFound,
|
||||
is ArchiveError.CredentialError.InvalidRequest,
|
||||
is ArchiveError.CredentialError.ZkVerificationFailed -> {
|
||||
return Result.retry(defaultBackoff()).logW(TAG, "Failed to get SVRB auth: ${error::class.simpleName}", error.cause, true)
|
||||
}
|
||||
is ArchiveError.ApplicationError -> {
|
||||
throw error.exception
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (SignalStore.backup.backupSecretRestoreRequired) {
|
||||
Log.i(TAG, "[svrb-restore] First backup of re-registered account without remote restore, read remote data if available to re-init")
|
||||
|
||||
val forwardSecrecyMetadata: ByteArray? = when (val result = BackupRepository.getRemoteBackupForwardSecrecyMetadata()) {
|
||||
is Either.Right -> result.value
|
||||
is Either.Left -> when (val error = result.value) {
|
||||
is ArchiveError.CredentialError.Unauthorized,
|
||||
is ArchiveError.EntitlementError.NotEntitled,
|
||||
is ArchiveError.CredentialError.NotFound -> {
|
||||
Log.i(TAG, "[svrb-restore] No backup data found, continuing.", true)
|
||||
null
|
||||
}
|
||||
is ArchiveError.CredentialError.ZkVerificationFailed -> {
|
||||
Log.w(TAG, "[svrb-restore] zkverification failed getting backup info, continuing.", true)
|
||||
null
|
||||
}
|
||||
is ArchiveError.CredentialError.RateLimited -> {
|
||||
return Result.retry(error.retryAfter?.inWholeMilliseconds ?: defaultBackoff()).logW(TAG, "[svrb-restore] Rate limited when getting remote forward secrecy metadata.", error.cause, true)
|
||||
}
|
||||
is ArchiveError.NetworkError,
|
||||
is ArchiveError.CredentialError.InvalidRequest,
|
||||
is ArchiveError.BackupFileError.UnexpectedResponse -> {
|
||||
return Result.retry(defaultBackoff()).logW(TAG, "[svrb-restore] Failed to get remote forward secrecy metadata: ${error::class.simpleName}", error.cause, true)
|
||||
}
|
||||
is ArchiveError.ApplicationError -> {
|
||||
throw error.exception
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (forwardSecrecyMetadata != null) {
|
||||
when (val result = SignalNetwork.svrB.restore(auth, SignalStore.backup.messageBackupKey, forwardSecrecyMetadata)) {
|
||||
is SvrBApi.RestoreResult.Success -> {
|
||||
Log.i(TAG, "[svrb-restore] Remote secrecy data restored successfully.")
|
||||
SignalStore.backup.nextBackupSecretData = result.data.nextBackupSecretData
|
||||
}
|
||||
|
||||
is SvrBApi.RestoreResult.NetworkError -> {
|
||||
Log.w(TAG, "[svrb-restore] Network error during SVRB.", result.exception)
|
||||
return Result.retry(defaultBackoff())
|
||||
}
|
||||
|
||||
is SvrBApi.RestoreResult.RestoreFailedError,
|
||||
SvrBApi.RestoreResult.InvalidDataError -> {
|
||||
Log.i(TAG, "[svrb-restore] Permanent SVRB error! Continuing $result")
|
||||
SignalStore.backup.nextBackupSecretData = null
|
||||
}
|
||||
|
||||
SvrBApi.RestoreResult.DataMissingError -> {
|
||||
Log.i(TAG, "[svrb-restore] No SVRB data found, resetting local secret data: $result")
|
||||
SignalStore.backup.nextBackupSecretData = null
|
||||
}
|
||||
|
||||
is SvrBApi.RestoreResult.SvrError -> {
|
||||
Log.w(TAG, "[svrb-restore] SVR enclave error, blocking backup until restore succeeds.", result.throwable, true)
|
||||
return Result.retry(defaultBackoff())
|
||||
}
|
||||
|
||||
is SvrBApi.RestoreResult.UnknownError -> {
|
||||
Log.e(TAG, "[svrb-restore] Unknown SVRB result! Crashing.", result.throwable)
|
||||
return Result.fatalFailure(RuntimeException(result.throwable))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
SignalStore.backup.backupSecretRestoreRequired = false
|
||||
}
|
||||
|
||||
val backupSecretData = SignalStore.backup.nextBackupSecretData ?: run {
|
||||
Log.i(TAG, "First SVRB backup! Creating new backup chain.", true)
|
||||
val secretData = SignalNetwork.svrB.createNewBackupChain(auth, SignalStore.backup.messageBackupKey)
|
||||
SignalStore.backup.nextBackupSecretData = secretData
|
||||
secretData
|
||||
}
|
||||
|
||||
val svrBMetadata: SvrBStoreResponse = when (val result = SignalNetwork.svrB.store(auth, SignalStore.backup.messageBackupKey, backupSecretData)) {
|
||||
is SvrBApi.StoreResult.Success -> result.data
|
||||
is SvrBApi.StoreResult.NetworkError -> return Result.retry(result.retryAfter?.inWholeMilliseconds ?: defaultBackoff()).logW(TAG, "SVRB transient network error.", result.exception, true)
|
||||
is SvrBApi.StoreResult.SvrError -> return Result.retry(defaultBackoff()).logW(TAG, "SVRB error.", result.throwable, true)
|
||||
SvrBApi.StoreResult.InvalidDataError -> {
|
||||
Log.w(TAG, "Invalid SVRB data on the server! Clearing backup secret data and retrying.", true)
|
||||
SignalStore.backup.nextBackupSecretData = null
|
||||
return Result.retry(defaultBackoff())
|
||||
}
|
||||
is SvrBApi.StoreResult.UnknownError -> return Result.fatalFailure(RuntimeException(result.throwable))
|
||||
}
|
||||
|
||||
Log.i(TAG, "Successfully stored data on SVRB.", true)
|
||||
stopwatch.split("svrb")
|
||||
|
||||
when (val generateBackupFileResult = createBackupFile(stopwatch, svrBMetadata.forwardSecrecyToken, svrBMetadata.metadata)) {
|
||||
is BackupFileResult.Success -> {
|
||||
tempBackupFile = generateBackupFileResult.tempBackupFile
|
||||
currentTime = generateBackupFileResult.currentTime
|
||||
messageCutoffTime = generateBackupFileResult.messageInclusionCutoffTime
|
||||
}
|
||||
BackupFileResult.Failure -> return Result.failure()
|
||||
BackupFileResult.Retry -> return Result.retry(defaultBackoff())
|
||||
}
|
||||
|
||||
nextBackupSecretData = svrBMetadata.nextBackupSecretData
|
||||
}
|
||||
|
||||
ArchiveUploadProgress.onMessageBackupCreated(tempBackupFile.length())
|
||||
SignalStore.backup.lastBackupProtoVersion = BackupRepository.VERSION
|
||||
|
||||
// These must be written together: the secret data is only valid for the exact file it was exported alongside.
|
||||
this.syncTime = currentTime
|
||||
this.dataFile = tempBackupFile.path
|
||||
this.pendingNextBackupSecretData = nextBackupSecretData
|
||||
this.pendingMessageCutoffTime = messageCutoffTime
|
||||
|
||||
val existingSpec = resumableMessagesBackupUploadSpec
|
||||
val form: AttachmentUploadForm = if (existingSpec == null) {
|
||||
@@ -319,7 +360,7 @@ class BackupMessagesJob private constructor(
|
||||
is ArchiveError.UploadFormError.TooLarge -> {
|
||||
Log.i(TAG, "Backup file is too large! Size: ${tempBackupFile.length()} bytes. Current threshold: ${SignalStore.backup.messageCuttoffDuration}", error.cause, true)
|
||||
tempBackupFile.delete()
|
||||
this.dataFile = ""
|
||||
clearPendingBackupFile()
|
||||
BackupRepository.markBackupCreationFailed(BackupValues.BackupCreationError.BACKUP_FILE_TOO_LARGE)
|
||||
backupErrorHandled = true
|
||||
|
||||
@@ -415,7 +456,8 @@ class BackupMessagesJob private constructor(
|
||||
SignalStore.backup.hasBackupBeenUploaded = true
|
||||
stopwatch.split("upload")
|
||||
|
||||
SignalStore.backup.nextBackupSecretData = svrBMetadata.nextBackupSecretData
|
||||
SignalStore.backup.nextBackupSecretData = nextBackupSecretData
|
||||
clearPendingBackupFile()
|
||||
|
||||
SignalStore.backup.lastBackupProtoSize = tempBackupFile.length()
|
||||
if (!tempBackupFile.delete()) {
|
||||
@@ -463,21 +505,37 @@ class BackupMessagesJob private constructor(
|
||||
return Result.success()
|
||||
}
|
||||
|
||||
private fun getOrCreateBackupFile(
|
||||
stopwatch: Stopwatch,
|
||||
forwardSecrecyToken: BackupForwardSecrecyToken,
|
||||
forwardSecrecyMetadata: ByteArray
|
||||
): BackupFileResult {
|
||||
/**
|
||||
* The backup file left behind by a previous attempt, if it's still present and recent enough to upload as-is.
|
||||
*/
|
||||
private fun findReusableBackupFile(): File? {
|
||||
if (System.currentTimeMillis() > syncTime && syncTime > 0L && dataFile.isNotNullOrBlank()) {
|
||||
val file = File(dataFile)
|
||||
val elapsed = (System.currentTimeMillis() - syncTime).milliseconds
|
||||
|
||||
if (file.exists() && file.canRead() && elapsed < FILE_REUSE_TIMEOUT) {
|
||||
Log.d(TAG, "File exists and is new enough to utilize.", true)
|
||||
return BackupFileResult.Success(file, syncTime, messageInclusionCutoffTime = SignalStore.backup.lastUsedMessageCutoffTime)
|
||||
return file
|
||||
}
|
||||
}
|
||||
|
||||
return null
|
||||
}
|
||||
|
||||
/**
|
||||
* Clear out backup data associated with previous backup attempts.
|
||||
*/
|
||||
private fun clearPendingBackupFile() {
|
||||
this.dataFile = ""
|
||||
this.pendingNextBackupSecretData = null
|
||||
this.pendingMessageCutoffTime = 0L
|
||||
}
|
||||
|
||||
private fun createBackupFile(
|
||||
stopwatch: Stopwatch,
|
||||
forwardSecrecyToken: BackupForwardSecrecyToken,
|
||||
forwardSecrecyMetadata: ByteArray
|
||||
): BackupFileResult {
|
||||
AppDependencies.blobs.clearTemporaryBackupsDirectory(AppDependencies.application)
|
||||
|
||||
val tempBackupFile = AppDependencies.blobs.forTemporaryBackup(AppDependencies.application)
|
||||
@@ -513,13 +571,13 @@ class BackupMessagesJob private constructor(
|
||||
if (e.message?.contains("ENOSPC") == true) {
|
||||
Log.w(TAG, "Not enough space to make a backup!", e, true)
|
||||
tempBackupFile.delete()
|
||||
this.dataFile = ""
|
||||
clearPendingBackupFile()
|
||||
BackupRepository.markBackupCreationFailed(BackupValues.BackupCreationError.NOT_ENOUGH_DISK_SPACE)
|
||||
return BackupFileResult.Failure
|
||||
} else {
|
||||
Log.w(TAG, "Exception during backup export", e)
|
||||
tempBackupFile.delete()
|
||||
this.dataFile = ""
|
||||
clearPendingBackupFile()
|
||||
return BackupFileResult.Retry
|
||||
}
|
||||
}
|
||||
@@ -549,7 +607,7 @@ class BackupMessagesJob private constructor(
|
||||
is ArchiveValidator.ValidationResult.MessageValidationError -> {
|
||||
Log.w(TAG, "The backup file fails validation! Message: ${result.exception.message}, Details: ${result.messageDetails}", true)
|
||||
tempBackupFile.delete()
|
||||
this.dataFile = ""
|
||||
clearPendingBackupFile()
|
||||
BackupRepository.markBackupCreationFailed(BackupValues.BackupCreationError.VALIDATION)
|
||||
backupErrorHandled = true
|
||||
return BackupFileResult.Failure
|
||||
@@ -558,7 +616,7 @@ class BackupMessagesJob private constructor(
|
||||
is ArchiveValidator.ValidationResult.RecipientDuplicateE164Error -> {
|
||||
Log.w(TAG, "The backup file fails validation with a duplicate recipient! Message: ${result.exception.message}, Details: ${result.details}", true)
|
||||
tempBackupFile.delete()
|
||||
this.dataFile = ""
|
||||
clearPendingBackupFile()
|
||||
AppDependencies.jobManager.add(E164FormattingJob())
|
||||
BackupRepository.markBackupCreationFailed(BackupValues.BackupCreationError.VALIDATION)
|
||||
backupErrorHandled = true
|
||||
@@ -651,6 +709,8 @@ class BackupMessagesJob private constructor(
|
||||
return BackupMessagesJob(
|
||||
syncTime = jobData.syncTime,
|
||||
dataFile = jobData.dataFile,
|
||||
pendingNextBackupSecretData = jobData.pendingNextBackupSecretData.takeIf { it.size > 0 }?.toByteArray(),
|
||||
pendingMessageCutoffTime = jobData.pendingMessageCutoffTime,
|
||||
resumableMessagesBackupUploadSpec = uploadSpecFromJobData(jobData),
|
||||
parameters = parameters
|
||||
)
|
||||
|
||||
@@ -165,6 +165,8 @@ message BackupMessagesJobData {
|
||||
string dataFile = 2;
|
||||
ResumableUpload uploadSpec = 3;
|
||||
string resumableUri = 4;
|
||||
bytes pendingNextBackupSecretData = 5;
|
||||
uint64 pendingMessageCutoffTime = 6;
|
||||
}
|
||||
|
||||
message NewLinkedDeviceNotificationJobData {
|
||||
|
||||
Reference in New Issue
Block a user