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

@@ -96,6 +96,9 @@ class BackupValues(store: KeyValueStore) : SignalStoreValues(store) {
private const val KEY_RESTORING_VIA_QR = "backup.restore_via_qr"
private const val KEY_MESSAGE_CUTOFF_DURATION = "backup.message_cutoff_duration"
private const val KEY_LAST_USED_MESSAGE_CUTOFF_TIME = "backup.last_used_message_cutoff_time"
private val cachedCdnCredentialsExpiresIn: Duration = 12.hours
private val lock = ReentrantLock()
@@ -259,8 +262,8 @@ class BackupValues(store: KeyValueStore) : SignalStoreValues(store) {
if (storedValue != value) {
clearNotEnoughRemoteStorageSpace()
clearBackupCreationFailed()
clearMessageBackupFailureSheetWatermark()
backupCreationError = null
}
deletionState = DeletionState.NONE
@@ -303,7 +306,7 @@ class BackupValues(store: KeyValueStore) : SignalStoreValues(store) {
var hasBackupBeenUploaded: Boolean by booleanValue(KEY_BACKUP_UPLOADED, false)
val hasBackupCreationError: Boolean get() = backupCreationError != null
val backupCreationError: BackupCreationError? by enumValue(KEY_BACKUP_CREATION_ERROR, null, BackupCreationError.serializer)
var backupCreationError: BackupCreationError? by enumValue(KEY_BACKUP_CREATION_ERROR, null, BackupCreationError.serializer)
val nextBackupFailureSnoozeTime: Duration get() = getLong(KEY_BACKUP_FAIL_ACKNOWLEDGED_SNOOZE_TIME, 0L).milliseconds
val nextBackupFailureSheetSnoozeTime: Duration get() = getLong(KEY_BACKUP_FAIL_SHEET_SNOOZE_TIME, getNextBackupFailureSheetSnoozeTime(lastBackupTime.milliseconds).inWholeMilliseconds).milliseconds
@@ -419,6 +422,20 @@ class BackupValues(store: KeyValueStore) : SignalStoreValues(store) {
.apply()
}
/**
* If set, this represents how far back we should backup messages. For instance, if the returned value is 1 year in milliseconds, you should back up
* every message within the last year. If unset, back up all messages. We only cutoff old messages for users whose backup is over the
* size limit, which is *extraordinarily* rare, so this value is almost always null.
*/
var messageCuttoffDuration: Duration? by durationValue(KEY_MESSAGE_CUTOFF_DURATION, null)
/**
* The last threshold we used for backing up messages. Messages sent before this time were not included in the backup.
* A value of 0 indicates that we included all messages. We only cutoff old messages for users whose backup is over the
* size limit, which is *extraordinarily* rare, so this value is almost always 0.
*/
var lastUsedMessageCutoffTime: Long by longValue(KEY_LAST_USED_MESSAGE_CUTOFF_TIME, 0)
/**
* When we are told by the server that we are out of storage space, we should show
* UX treatment to make the user aware of this.

View File

@@ -5,6 +5,8 @@ import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableStateFlow
import org.signal.core.util.LongSerializer
import kotlin.reflect.KProperty
import kotlin.time.Duration
import kotlin.time.Duration.Companion.nanoseconds
internal fun SignalStoreValues.longValue(key: String, default: Long): SignalStoreValueDelegate<Long> {
return LongValue(key, default, this.store)
@@ -46,6 +48,10 @@ internal fun <M> SignalStoreValues.protoValue(key: String, default: M, adapter:
return KeyValueProtoWithDefaultValue(key, default, adapter, this.store, onSet)
}
internal fun SignalStoreValues.durationValue(key: String, default: Duration?): SignalStoreValueDelegate<Duration?> {
return DurationValue(key, default, this.store)
}
internal fun <T> SignalStoreValueDelegate<T>.withPrecondition(precondition: () -> Boolean): SignalStoreValueDelegate<T> {
return PreconditionDelegate(
delegate = this,
@@ -159,6 +165,20 @@ private class NullableBlobValue(private val key: String, default: ByteArray?, st
}
}
private class DurationValue(private val key: String, default: Duration?, store: KeyValueStore) : SignalStoreValueDelegate<Duration?>(store, default) {
companion object {
private const val UNSET: Long = -1
}
override fun getValue(values: KeyValueStore): Duration? {
return values.getLong(key, default?.inWholeNanoseconds ?: UNSET).takeUnless { it == UNSET }?.nanoseconds
}
override fun setValue(values: KeyValueStore, value: Duration?) {
values.beginWrite().putLong(key, value?.inWholeNanoseconds ?: UNSET).apply()
}
}
private class KeyValueProtoWithDefaultValue<M>(
private val key: String,
default: M,