Attempt backing up a subset of messages if you hit the limit.

This commit is contained in:
Greyson Parrelli
2025-11-11 14:48:09 -05:00
committed by GitHub
parent f4e82e6aab
commit b047f8bc0a
11 changed files with 112 additions and 24 deletions

View File

@@ -417,7 +417,7 @@ object BackupRepository {
}
fun clearBackupFailure() {
SignalStore.backup.clearBackupCreationFailed()
SignalStore.backup.backupCreationError = null
ServiceUtil.getNotificationManager(AppDependencies.application).cancel(NotificationIds.INITIAL_BACKUP_FAILED)
}
@@ -767,7 +767,8 @@ object BackupRepository {
progressEmitter = localBackupProgressEmitter,
cancellationSignal = cancellationSignal,
forTransfer = false,
extraFrameOperation = null
extraFrameOperation = null,
messageInclusionCutoffTime = 0
) { dbSnapshot ->
val localArchivableAttachments = dbSnapshot
.attachmentTable
@@ -801,6 +802,7 @@ object BackupRepository {
forwardSecrecyToken: BackupForwardSecrecyToken,
forwardSecrecyMetadata: ByteArray,
currentTime: Long,
messageInclusionCutoffTime: Long = 0,
progressEmitter: ExportProgressListener? = null,
cancellationSignal: () -> Boolean = { false },
extraFrameOperation: ((Frame) -> Unit)?
@@ -822,7 +824,8 @@ object BackupRepository {
progressEmitter = progressEmitter,
cancellationSignal = cancellationSignal,
extraFrameOperation = extraFrameOperation,
endingExportOperation = null
endingExportOperation = null,
messageInclusionCutoffTime = messageInclusionCutoffTime
)
}
@@ -852,7 +855,8 @@ object BackupRepository {
progressEmitter = progressEmitter,
cancellationSignal = cancellationSignal,
extraFrameOperation = null,
endingExportOperation = null
endingExportOperation = null,
messageInclusionCutoffTime = 0
)
}
@@ -887,7 +891,8 @@ object BackupRepository {
progressEmitter = progressEmitter,
cancellationSignal = cancellationSignal,
extraFrameOperation = null,
endingExportOperation = null
endingExportOperation = null,
messageInclusionCutoffTime = 0
)
}
@@ -907,6 +912,7 @@ object BackupRepository {
isLocal: Boolean,
writer: BackupExportWriter,
forTransfer: Boolean,
messageInclusionCutoffTime: Long,
progressEmitter: ExportProgressListener?,
cancellationSignal: () -> Boolean,
extraFrameOperation: ((Frame) -> Unit)?,
@@ -1033,7 +1039,7 @@ object BackupRepository {
val approximateMessageCount = dbSnapshot.messageTable.getApproximateExportableMessageCount(exportState.threadIds)
val frameCountStart = frameCount
progressEmitter?.onMessage(0, approximateMessageCount)
ChatItemArchiveProcessor.export(dbSnapshot, exportState, selfRecipientId, cancellationSignal) { frame ->
ChatItemArchiveProcessor.export(dbSnapshot, exportState, selfRecipientId, messageInclusionCutoffTime, cancellationSignal) { frame ->
writer.write(frame)
extraFrameOperation?.invoke(frame)
eventTimer.emit("message")

View File

@@ -24,7 +24,7 @@ import kotlin.time.Duration.Companion.days
private val TAG = "MessageTableArchiveExtensions"
fun MessageTable.getMessagesForBackup(db: SignalDatabase, backupTime: Long, selfRecipientId: RecipientId, exportState: ExportState): ChatItemArchiveExporter {
fun MessageTable.getMessagesForBackup(db: SignalDatabase, backupTime: Long, selfRecipientId: RecipientId, messageInclusionCutoffTime: Long, exportState: ExportState): ChatItemArchiveExporter {
// We create a covering index for the query to drastically speed up perf here.
// Remember that we're working on a temporary snapshot of the database, so we can create an index and not worry about cleaning it up.
val startTime = System.currentTimeMillis()
@@ -105,6 +105,12 @@ fun MessageTable.getMessagesForBackup(db: SignalDatabase, backupTime: Long, self
)
Log.d(TAG, "Cleanup took ${System.currentTimeMillis() - cleanupStartTime} ms")
val cutoffQuery = if (messageInclusionCutoffTime > 0) {
" AND $DATE_RECEIVED >= $messageInclusionCutoffTime"
} else {
""
}
return ChatItemArchiveExporter(
db = db,
selfRecipientId = selfRecipientId,
@@ -152,7 +158,7 @@ fun MessageTable.getMessagesForBackup(db: SignalDatabase, backupTime: Long, self
PARENT_STORY_ID
)
.from("${MessageTable.TABLE_NAME} INDEXED BY $dateReceivedIndex")
.where("$STORY_TYPE = 0 AND $PARENT_STORY_ID <= 0 AND $SCHEDULED_DATE = -1 AND ($EXPIRES_IN == 0 OR $EXPIRES_IN > ${1.days.inWholeMilliseconds}) AND $DATE_RECEIVED >= $lastSeenReceivedTime")
.where("$STORY_TYPE = 0 AND $PARENT_STORY_ID <= 0 AND $SCHEDULED_DATE = -1 AND ($EXPIRES_IN == 0 OR $EXPIRES_IN > ${1.days.inWholeMilliseconds}) AND $DATE_RECEIVED >= $lastSeenReceivedTime $cutoffQuery")
.limit(count)
.orderBy("$DATE_RECEIVED ASC")
.run()

View File

@@ -23,8 +23,8 @@ import org.thoughtcrime.securesms.recipients.RecipientId
object ChatItemArchiveProcessor {
val TAG = Log.tag(ChatItemArchiveProcessor::class.java)
fun export(db: SignalDatabase, exportState: ExportState, selfRecipientId: RecipientId, cancellationSignal: () -> Boolean, emitter: BackupFrameEmitter) {
db.messageTable.getMessagesForBackup(db, exportState.backupTime, selfRecipientId, exportState).use { chatItems ->
fun export(db: SignalDatabase, exportState: ExportState, selfRecipientId: RecipientId, messageInclusionCutoffTime: Long, cancellationSignal: () -> Boolean, emitter: BackupFrameEmitter) {
db.messageTable.getMessagesForBackup(db, exportState.backupTime, selfRecipientId, messageInclusionCutoffTime, exportState).use { chatItems ->
var count = 0
while (chatItems.hasNext()) {
if (count % 1000 == 0 && cancellationSignal()) {

View File

@@ -19,6 +19,7 @@ import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.dimensionResource
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.AnnotatedString.Builder
@@ -35,6 +36,9 @@ import org.signal.core.ui.compose.DayNightPreviews
import org.signal.core.ui.compose.Previews
import org.thoughtcrime.securesms.R
import org.thoughtcrime.securesms.keyvalue.BackupValues
import org.thoughtcrime.securesms.util.DateUtils
import java.util.Locale
import kotlin.time.Duration.Companion.days
import org.signal.core.ui.R as CoreUiR
private val YELLOW_DOT = Color(0xFFFFCC00)
@@ -45,8 +49,12 @@ private val YELLOW_DOT = Color(0xFFFFCC00)
@Composable
fun BackupCreateErrorRow(
error: BackupValues.BackupCreationError,
lastMessageCutoffTime: Long = 0,
onLearnMoreClick: () -> Unit = {}
) {
val context = LocalContext.current
val locale = Locale.getDefault()
when (error) {
BackupValues.BackupCreationError.TRANSIENT -> {
BackupAlertText {
@@ -73,7 +81,11 @@ fun BackupCreateErrorRow(
BackupValues.BackupCreationError.BACKUP_FILE_TOO_LARGE -> {
BackupAlertText {
append(stringResource(R.string.BackupStatusRow__backup_file_too_large))
if (lastMessageCutoffTime > 0) {
append(stringResource(R.string.BackupStatusRow__not_backing_up_old_messages, DateUtils.getDayPrecisionTimeString(context, locale, lastMessageCutoffTime)))
} else {
append(stringResource(R.string.BackupStatusRow__backup_file_too_large))
}
}
}
}
@@ -114,6 +126,10 @@ fun BackupStatusRowCouldNotCompleteBackupPreview() {
BackupCreateErrorRow(error = error, onLearnMoreClick = {})
Spacer(modifier = Modifier.size(8.dp))
}
Text(BackupValues.BackupCreationError.BACKUP_FILE_TOO_LARGE.name + " with cutoff duration")
BackupCreateErrorRow(error = BackupValues.BackupCreationError.BACKUP_FILE_TOO_LARGE, lastMessageCutoffTime = System.currentTimeMillis() - 365.days.inWholeMilliseconds, onLearnMoreClick = {})
Spacer(modifier = Modifier.size(8.dp))
}
}
}