Move storage service operations into the network module.

This commit is contained in:
Greyson Parrelli
2026-04-18 22:35:59 -04:00
committed by Alex Hart
parent 89c645dea3
commit 4dea1d8aa1
7 changed files with 62 additions and 55 deletions
@@ -16,11 +16,11 @@ import kotlinx.coroutines.withContext
import org.signal.core.util.ByteSize
import org.signal.core.util.bytes
import org.signal.core.util.logging.Log
import org.signal.network.service.StorageServiceService
import org.thoughtcrime.securesms.dependencies.AppDependencies
import org.thoughtcrime.securesms.keyvalue.SignalStore
import org.whispersystems.signalservice.api.storage.SignalStorageManifest
import org.whispersystems.signalservice.api.storage.SignalStorageRecord
import org.whispersystems.signalservice.api.storage.StorageServiceRepository
class InternalStorageServicePlaygroundViewModel : ViewModel() {
@@ -47,12 +47,12 @@ class InternalStorageServicePlaygroundViewModel : ViewModel() {
fun onViewTabSelected() {
viewModelScope.launch {
withContext(Dispatchers.IO) {
val repository = StorageServiceRepository(AppDependencies.storageServiceApi)
val repository = StorageServiceService(AppDependencies.storageServiceApi)
val storageKey = SignalStore.storageService.storageKeyForInitialDataRestore ?: SignalStore.storageService.storageKey
val manifest = when (val result = repository.getStorageManifest(storageKey)) {
is StorageServiceRepository.ManifestResult.Success -> result.manifest
is StorageServiceRepository.ManifestResult.NotFoundError -> {
is StorageServiceService.ManifestResult.Success -> result.manifest
is StorageServiceService.ManifestResult.NotFoundError -> {
Log.w(TAG, "Manifest not found!")
_oneOffEvents.value = OneOffEvent.ManifestNotFoundError
return@withContext
@@ -66,7 +66,7 @@ class InternalStorageServicePlaygroundViewModel : ViewModel() {
_manifest.value = manifest
val records = when (val result = repository.readStorageRecords(storageKey, manifest.recordIkm, manifest.storageIds)) {
is StorageServiceRepository.StorageRecordResult.Success -> result.records
is StorageServiceService.StorageRecordResult.Success -> result.records
else -> {
Log.w(TAG, "Failed to fetch records!")
_oneOffEvents.value = OneOffEvent.StorageRecordDecryptionError
@@ -1,6 +1,8 @@
package org.thoughtcrime.securesms.jobs
import org.signal.core.util.logging.Log
import org.signal.network.service.StorageServiceService
import org.signal.network.service.StorageServiceService.ManifestResult
import org.thoughtcrime.securesms.database.SignalDatabase
import org.thoughtcrime.securesms.dependencies.AppDependencies
import org.thoughtcrime.securesms.jobmanager.Job
@@ -14,8 +16,6 @@ import org.whispersystems.signalservice.api.push.exceptions.PushNetworkException
import org.whispersystems.signalservice.api.storage.SignalAccountRecord
import org.whispersystems.signalservice.api.storage.SignalStorageManifest
import org.whispersystems.signalservice.api.storage.SignalStorageRecord
import org.whispersystems.signalservice.api.storage.StorageServiceRepository
import org.whispersystems.signalservice.api.storage.StorageServiceRepository.ManifestResult
import java.util.concurrent.TimeUnit
/**
@@ -56,7 +56,7 @@ class StorageAccountRestoreJob private constructor(parameters: Parameters) : Bas
SignalStore.storageService.storageKey
}
val repository = StorageServiceRepository(SignalNetwork.storageService)
val repository = StorageServiceService(SignalNetwork.storageService)
Log.i(TAG, "Retrieving manifest...")
val manifest: SignalStorageManifest? = when (val result = repository.getStorageManifest(storageServiceKey)) {
@@ -85,14 +85,14 @@ class StorageAccountRestoreJob private constructor(parameters: Parameters) : Bas
Log.i(TAG, "Retrieving account record...")
val records: List<SignalStorageRecord> = when (val result = repository.readStorageRecords(storageServiceKey, manifest.recordIkm, listOf(accountId.get()))) {
is StorageServiceRepository.StorageRecordResult.Success -> result.records
is StorageServiceRepository.StorageRecordResult.DecryptionError -> {
is StorageServiceService.StorageRecordResult.Success -> result.records
is StorageServiceService.StorageRecordResult.DecryptionError -> {
Log.w(TAG, "Account record was undecryptable. Not restoring. Force-pushing.")
AppDependencies.jobManager.add(StorageForcePushJob())
return
}
is StorageServiceRepository.StorageRecordResult.NetworkError -> throw result.exception
is StorageServiceRepository.StorageRecordResult.StatusCodeError -> throw result.exception
is StorageServiceService.StorageRecordResult.NetworkError -> throw result.exception
is StorageServiceService.StorageRecordResult.StatusCodeError -> throw result.exception
}
val record = if (records.isNotEmpty()) records[0] else null
@@ -3,6 +3,7 @@ package org.thoughtcrime.securesms.jobs
import org.signal.core.util.SqlUtil
import org.signal.core.util.logging.Log
import org.signal.core.util.logging.logI
import org.signal.network.service.StorageServiceService
import org.thoughtcrime.securesms.components.settings.app.chats.folders.ChatFolderId
import org.thoughtcrime.securesms.database.ChatFolderTables.ChatFolderTable
import org.thoughtcrime.securesms.database.NotificationProfileTables
@@ -25,7 +26,6 @@ import org.whispersystems.signalservice.api.storage.RecordIkm
import org.whispersystems.signalservice.api.storage.SignalStorageManifest
import org.whispersystems.signalservice.api.storage.SignalStorageRecord
import org.whispersystems.signalservice.api.storage.StorageId
import org.whispersystems.signalservice.api.storage.StorageServiceRepository
import java.io.IOException
import java.util.Collections
import java.util.concurrent.TimeUnit
@@ -71,7 +71,7 @@ class StorageForcePushJob private constructor(parameters: Parameters) : BaseJob(
}
val storageServiceKey = SignalStore.storageService.storageKey
val repository = StorageServiceRepository(AppDependencies.storageServiceApi)
val repository = StorageServiceService(AppDependencies.storageServiceApi)
val currentVersion: Long = when (val result = repository.getManifestVersion()) {
is NetworkResult.Success -> result.result
@@ -133,10 +133,10 @@ class StorageForcePushJob private constructor(parameters: Parameters) : BaseJob(
if (newVersion > 1) {
Log.i(TAG, "Force-pushing data. Inserting ${inserts.size} IDs.")
when (val result = repository.resetAndWriteStorageRecords(storageServiceKey, manifest, inserts)) {
StorageServiceRepository.WriteStorageRecordsResult.Success -> Unit
is StorageServiceRepository.WriteStorageRecordsResult.StatusCodeError -> throw result.exception
is StorageServiceRepository.WriteStorageRecordsResult.NetworkError -> throw result.exception
StorageServiceRepository.WriteStorageRecordsResult.ConflictError -> {
StorageServiceService.WriteStorageRecordsResult.Success -> Unit
is StorageServiceService.WriteStorageRecordsResult.StatusCodeError -> throw result.exception
is StorageServiceService.WriteStorageRecordsResult.NetworkError -> throw result.exception
StorageServiceService.WriteStorageRecordsResult.ConflictError -> {
Log.w(TAG, "Hit a conflict. Trying again.")
throw RetryLaterException()
}
@@ -144,10 +144,10 @@ class StorageForcePushJob private constructor(parameters: Parameters) : BaseJob(
} else {
Log.i(TAG, "First version, normal push. Inserting ${inserts.size} IDs.")
when (val result = repository.writeStorageRecords(storageServiceKey, manifest, inserts, emptyList())) {
StorageServiceRepository.WriteStorageRecordsResult.Success -> Unit
is StorageServiceRepository.WriteStorageRecordsResult.StatusCodeError -> throw result.exception
is StorageServiceRepository.WriteStorageRecordsResult.NetworkError -> throw result.exception
is StorageServiceRepository.WriteStorageRecordsResult.ConflictError -> {
StorageServiceService.WriteStorageRecordsResult.Success -> Unit
is StorageServiceService.WriteStorageRecordsResult.StatusCodeError -> throw result.exception
is StorageServiceService.WriteStorageRecordsResult.NetworkError -> throw result.exception
is StorageServiceService.WriteStorageRecordsResult.ConflictError -> {
Log.w(TAG, "Hit a conflict. Trying again.")
throw RetryLaterException()
}
@@ -2,12 +2,12 @@ package org.thoughtcrime.securesms.jobs
import org.signal.core.models.storageservice.StorageKey
import org.signal.core.util.logging.Log
import org.signal.network.service.StorageServiceService
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.whispersystems.signalservice.api.storage.SignalStorageManifest
import org.whispersystems.signalservice.api.storage.StorageServiceRepository
import java.util.concurrent.TimeUnit
/**
@@ -52,27 +52,27 @@ class StorageRotateManifestJob private constructor(parameters: Parameters) : Job
}
val storageServiceKey = SignalStore.storageService.storageKey
val repository = StorageServiceRepository(AppDependencies.storageServiceApi)
val repository = StorageServiceService(AppDependencies.storageServiceApi)
val currentManifest: SignalStorageManifest = when (val result = repository.getStorageManifest(restoreKey)) {
is StorageServiceRepository.ManifestResult.Success -> {
is StorageServiceService.ManifestResult.Success -> {
result.manifest
}
is StorageServiceRepository.ManifestResult.DecryptionError -> {
is StorageServiceService.ManifestResult.DecryptionError -> {
Log.w(TAG, "Failed to decrypt the manifest! Only recourse is to force push.", result.exception)
AppDependencies.jobManager.add(StorageForcePushJob())
return Result.failure()
}
is StorageServiceRepository.ManifestResult.NetworkError -> {
is StorageServiceService.ManifestResult.NetworkError -> {
Log.w(TAG, "Encountered a network error during read, retrying.", result.exception)
return Result.retry(defaultBackoff())
}
StorageServiceRepository.ManifestResult.NotFoundError -> {
StorageServiceService.ManifestResult.NotFoundError -> {
Log.w(TAG, "No existing manifest was found! Force pushing.")
AppDependencies.jobManager.add(StorageForcePushJob())
return Result.failure()
}
is StorageServiceRepository.ManifestResult.StatusCodeError -> {
is StorageServiceService.ManifestResult.StatusCodeError -> {
Log.w(TAG, "Encountered a status code error during read, retrying.", result.exception)
return Result.retry(defaultBackoff())
}
@@ -87,7 +87,7 @@ class StorageRotateManifestJob private constructor(parameters: Parameters) : Job
val manifestWithNewVersion = currentManifest.copy(version = currentManifest.version + 1)
return when (val result = repository.writeUnchangedManifest(storageServiceKey, manifestWithNewVersion)) {
StorageServiceRepository.WriteStorageRecordsResult.Success -> {
StorageServiceService.WriteStorageRecordsResult.Success -> {
Log.i(TAG, "Successfully rotated the manifest as version ${manifestWithNewVersion.version}.${manifestWithNewVersion.sourceDeviceId}. Clearing restore key.")
SignalStore.svr.masterKeyForInitialDataRestore = null
@@ -96,18 +96,18 @@ class StorageRotateManifestJob private constructor(parameters: Parameters) : Job
Result.success()
}
StorageServiceRepository.WriteStorageRecordsResult.ConflictError -> {
StorageServiceService.WriteStorageRecordsResult.ConflictError -> {
Log.w(TAG, "Hit a conflict! Enqueuing a sync followed by another rotation.")
AppDependencies.jobManager.add(StorageSyncJob.forRemoteChange())
AppDependencies.jobManager.add(StorageRotateManifestJob())
Result.failure()
}
is StorageServiceRepository.WriteStorageRecordsResult.StatusCodeError -> {
is StorageServiceService.WriteStorageRecordsResult.StatusCodeError -> {
Log.w(TAG, "Encountered a non-conflict status code error during write. Failing.", result.exception)
Result.failure()
}
is StorageServiceRepository.WriteStorageRecordsResult.NetworkError -> {
is StorageServiceService.WriteStorageRecordsResult.NetworkError -> {
Log.w(TAG, "Encountered a network error during write, retrying.", result.exception)
Result.retry(defaultBackoff())
}
@@ -8,6 +8,8 @@ import org.signal.core.util.Stopwatch
import org.signal.core.util.logging.Log
import org.signal.core.util.withinTransaction
import org.signal.libsignal.protocol.InvalidKeyException
import org.signal.network.service.StorageServiceService
import org.signal.network.service.StorageServiceService.ManifestIfDifferentVersionResult
import org.thoughtcrime.securesms.database.ChatFolderTables.ChatFolderTable
import org.thoughtcrime.securesms.database.NotificationProfileTables
import org.thoughtcrime.securesms.database.RecipientTable
@@ -47,8 +49,6 @@ import org.whispersystems.signalservice.api.storage.SignalStorageManifest
import org.whispersystems.signalservice.api.storage.SignalStorageRecord
import org.whispersystems.signalservice.api.storage.SignalStoryDistributionListRecord
import org.whispersystems.signalservice.api.storage.StorageId
import org.whispersystems.signalservice.api.storage.StorageServiceRepository
import org.whispersystems.signalservice.api.storage.StorageServiceRepository.ManifestIfDifferentVersionResult
import org.whispersystems.signalservice.api.storage.toSignalAccountRecord
import org.whispersystems.signalservice.api.storage.toSignalCallLinkRecord
import org.whispersystems.signalservice.api.storage.toSignalChatFolderRecord
@@ -247,7 +247,7 @@ class StorageSyncJob private constructor(parameters: Parameters, private var loc
private fun performSync(storageServiceKey: StorageKey): Boolean {
val stopwatch = Stopwatch("StorageSync")
val db = SignalDatabase.rawDatabase
val repository = StorageServiceRepository(SignalNetwork.storageService)
val repository = StorageServiceService(SignalNetwork.storageService)
val localManifest = SignalStore.storageService.manifest
val remoteManifest = if (localManifestOutOfDate || localManifest.version < 1 || runAttempt >= 3) {
@@ -309,10 +309,10 @@ class StorageSyncJob private constructor(parameters: Parameters, private var loc
Log.i(TAG, "[Remote Sync] Retrieving records for key difference.")
val remoteOnlyRecords = when (val result = repository.readStorageRecords(storageServiceKey, remoteManifest.recordIkm, idDifference.remoteOnlyIds)) {
is StorageServiceRepository.StorageRecordResult.Success -> result.records
is StorageServiceRepository.StorageRecordResult.DecryptionError -> throw result.exception
is StorageServiceRepository.StorageRecordResult.NetworkError -> throw result.exception
is StorageServiceRepository.StorageRecordResult.StatusCodeError -> throw result.exception
is StorageServiceService.StorageRecordResult.Success -> result.records
is StorageServiceService.StorageRecordResult.DecryptionError -> throw result.exception
is StorageServiceService.StorageRecordResult.NetworkError -> throw result.exception
is StorageServiceService.StorageRecordResult.StatusCodeError -> throw result.exception
}
stopwatch.split("remote-records")
@@ -400,10 +400,10 @@ class StorageSyncJob private constructor(parameters: Parameters, private var loc
StorageSyncValidations.validate(remoteWriteOperation, remoteManifest, needsForcePush, self)
when (val result = repository.writeStorageRecords(storageServiceKey, remoteWriteOperation.manifest, remoteWriteOperation.inserts, remoteWriteOperation.deletes)) {
StorageServiceRepository.WriteStorageRecordsResult.Success -> Unit
is StorageServiceRepository.WriteStorageRecordsResult.StatusCodeError -> throw result.exception
is StorageServiceRepository.WriteStorageRecordsResult.NetworkError -> throw result.exception
StorageServiceRepository.WriteStorageRecordsResult.ConflictError -> {
StorageServiceService.WriteStorageRecordsResult.Success -> Unit
is StorageServiceService.WriteStorageRecordsResult.StatusCodeError -> throw result.exception
is StorageServiceService.WriteStorageRecordsResult.NetworkError -> throw result.exception
StorageServiceService.WriteStorageRecordsResult.ConflictError -> {
Log.w(TAG, "Hit a conflict when trying to resolve the conflict! Retrying.")
localManifestOutOfDate = true
throw RetryLaterException()
@@ -428,10 +428,10 @@ class StorageSyncJob private constructor(parameters: Parameters, private var loc
Log.i(TAG, "We have ${knownUnknownIds.size} unknown records that we can now process.")
val remote = when (val result = repository.readStorageRecords(storageServiceKey, remoteManifest.recordIkm, knownUnknownIds)) {
is StorageServiceRepository.StorageRecordResult.Success -> result.records
is StorageServiceRepository.StorageRecordResult.DecryptionError -> throw result.exception
is StorageServiceRepository.StorageRecordResult.NetworkError -> throw result.exception
is StorageServiceRepository.StorageRecordResult.StatusCodeError -> throw result.exception
is StorageServiceService.StorageRecordResult.Success -> result.records
is StorageServiceService.StorageRecordResult.DecryptionError -> throw result.exception
is StorageServiceService.StorageRecordResult.NetworkError -> throw result.exception
is StorageServiceService.StorageRecordResult.StatusCodeError -> throw result.exception
}
val records = StorageRecordCollection(remote)