mirror of
https://github.com/signalapp/Signal-Android.git
synced 2026-02-26 04:33:36 +00:00
Remove Media tab from backup playground.
This commit is contained in:
@@ -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<Unit> {
|
||||
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<Unit> {
|
||||
return initBackupAndFetchAuth()
|
||||
.then { credential ->
|
||||
@@ -1208,74 +1191,6 @@ object BackupRepository {
|
||||
.also { Log.i(TAG, "archiveMediaResult: $it") }
|
||||
}
|
||||
|
||||
fun copyAttachmentToArchive(databaseAttachments: List<DatabaseAttachment>): NetworkResult<BatchArchiveMediaResult> {
|
||||
return initBackupAndFetchAuth()
|
||||
.then { credential ->
|
||||
val requests = mutableListOf<ArchiveMediaRequest>()
|
||||
val mediaIdToAttachmentId = mutableMapOf<String, AttachmentId>()
|
||||
val attachmentIdToMediaName = mutableMapOf<AttachmentId, String>()
|
||||
|
||||
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<DatabaseAttachment>): NetworkResult<Unit> {
|
||||
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<ArchivedMediaObject>): NetworkResult<Unit> {
|
||||
val mediaToDelete = mediaObjects
|
||||
.map {
|
||||
|
||||
@@ -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<AttachmentId>) -> Unit,
|
||||
batchDeleteBackupAttachmentMedia: (Set<AttachmentId>) -> 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<AttachmentId> = emptySet(),
|
||||
val expandedOption: AttachmentId? = null
|
||||
)
|
||||
|
||||
@SignalPreview
|
||||
@Composable
|
||||
fun PreviewScreen() {
|
||||
|
||||
@@ -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<ScreenState> = _state
|
||||
|
||||
private val _mediaState: MutableState<MediaState> = mutableStateOf(MediaState())
|
||||
val mediaState: State<MediaState> = _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<AttachmentId>) {
|
||||
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<AttachmentId>) {
|
||||
deleteArchivedMedia(mediaState.value.attachments.filter { attachmentIds.contains(it.dbAttachment.attachmentId) })
|
||||
}
|
||||
|
||||
fun deleteArchivedMedia(attachment: BackupAttachment) {
|
||||
deleteArchivedMedia(listOf(attachment))
|
||||
}
|
||||
|
||||
private fun deleteArchivedMedia(attachments: List<BackupAttachment>) {
|
||||
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")
|
||||
|
||||
Reference in New Issue
Block a user