diff --git a/app/src/main/java/org/thoughtcrime/securesms/backup/v2/BackupRepository.kt b/app/src/main/java/org/thoughtcrime/securesms/backup/v2/BackupRepository.kt index c6d28a1472..221a3e4747 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/backup/v2/BackupRepository.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/backup/v2/BackupRepository.kt @@ -8,7 +8,6 @@ package org.thoughtcrime.securesms.backup.v2 import android.database.Cursor import android.os.Environment import android.os.StatFs -import androidx.annotation.Discouraged import androidx.annotation.WorkerThread import okio.ByteString import okio.ByteString.Companion.toByteString @@ -35,7 +34,6 @@ import org.signal.core.util.withinTransaction import org.signal.libsignal.zkgroup.backups.BackupLevel import org.signal.libsignal.zkgroup.profiles.ProfileKey import org.thoughtcrime.securesms.attachments.Attachment -import org.thoughtcrime.securesms.attachments.AttachmentId import org.thoughtcrime.securesms.attachments.Cdn import org.thoughtcrime.securesms.attachments.DatabaseAttachment import org.thoughtcrime.securesms.backup.ArchiveUploadProgress @@ -1105,21 +1103,6 @@ object BackupRepository { .also { Log.i(TAG, "UploadBackupFileResult: $it") } } - /** - * A simple test method that just hits various network endpoints. Only useful for the playground. - * - * @return True if successful, otherwise false. - */ - @Discouraged("This will upload the entire backup file on every execution.") - fun debugUploadBackupFile(backupStream: InputStream, backupStreamLength: Long): NetworkResult { - return getResumableMessagesBackupUploadSpec() - .then { formAndUploadUrl -> - val (form, resumableUploadUrl) = formAndUploadUrl - SignalNetwork.archive.uploadBackupFile(form, resumableUploadUrl, backupStream, backupStreamLength) - .also { Log.i(TAG, "UploadBackupFileResult: $it") } - } - } - fun downloadBackupFile(destination: File, listener: ProgressListener? = null): NetworkResult { return initBackupAndFetchAuth() .then { credential -> @@ -1208,74 +1191,6 @@ object BackupRepository { .also { Log.i(TAG, "archiveMediaResult: $it") } } - fun copyAttachmentToArchive(databaseAttachments: List): NetworkResult { - return initBackupAndFetchAuth() - .then { credential -> - val requests = mutableListOf() - val mediaIdToAttachmentId = mutableMapOf() - val attachmentIdToMediaName = mutableMapOf() - - databaseAttachments.forEach { - val mediaName = it.requireMediaName() - val request = it.toArchiveMediaRequest(mediaName, credential.mediaBackupAccess.backupKey) - requests += request - mediaIdToAttachmentId[request.mediaId] = it.attachmentId - attachmentIdToMediaName[it.attachmentId] = mediaName.name - } - - SignalNetwork.archive - .copyAttachmentToArchive( - aci = SignalStore.account.requireAci(), - archiveServiceAccess = credential.mediaBackupAccess, - items = requests - ) - .map { credential to BatchArchiveMediaResult(it, mediaIdToAttachmentId, attachmentIdToMediaName) } - } - .map { (credential, result) -> - result - .successfulResponses - .forEach { - val attachmentId = result.mediaIdToAttachmentId(it.mediaId) - val mediaName = result.attachmentIdToMediaName(attachmentId) - val thumbnailId = credential.mediaBackupAccess.backupKey.deriveMediaId(MediaName.forThumbnailFromMediaName(mediaName = mediaName)).encode() - SignalDatabase.attachments.setArchiveCdn(attachmentId = attachmentId, archiveCdn = it.cdn!!) - } - result - } - .also { Log.i(TAG, "archiveMediaResult: $it") } - } - - fun deleteArchivedMedia(attachments: List): NetworkResult { - val mediaRootBackupKey = SignalStore.backup.mediaRootBackupKey - - val mediaToDelete = attachments - .filter { it.archiveTransferState == AttachmentTable.ArchiveTransferState.FINISHED } - .map { - DeleteArchivedMediaRequest.ArchivedMediaObject( - cdn = it.archiveCdn, - mediaId = it.requireMediaName().toMediaId(mediaRootBackupKey).encode() - ) - } - - if (mediaToDelete.isEmpty()) { - Log.i(TAG, "No media to delete, quick success") - return NetworkResult.Success(Unit) - } - - return initBackupAndFetchAuth() - .then { credential -> - SignalNetwork.archive.deleteArchivedMedia( - aci = SignalStore.account.requireAci(), - archiveServiceAccess = credential.mediaBackupAccess, - mediaToDelete = mediaToDelete - ) - } - .map { - SignalDatabase.attachments.clearArchiveData(attachments.map { it.attachmentId }) - } - .also { Log.i(TAG, "deleteArchivedMediaResult: $it") } - } - fun deleteAbandonedMediaObjects(mediaObjects: Collection): NetworkResult { val mediaToDelete = mediaObjects .map { diff --git a/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/internal/backup/InternalBackupPlaygroundFragment.kt b/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/internal/backup/InternalBackupPlaygroundFragment.kt index 43a675a2ca..e9e7b5e716 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/internal/backup/InternalBackupPlaygroundFragment.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/internal/backup/InternalBackupPlaygroundFragment.kt @@ -11,26 +11,16 @@ import android.os.Bundle import android.widget.Toast import androidx.activity.result.ActivityResultLauncher import androidx.activity.result.contract.ActivityResultContracts -import androidx.compose.foundation.ExperimentalFoundationApi -import androidx.compose.foundation.background -import androidx.compose.foundation.combinedClickable import androidx.compose.foundation.layout.Arrangement -import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.padding -import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.foundation.rememberScrollState -import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.foundation.text.KeyboardOptions import androidx.compose.foundation.verticalScroll -import androidx.compose.material3.Button -import androidx.compose.material3.CircularProgressIndicator -import androidx.compose.material3.DropdownMenu -import androidx.compose.material3.DropdownMenuItem import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.Icon import androidx.compose.material3.IconButton @@ -45,7 +35,6 @@ import androidx.compose.material3.Text import androidx.compose.material3.TextButton import androidx.compose.material3.TopAppBar import androidx.compose.runtime.Composable -import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableIntStateOf import androidx.compose.runtime.mutableStateOf @@ -54,9 +43,7 @@ import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color -import androidx.compose.ui.hapticfeedback.HapticFeedbackType import androidx.compose.ui.platform.LocalContext -import androidx.compose.ui.platform.LocalHapticFeedback import androidx.compose.ui.res.painterResource import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.text.input.ImeAction @@ -81,9 +68,7 @@ import org.signal.core.util.Base64 import org.signal.core.util.Hex import org.signal.core.util.getLength import org.thoughtcrime.securesms.R -import org.thoughtcrime.securesms.attachments.AttachmentId import org.thoughtcrime.securesms.backup.v2.MessageBackupTier -import org.thoughtcrime.securesms.components.compose.RoundCheckbox import org.thoughtcrime.securesms.components.settings.app.internal.backup.InternalBackupPlaygroundViewModel.DialogState import org.thoughtcrime.securesms.components.settings.app.internal.backup.InternalBackupPlaygroundViewModel.ScreenState import org.thoughtcrime.securesms.compose.ComposeFragment @@ -148,15 +133,9 @@ class InternalBackupPlaygroundFragment : ComposeFragment() { override fun FragmentContent() { val context = LocalContext.current val state by viewModel.state - val mediaState by viewModel.mediaState - - LaunchedEffect(Unit) { - viewModel.loadMedia() - } Tabs( onBack = { findNavController().popBackStack() }, - onDeleteAllArchivedMedia = { viewModel.deleteAllArchivedMedia() }, mainContent = { Screen( state = state, @@ -246,19 +225,6 @@ class InternalBackupPlaygroundFragment : ComposeFragment() { .show() } ) - }, - mediaContent = { snackbarHostState -> - MediaList( - enabled = SignalStore.backup.backsUpMedia, - state = mediaState, - snackbarHostState = snackbarHostState, - archiveAttachmentMedia = { viewModel.archiveAttachmentMedia(it) }, - deleteArchivedMedia = { viewModel.deleteArchivedMedia(it) }, - batchArchiveAttachmentMedia = { viewModel.archiveAttachmentMedia(it) }, - batchDeleteBackupAttachmentMedia = { viewModel.deleteArchivedMedia(it) }, - restoreArchivedMedia = { viewModel.restoreArchivedMedia(it, asThumbnail = false) }, - restoreArchivedMediaThumbnail = { viewModel.restoreArchivedMedia(it, asThumbnail = true) } - ) } ) } @@ -268,11 +234,9 @@ class InternalBackupPlaygroundFragment : ComposeFragment() { @Composable fun Tabs( onBack: () -> Unit, - onDeleteAllArchivedMedia: () -> Unit, - mainContent: @Composable () -> Unit, - mediaContent: @Composable (snackbarHostState: SnackbarHostState) -> Unit + mainContent: @Composable () -> Unit ) { - val tabs = listOf("Main", "Media") + val tabs = listOf("Main") var tabIndex by remember { mutableIntStateOf(0) } val snackbarHostState: SnackbarHostState = remember { SnackbarHostState() } @@ -293,13 +257,6 @@ fun Tabs( contentDescription = null ) } - }, - actions = { - if (tabIndex == 1 && SignalStore.backup.backsUpMedia) { - TextButton(onClick = onDeleteAllArchivedMedia) { - Text(text = "Delete All") - } - } } ) TabRow(selectedTabIndex = tabIndex) { @@ -317,7 +274,6 @@ fun Tabs( Surface(modifier = Modifier.padding(it)) { when (tabIndex) { 0 -> mainContent() - 1 -> mediaContent(snackbarHostState) } } } @@ -604,181 +560,6 @@ private fun ImportCredentialsDialog(onSubmit: (aci: String, backupKey: String) - ) } -@OptIn(ExperimentalFoundationApi::class) -@Composable -fun MediaList( - enabled: Boolean, - state: InternalBackupPlaygroundViewModel.MediaState, - snackbarHostState: SnackbarHostState, - archiveAttachmentMedia: (InternalBackupPlaygroundViewModel.BackupAttachment) -> Unit, - deleteArchivedMedia: (InternalBackupPlaygroundViewModel.BackupAttachment) -> Unit, - batchArchiveAttachmentMedia: (Set) -> Unit, - batchDeleteBackupAttachmentMedia: (Set) -> Unit, - restoreArchivedMedia: (InternalBackupPlaygroundViewModel.BackupAttachment) -> Unit, - restoreArchivedMediaThumbnail: (InternalBackupPlaygroundViewModel.BackupAttachment) -> Unit -) { - if (!enabled) { - Text( - text = "You do not have read/write to archive cdn enabled via SignalStore.backup", - modifier = Modifier - .padding(16.dp) - ) - return - } - - LaunchedEffect(state.error?.id) { - state.error?.let { - snackbarHostState.showSnackbar(it.errorText) - } - } - - val haptics = LocalHapticFeedback.current - var selectionState by remember { mutableStateOf(MediaMultiSelectState()) } - - Box(modifier = Modifier.fillMaxSize()) { - LazyColumn(modifier = Modifier.fillMaxSize()) { - items( - count = state.attachments.size, - key = { index -> state.attachments[index].id } - ) { index -> - val attachment = state.attachments[index] - Row( - modifier = Modifier - .combinedClickable(onClick = { - if (selectionState.selecting) { - selectionState = selectionState.copy(selected = if (selectionState.selected.contains(attachment.id)) selectionState.selected - attachment.id else selectionState.selected + attachment.id) - } - }, onLongClick = { - haptics.performHapticFeedback(HapticFeedbackType.LongPress) - selectionState = if (selectionState.selecting) MediaMultiSelectState() else MediaMultiSelectState(selecting = true, selected = setOf(attachment.id)) - }) - .padding(horizontal = 16.dp, vertical = 8.dp) - ) { - if (selectionState.selecting) { - RoundCheckbox( - checked = selectionState.selected.contains(attachment.id), - onCheckedChange = { selected -> - selectionState = selectionState.copy(selected = if (selected) selectionState.selected + attachment.id else selectionState.selected - attachment.id) - } - ) - } - - Column(modifier = Modifier.weight(1f, true)) { - Text(text = attachment.title) - Text(text = "State: ${attachment.state}") - } - - if (attachment.state == InternalBackupPlaygroundViewModel.BackupAttachment.State.IN_PROGRESS) { - CircularProgressIndicator() - } else { - Button( - enabled = !selectionState.selecting, - onClick = { - when (attachment.state) { - InternalBackupPlaygroundViewModel.BackupAttachment.State.ATTACHMENT_CDN, - InternalBackupPlaygroundViewModel.BackupAttachment.State.LOCAL_ONLY -> archiveAttachmentMedia(attachment) - - InternalBackupPlaygroundViewModel.BackupAttachment.State.UPLOADED_UNDOWNLOADED, - InternalBackupPlaygroundViewModel.BackupAttachment.State.UPLOADED_FINAL -> selectionState = selectionState.copy(expandedOption = attachment.dbAttachment.attachmentId) - - else -> throw AssertionError("Unsupported state: ${attachment.state}") - } - } - ) { - Text( - text = when (attachment.state) { - InternalBackupPlaygroundViewModel.BackupAttachment.State.ATTACHMENT_CDN, - InternalBackupPlaygroundViewModel.BackupAttachment.State.LOCAL_ONLY -> "Backup" - - InternalBackupPlaygroundViewModel.BackupAttachment.State.UPLOADED_UNDOWNLOADED, - InternalBackupPlaygroundViewModel.BackupAttachment.State.UPLOADED_FINAL -> "Options..." - - else -> throw AssertionError("Unsupported state: ${attachment.state}") - } - ) - - DropdownMenu( - expanded = attachment.dbAttachment.attachmentId == selectionState.expandedOption, - onDismissRequest = { selectionState = selectionState.copy(expandedOption = null) } - ) { - DropdownMenuItem( - text = { Text("Remote Delete") }, - onClick = { - selectionState = selectionState.copy(expandedOption = null) - deleteArchivedMedia(attachment) - } - ) - - DropdownMenuItem( - text = { Text("Pseudo Restore") }, - onClick = { - selectionState = selectionState.copy(expandedOption = null) - restoreArchivedMedia(attachment) - } - ) - - DropdownMenuItem( - text = { Text("Pseudo Restore Thumbnail") }, - onClick = { - selectionState = selectionState.copy(expandedOption = null) - restoreArchivedMediaThumbnail(attachment) - } - ) - - if (attachment.dbAttachment.dataHash != null && attachment.state == InternalBackupPlaygroundViewModel.BackupAttachment.State.UPLOADED_UNDOWNLOADED) { - DropdownMenuItem( - text = { Text("Re-copy with hash") }, - onClick = { - selectionState = selectionState.copy(expandedOption = null) - archiveAttachmentMedia(attachment) - } - ) - } - } - } - } - } - } - } - - if (selectionState.selecting) { - Row( - horizontalArrangement = Arrangement.spacedBy(8.dp), - modifier = Modifier - .align(Alignment.BottomCenter) - .padding(bottom = 24.dp) - .background( - color = MaterialTheme.colorScheme.secondaryContainer, - shape = RoundedCornerShape(8.dp) - ) - .padding(8.dp) - ) { - Button(onClick = { selectionState = MediaMultiSelectState() }) { - Text("Cancel") - } - Button(onClick = { - batchArchiveAttachmentMedia(selectionState.selected) - selectionState = MediaMultiSelectState() - }) { - Text("Backup") - } - Button(onClick = { - batchDeleteBackupAttachmentMedia(selectionState.selected) - selectionState = MediaMultiSelectState() - }) { - Text("Delete") - } - } - } - } -} - -private data class MediaMultiSelectState( - val selecting: Boolean = false, - val selected: Set = emptySet(), - val expandedOption: AttachmentId? = null -) - @SignalPreview @Composable fun PreviewScreen() { diff --git a/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/internal/backup/InternalBackupPlaygroundViewModel.kt b/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/internal/backup/InternalBackupPlaygroundViewModel.kt index 3a9251d030..20e5b5d0bc 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/internal/backup/InternalBackupPlaygroundViewModel.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/internal/backup/InternalBackupPlaygroundViewModel.kt @@ -12,7 +12,6 @@ import androidx.compose.runtime.mutableStateOf import androidx.documentfile.provider.DocumentFile import androidx.lifecycle.ViewModel import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers -import io.reactivex.rxjava3.core.Completable import io.reactivex.rxjava3.core.Single import io.reactivex.rxjava3.disposables.CompositeDisposable import io.reactivex.rxjava3.kotlin.plusAssign @@ -43,19 +42,12 @@ import org.thoughtcrime.securesms.backup.v2.local.LocalArchiver.FailureCause import org.thoughtcrime.securesms.backup.v2.local.SnapshotFileSystem import org.thoughtcrime.securesms.backup.v2.stream.EncryptedBackupReader.Companion.MAC_SIZE import org.thoughtcrime.securesms.database.AttachmentTable -import org.thoughtcrime.securesms.database.MessageType -import org.thoughtcrime.securesms.database.SignalDatabase import org.thoughtcrime.securesms.dependencies.AppDependencies -import org.thoughtcrime.securesms.jobs.AttachmentUploadJob import org.thoughtcrime.securesms.jobs.BackupMessagesJob import org.thoughtcrime.securesms.jobs.BackupRestoreJob import org.thoughtcrime.securesms.jobs.BackupRestoreMediaJob -import org.thoughtcrime.securesms.jobs.CopyAttachmentToArchiveJob -import org.thoughtcrime.securesms.jobs.RestoreAttachmentJob -import org.thoughtcrime.securesms.jobs.RestoreAttachmentThumbnailJob import org.thoughtcrime.securesms.jobs.RestoreLocalAttachmentJob import org.thoughtcrime.securesms.keyvalue.SignalStore -import org.thoughtcrime.securesms.mms.IncomingMessage import org.thoughtcrime.securesms.providers.BlobProvider import org.thoughtcrime.securesms.recipients.Recipient import org.whispersystems.signalservice.api.NetworkResult @@ -92,9 +84,6 @@ class InternalBackupPlaygroundViewModel : ViewModel() { ) val state: State = _state - private val _mediaState: MutableState = mutableStateOf(MediaState()) - val mediaState: State = _mediaState - enum class DialogState { None, ImportCredentials @@ -359,179 +348,6 @@ class InternalBackupPlaygroundViewModel : ViewModel() { } } - fun loadMedia() { - disposables += Single - .fromCallable { SignalDatabase.attachments.debugGetLatestAttachments() } - .subscribeOn(Schedulers.io()) - .observeOn(Schedulers.single()) - .subscribeBy { - _mediaState.set { update(attachments = it.map { a -> BackupAttachment(dbAttachment = a) }) } - } - } - - fun archiveAttachmentMedia(attachments: Set) { - disposables += Single - .fromCallable { - val toArchive = mediaState.value - .attachments - .filter { attachments.contains(it.dbAttachment.attachmentId) } - .map { it.dbAttachment } - - BackupRepository.copyAttachmentToArchive(toArchive) - } - .subscribeOn(Schedulers.io()) - .observeOn(Schedulers.single()) - .doOnSubscribe { _mediaState.set { update(inProgress = inProgressMediaIds + attachments) } } - .doOnTerminate { _mediaState.set { update(inProgress = inProgressMediaIds - attachments) } } - .subscribeBy { result -> - when (result) { - is NetworkResult.Success -> { - loadMedia() - result - .result - .sourceNotFoundResponses - .forEach { - reUploadAndArchiveMedia(result.result.mediaIdToAttachmentId(it.mediaId)) - } - } - - else -> _mediaState.set { copy(error = MediaStateError(errorText = "$result")) } - } - } - } - - fun archiveAttachmentMedia(attachment: BackupAttachment) { - disposables += Single.fromCallable { BackupRepository.copyAttachmentToArchive(attachment.dbAttachment) } - .subscribeOn(Schedulers.io()) - .observeOn(Schedulers.single()) - .doOnSubscribe { _mediaState.set { update(inProgress = inProgressMediaIds + attachment.dbAttachment.attachmentId) } } - .doOnTerminate { _mediaState.set { update(inProgress = inProgressMediaIds - attachment.dbAttachment.attachmentId) } } - .subscribeBy { result -> - when (result) { - is NetworkResult.Success -> loadMedia() - is NetworkResult.StatusCodeError -> { - if (result.code == 410) { - reUploadAndArchiveMedia(attachment.id) - } else { - _mediaState.set { copy(error = MediaStateError(errorText = "$result")) } - } - } - - else -> _mediaState.set { copy(error = MediaStateError(errorText = "$result")) } - } - } - } - - private fun reUploadAndArchiveMedia(attachmentId: AttachmentId) { - disposables += Single - .fromCallable { - AppDependencies - .jobManager - .startChain(AttachmentUploadJob(attachmentId)) - .then(CopyAttachmentToArchiveJob(attachmentId)) - .enqueueAndBlockUntilCompletion(15.seconds.inWholeMilliseconds) - } - .subscribeOn(Schedulers.io()) - .observeOn(Schedulers.single()) - .doOnSubscribe { _mediaState.set { update(inProgress = inProgressMediaIds + attachmentId) } } - .doOnTerminate { _mediaState.set { update(inProgress = inProgressMediaIds - attachmentId) } } - .subscribeBy { - if (it.isPresent && it.get().isComplete) { - loadMedia() - } else { - _mediaState.set { copy(error = MediaStateError(errorText = "Reupload slow or failed, try again")) } - } - } - } - - fun deleteArchivedMedia(attachmentIds: Set) { - deleteArchivedMedia(mediaState.value.attachments.filter { attachmentIds.contains(it.dbAttachment.attachmentId) }) - } - - fun deleteArchivedMedia(attachment: BackupAttachment) { - deleteArchivedMedia(listOf(attachment)) - } - - private fun deleteArchivedMedia(attachments: List) { - val ids = attachments.map { it.dbAttachment.attachmentId }.toSet() - disposables += Single.fromCallable { BackupRepository.deleteArchivedMedia(attachments.map { it.dbAttachment }) } - .subscribeOn(Schedulers.io()) - .observeOn(Schedulers.single()) - .doOnSubscribe { _mediaState.set { update(inProgress = inProgressMediaIds + ids) } } - .doOnTerminate { _mediaState.set { update(inProgress = inProgressMediaIds - ids) } } - .subscribeBy { - when (it) { - is NetworkResult.Success -> loadMedia() - else -> _mediaState.set { copy(error = MediaStateError(errorText = "$it")) } - } - } - } - - fun deleteAllArchivedMedia() { - disposables += Single - .fromCallable { BackupRepository.debugDeleteAllArchivedMedia() } - .subscribeOn(Schedulers.io()) - .observeOn(Schedulers.single()) - .subscribeBy { result -> - when (result) { - is NetworkResult.Success -> loadMedia() - else -> _mediaState.set { copy(error = MediaStateError(errorText = "$result")) } - } - } - } - - fun restoreArchivedMedia(attachment: BackupAttachment, asThumbnail: Boolean) { - disposables += Completable - .fromCallable { - val recipientId = SignalStore.releaseChannel.releaseChannelRecipientId!! - val threadId = SignalDatabase.threads.getOrCreateThreadIdFor(Recipient.resolved(recipientId)) - - val message = IncomingMessage( - type = MessageType.NORMAL, - from = recipientId, - sentTimeMillis = System.currentTimeMillis(), - serverTimeMillis = System.currentTimeMillis(), - receivedTimeMillis = System.currentTimeMillis(), - body = "Restored from Archive!?", - serverGuid = UUID.randomUUID().toString() - ) - - val insertMessage = SignalDatabase.messages.insertMessageInbox(message, threadId).get() - - SignalDatabase.attachments.debugCopyAttachmentForArchiveRestore( - mmsId = insertMessage.messageId, - attachment = attachment.dbAttachment, - forThumbnail = asThumbnail - ) - - val archivedAttachment = SignalDatabase.attachments.getAttachmentsForMessage(insertMessage.messageId).first() - - if (asThumbnail) { - AppDependencies.jobManager.add( - RestoreAttachmentThumbnailJob( - messageId = insertMessage.messageId, - attachmentId = archivedAttachment.attachmentId, - highPriority = false - ) - ) - } else { - AppDependencies.jobManager.add( - RestoreAttachmentJob.forInitialRestore( - messageId = insertMessage.messageId, - attachmentId = archivedAttachment.attachmentId - ) - ) - } - } - .subscribeOn(Schedulers.io()) - .observeOn(Schedulers.single()) - .subscribeBy( - onError = { - _mediaState.set { copy(error = MediaStateError(errorText = "$it")) } - } - ) - } - suspend fun deleteRemoteBackupData(): Boolean = withContext(Dispatchers.IO) { when (val result = BackupRepository.debugDeleteAllArchivedMedia()) { is NetworkResult.Success -> Log.i(TAG, "Remote data deleted")