Implement checkin job for backups.
This commit is contained in:
parent
ae37001949
commit
6ff31b950d
8 changed files with 146 additions and 0 deletions
|
@ -54,6 +54,7 @@ import org.thoughtcrime.securesms.emoji.EmojiSource;
|
|||
import org.thoughtcrime.securesms.emoji.JumboEmoji;
|
||||
import org.thoughtcrime.securesms.gcm.FcmFetchManager;
|
||||
import org.thoughtcrime.securesms.jobs.AccountConsistencyWorkerJob;
|
||||
import org.thoughtcrime.securesms.jobs.BackupRefreshJob;
|
||||
import org.thoughtcrime.securesms.jobs.BackupSubscriptionCheckJob;
|
||||
import org.thoughtcrime.securesms.jobs.BuildExpirationConfirmationJob;
|
||||
import org.thoughtcrime.securesms.jobs.CheckServiceReachabilityJob;
|
||||
|
@ -247,6 +248,7 @@ public class ApplicationContext extends Application implements AppForegroundObse
|
|||
startAnrDetector();
|
||||
|
||||
SignalExecutors.BOUNDED.execute(() -> {
|
||||
BackupRefreshJob.enqueueIfNecessary();
|
||||
InAppPaymentAuthCheckJob.enqueueIfNeeded();
|
||||
RemoteConfig.refreshIfNecessary();
|
||||
RetrieveProfileJob.enqueueRoutineFetchIfNecessary();
|
||||
|
|
|
@ -132,6 +132,19 @@ object BackupRepository {
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Refreshes backup via server
|
||||
*/
|
||||
fun refreshBackup(): NetworkResult<Unit> {
|
||||
return initBackupAndFetchAuth()
|
||||
.then { accessPair ->
|
||||
AppDependencies.archiveApi.refreshBackup(
|
||||
aci = SignalStore.account.requireAci(),
|
||||
archiveServiceAccess = accessPair.messageBackupAccess
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the free storage space in the device's data partition.
|
||||
*/
|
||||
|
|
|
@ -0,0 +1,105 @@
|
|||
/*
|
||||
* Copyright 2024 Signal Messenger, LLC
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
package org.thoughtcrime.securesms.jobs
|
||||
|
||||
import org.signal.core.util.logging.Log
|
||||
import org.thoughtcrime.securesms.backup.v2.BackupRepository
|
||||
import org.thoughtcrime.securesms.dependencies.AppDependencies
|
||||
import org.thoughtcrime.securesms.jobmanager.Job
|
||||
import org.thoughtcrime.securesms.jobmanager.impl.NetworkConstraint
|
||||
import org.thoughtcrime.securesms.keyvalue.SignalStore
|
||||
import org.thoughtcrime.securesms.util.RemoteConfig
|
||||
import org.whispersystems.signalservice.api.NetworkResult
|
||||
import kotlin.time.Duration.Companion.days
|
||||
import kotlin.time.Duration.Companion.milliseconds
|
||||
|
||||
/**
|
||||
* Notifies the server that the backup for the local user is still being used.
|
||||
*/
|
||||
class BackupRefreshJob private constructor(
|
||||
parameters: Parameters
|
||||
) : Job(parameters) {
|
||||
|
||||
companion object {
|
||||
private val TAG = Log.tag(BackupRefreshJob::class)
|
||||
const val KEY = "BackupRefreshJob"
|
||||
|
||||
private val TIME_BETWEEN_CHECKINS = 3.days
|
||||
|
||||
@JvmStatic
|
||||
fun enqueueIfNecessary() {
|
||||
if (!canExecuteJob()) {
|
||||
return
|
||||
}
|
||||
|
||||
val now = System.currentTimeMillis().milliseconds
|
||||
val lastCheckIn = SignalStore.backup.lastCheckInMillis.milliseconds
|
||||
|
||||
if ((now - lastCheckIn) >= TIME_BETWEEN_CHECKINS) {
|
||||
AppDependencies.jobManager.add(
|
||||
BackupRefreshJob(
|
||||
parameters = Parameters.Builder()
|
||||
.addConstraint(NetworkConstraint.KEY)
|
||||
.setMaxAttempts(Parameters.UNLIMITED)
|
||||
.setLifespan(3.days.inWholeMilliseconds)
|
||||
.setMaxInstancesForFactory(1)
|
||||
.build()
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private fun canExecuteJob(): Boolean {
|
||||
if (!SignalStore.account.isRegistered) {
|
||||
Log.i(TAG, "Account not registered. Exiting.")
|
||||
return false
|
||||
}
|
||||
|
||||
if (!RemoteConfig.messageBackups) {
|
||||
Log.i(TAG, "Backups are not enabled in remote config. Exiting.")
|
||||
return false
|
||||
}
|
||||
|
||||
if (!SignalStore.backup.areBackupsEnabled) {
|
||||
Log.i(TAG, "Backups have not been enabled on this device. Exiting.")
|
||||
return false
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
override fun run(): Result {
|
||||
if (!canExecuteJob()) {
|
||||
return Result.success()
|
||||
}
|
||||
|
||||
val result = BackupRepository.refreshBackup()
|
||||
|
||||
return when (result) {
|
||||
is NetworkResult.Success -> {
|
||||
SignalStore.backup.lastCheckInMillis = System.currentTimeMillis()
|
||||
Result.success()
|
||||
}
|
||||
else -> {
|
||||
Log.w(TAG, "Failed to refresh backup with server.", result.getCause())
|
||||
Result.failure()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun serialize(): ByteArray? = null
|
||||
|
||||
override fun getFactoryKey(): String = KEY
|
||||
|
||||
override fun onFailure() = Unit
|
||||
|
||||
class Factory : Job.Factory<BackupRefreshJob> {
|
||||
override fun create(parameters: Parameters, serializedData: ByteArray?): BackupRefreshJob {
|
||||
return BackupRefreshJob(parameters)
|
||||
}
|
||||
}
|
||||
}
|
|
@ -258,6 +258,7 @@ class InAppPaymentRedemptionJob private constructor(
|
|||
if (inAppPayment.type == InAppPaymentType.RECURRING_BACKUP) {
|
||||
Log.i(TAG, "Setting backup tier to PAID", true)
|
||||
SignalStore.backup.backupTier = MessageBackupTier.PAID
|
||||
SignalStore.backup.lastCheckInMillis = System.currentTimeMillis()
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -271,6 +271,7 @@ public final class JobManagerFactories {
|
|||
put(BackfillDigestsForDuplicatesMigrationJob.KEY, new BackfillDigestsForDuplicatesMigrationJob.Factory());
|
||||
put(BackupJitterMigrationJob.KEY, new BackupJitterMigrationJob.Factory());
|
||||
put(BackupNotificationMigrationJob.KEY, new BackupNotificationMigrationJob.Factory());
|
||||
put(BackupRefreshJob.KEY, new BackupRefreshJob.Factory());
|
||||
put(BlobStorageLocationMigrationJob.KEY, new BlobStorageLocationMigrationJob.Factory());
|
||||
put(CachedAttachmentsMigrationJob.KEY, new CachedAttachmentsMigrationJob.Factory());
|
||||
put(ClearGlideCacheMigrationJob.KEY, new ClearGlideCacheMigrationJob.Factory());
|
||||
|
|
|
@ -35,6 +35,7 @@ class BackupValues(store: KeyValueStore) : SignalStoreValues(store) {
|
|||
private const val KEY_BACKUP_LAST_PROTO_SIZE = "backup.lastProtoSize"
|
||||
private const val KEY_BACKUP_TIER = "backup.backupTier"
|
||||
private const val KEY_LATEST_BACKUP_TIER = "backup.latestBackupTier"
|
||||
private const val KEY_LAST_CHECK_IN_MILLIS = "backup.lastCheckInMilliseconds"
|
||||
|
||||
private const val KEY_NEXT_BACKUP_TIME = "backup.nextBackupTime"
|
||||
private const val KEY_LAST_BACKUP_TIME = "backup.lastBackupTime"
|
||||
|
@ -94,6 +95,8 @@ class BackupValues(store: KeyValueStore) : SignalStoreValues(store) {
|
|||
|
||||
var userManuallySkippedMediaRestore: Boolean by booleanValue(KEY_USER_MANUALLY_SKIPPED_MEDIA_RESTORE, false)
|
||||
|
||||
var lastCheckInMillis: Long by longValue(KEY_LAST_CHECK_IN_MILLIS, 0L)
|
||||
|
||||
/**
|
||||
* Key used to backup messages.
|
||||
*/
|
||||
|
|
|
@ -150,6 +150,18 @@ class ArchiveApi(private val pushServiceSocket: PushServiceSocket) {
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Backup keep-alive that informs the server that the backup is still in use. If a backup is not refreshed, it may be deleted
|
||||
* after 30 days.
|
||||
*/
|
||||
fun refreshBackup(aci: ACI, archiveServiceAccess: ArchiveServiceAccess<MessageBackupKey>): NetworkResult<Unit> {
|
||||
return NetworkResult.fromFetch {
|
||||
val zkCredential = getZkCredential(aci, archiveServiceAccess)
|
||||
val presentationData = CredentialPresentationData.from(archiveServiceAccess.backupKey, aci, zkCredential, backupServerPublicParams)
|
||||
pushServiceSocket.refreshBackup(presentationData.toArchiveCredentialPresentation())
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Lists the media objects in the backup
|
||||
*/
|
||||
|
|
|
@ -563,6 +563,15 @@ public class PushServiceSocket {
|
|||
return JsonUtil.fromJson(response, ArchiveGetBackupInfoResponse.class);
|
||||
}
|
||||
|
||||
/**
|
||||
* POST credential presentation to the server to keep backup alive.
|
||||
*/
|
||||
public void refreshBackup(ArchiveCredentialPresentation credentialPresentation) throws IOException {
|
||||
Map<String, String> headers = credentialPresentation.toHeaders();
|
||||
|
||||
makeServiceRequestWithoutAuthentication(ARCHIVE_INFO, "POST", null, headers, NO_HANDLER);
|
||||
}
|
||||
|
||||
public List<ArchiveGetMediaItemsResponse.StoredMediaObject> debugGetAllArchiveMediaItems(ArchiveCredentialPresentation credentialPresentation) throws IOException {
|
||||
List<ArchiveGetMediaItemsResponse.StoredMediaObject> mediaObjects = new ArrayList<>();
|
||||
|
||||
|
|
Loading…
Add table
Reference in a new issue