From f6c7c6de735baa25f1bbb1b685ec248f3231d2e4 Mon Sep 17 00:00:00 2001 From: Alex Hart Date: Fri, 27 Mar 2026 12:38:36 -0300 Subject: [PATCH] Fix local archive progression reporting in notification. --- .../securesms/jobs/LocalArchiveJob.kt | 35 ++++++++++++- .../jobs/LocalPlaintextArchiveJob.kt | 49 ++++++++++++++++--- 2 files changed, 74 insertions(+), 10 deletions(-) diff --git a/app/src/main/java/org/thoughtcrime/securesms/jobs/LocalArchiveJob.kt b/app/src/main/java/org/thoughtcrime/securesms/jobs/LocalArchiveJob.kt index af07d3a5c3..bfc0c0c64d 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/jobs/LocalArchiveJob.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/jobs/LocalArchiveJob.kt @@ -1,6 +1,11 @@ package org.thoughtcrime.securesms.jobs import android.net.Uri +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.SupervisorJob +import kotlinx.coroutines.cancel +import kotlinx.coroutines.launch import org.signal.core.util.Stopwatch import org.signal.core.util.logging.Log import org.thoughtcrime.securesms.R @@ -85,6 +90,13 @@ class LocalArchiveJob internal constructor(parameters: Parameters) : Job(paramet try { SignalDatabase.attachmentMetadata.insertNewKeysForExistingAttachments() + val progressScope = CoroutineScope(SupervisorJob() + Dispatchers.Default) + progressScope.launch { + SignalStore.backup.newLocalBackupProgressFlow.collect { progress -> + updateNotification(progress, notification) + } + } + try { val result = LocalArchiver.export(snapshotFileSystem, archiveFileSystem.filesFileSystem, stopwatch, cancellationSignal = { isCanceled }) Log.i(TAG, "Archive finished with result: $result") @@ -94,6 +106,8 @@ class LocalArchiveJob internal constructor(parameters: Parameters) : Job(paramet } catch (e: Exception) { Log.w(TAG, "Unable to create local archive", e) return Result.failure() + } finally { + progressScope.cancel() } stopwatch.split("archive-create") @@ -158,8 +172,25 @@ class LocalArchiveJob internal constructor(parameters: Parameters) : Job(paramet when { exporting != null -> { val phase = NotificationPhase.Export(exporting.phase) - if (previousPhase != phase) { - notification.replaceTitle(exporting.phase.toString()) + val title = when (exporting.phase) { + LocalBackupCreationProgress.ExportPhase.MESSAGE -> { + if (exporting.frameTotalCount > 0) { + context.getString( + R.string.BackupCreationProgressRow__processing_messages_s_of_s_d, + "%,d".format(exporting.frameExportCount), + "%,d".format(exporting.frameTotalCount), + (exporting.frameExportCount * 100 / exporting.frameTotalCount).toInt() + ) + } else { + context.getString(R.string.BackupCreationProgressRow__processing_messages) + } + } + LocalBackupCreationProgress.ExportPhase.FINALIZING -> context.getString(R.string.BackupCreationProgressRow__finalizing) + LocalBackupCreationProgress.ExportPhase.NONE -> context.getString(R.string.BackupCreationProgressRow__processing_backup) + else -> context.getString(R.string.BackupCreationProgressRow__preparing_backup) + } + if (previousPhase != phase || exporting.phase == LocalBackupCreationProgress.ExportPhase.MESSAGE) { + notification.replaceTitle(title) previousPhase = phase } if (exporting.frameTotalCount == 0L) { diff --git a/app/src/main/java/org/thoughtcrime/securesms/jobs/LocalPlaintextArchiveJob.kt b/app/src/main/java/org/thoughtcrime/securesms/jobs/LocalPlaintextArchiveJob.kt index 925ab3058e..d98da8098f 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/jobs/LocalPlaintextArchiveJob.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/jobs/LocalPlaintextArchiveJob.kt @@ -2,6 +2,11 @@ package org.thoughtcrime.securesms.jobs import android.net.Uri import androidx.documentfile.provider.DocumentFile +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.SupervisorJob +import kotlinx.coroutines.cancel +import kotlinx.coroutines.launch import org.signal.core.util.Stopwatch import org.signal.core.util.logging.Log import org.thoughtcrime.securesms.R @@ -95,15 +100,26 @@ class LocalPlaintextArchiveJob internal constructor( return Result.failure() } - ZipOutputStream(outputStream).use { zipOutputStream -> - val result = LocalArchiver.exportPlaintext(zipOutputStream, includeMedia, stopwatch, cancellationSignal = { isCanceled }) - Log.i(TAG, "Plaintext archive finished with result: $result") - if (result !is org.signal.core.util.Result.Success) { - zipFile.delete() - return Result.failure() + val progressScope = CoroutineScope(SupervisorJob() + Dispatchers.Default) + progressScope.launch { + SignalStore.backup.newLocalPlaintextBackupProgressFlow.collect { progress -> + updateNotification(progress, notification) } } + try { + ZipOutputStream(outputStream).use { zipOutputStream -> + val result = LocalArchiver.exportPlaintext(zipOutputStream, includeMedia, stopwatch, cancellationSignal = { isCanceled }) + Log.i(TAG, "Plaintext archive finished with result: $result") + if (result !is org.signal.core.util.Result.Success) { + zipFile.delete() + return Result.failure() + } + } + } finally { + progressScope.cancel() + } + stopwatch.split("archive-create") setProgress(LocalBackupCreationProgress(idle = LocalBackupCreationProgress.Idle()), notification) } catch (e: IOException) { @@ -141,8 +157,25 @@ class LocalPlaintextArchiveJob internal constructor( when { exporting != null -> { val phase = NotificationPhase.Export(exporting.phase) - if (previousPhase != phase) { - notification.replaceTitle(exporting.phase.toString()) + val title = when (exporting.phase) { + LocalBackupCreationProgress.ExportPhase.MESSAGE -> { + if (exporting.frameTotalCount > 0) { + context.getString( + R.string.BackupCreationProgressRow__processing_messages_s_of_s_d, + "%,d".format(exporting.frameExportCount), + "%,d".format(exporting.frameTotalCount), + (exporting.frameExportCount * 100 / exporting.frameTotalCount).toInt() + ) + } else { + context.getString(R.string.BackupCreationProgressRow__processing_messages) + } + } + LocalBackupCreationProgress.ExportPhase.FINALIZING -> context.getString(R.string.BackupCreationProgressRow__finalizing) + LocalBackupCreationProgress.ExportPhase.NONE -> context.getString(R.string.BackupCreationProgressRow__processing_backup) + else -> context.getString(R.string.BackupCreationProgressRow__preparing_backup) + } + if (previousPhase != phase || exporting.phase == LocalBackupCreationProgress.ExportPhase.MESSAGE) { + notification.replaceTitle(title) previousPhase = phase } if (exporting.frameTotalCount == 0L) {