Make demo restore with file selection functional

This injects FileSelectionManager as a singleton, so we can use its selection to recreate a snapshot, even in a service.

Also includes some UI improvements.
This commit is contained in:
Torsten Grote 2024-06-21 10:27:31 -03:00
parent 5012099419
commit 7d6ab6f8e0
No known key found for this signature in database
GPG key ID: 3E5F77D92CF891FF
17 changed files with 139 additions and 54 deletions

View file

@ -57,6 +57,7 @@ class KoinInstrumentationTestApp : App() {
iconManager = get(), iconManager = get(),
storageBackup = get(), storageBackup = get(),
pluginManager = get(), pluginManager = get(),
fileSelectionManager = get(),
) )
) )
currentRestoreViewModel!! currentRestoreViewModel!!

View file

@ -28,8 +28,8 @@ import com.stevesoltys.seedvault.metadata.metadataModule
import com.stevesoltys.seedvault.plugins.StoragePluginManager import com.stevesoltys.seedvault.plugins.StoragePluginManager
import com.stevesoltys.seedvault.plugins.saf.storagePluginModuleSaf import com.stevesoltys.seedvault.plugins.saf.storagePluginModuleSaf
import com.stevesoltys.seedvault.plugins.webdav.storagePluginModuleWebDav import com.stevesoltys.seedvault.plugins.webdav.storagePluginModuleWebDav
import com.stevesoltys.seedvault.restore.RestoreViewModel
import com.stevesoltys.seedvault.restore.install.installModule import com.stevesoltys.seedvault.restore.install.installModule
import com.stevesoltys.seedvault.restore.restoreUiModule
import com.stevesoltys.seedvault.settings.AppListRetriever import com.stevesoltys.seedvault.settings.AppListRetriever
import com.stevesoltys.seedvault.settings.SettingsManager import com.stevesoltys.seedvault.settings.SettingsManager
import com.stevesoltys.seedvault.settings.SettingsViewModel import com.stevesoltys.seedvault.settings.SettingsViewModel
@ -37,7 +37,6 @@ import com.stevesoltys.seedvault.storage.storageModule
import com.stevesoltys.seedvault.transport.TRANSPORT_ID import com.stevesoltys.seedvault.transport.TRANSPORT_ID
import com.stevesoltys.seedvault.transport.backup.backupModule import com.stevesoltys.seedvault.transport.backup.backupModule
import com.stevesoltys.seedvault.transport.restore.restoreModule import com.stevesoltys.seedvault.transport.restore.restoreModule
import com.stevesoltys.seedvault.ui.files.FileSelectionViewModel
import com.stevesoltys.seedvault.ui.notification.BackupNotificationManager import com.stevesoltys.seedvault.ui.notification.BackupNotificationManager
import com.stevesoltys.seedvault.ui.recoverycode.RecoveryCodeViewModel import com.stevesoltys.seedvault.ui.recoverycode.RecoveryCodeViewModel
import com.stevesoltys.seedvault.ui.storage.BackupStorageViewModel import com.stevesoltys.seedvault.ui.storage.BackupStorageViewModel
@ -97,20 +96,6 @@ open class App : Application() {
) )
} }
viewModel { RestoreStorageViewModel(this@App, get(), get(), get(), get()) } viewModel { RestoreStorageViewModel(this@App, get(), get(), get(), get()) }
viewModel {
RestoreViewModel(
app = this@App,
settingsManager = get(),
keyManager = get(),
backupManager = get(),
restoreCoordinator = get(),
apkRestore = get(),
iconManager = get(),
storageBackup = get(),
pluginManager = get(),
)
}
viewModel { FileSelectionViewModel(this@App, get()) }
} }
override fun onCreate() { override fun onCreate() {
@ -155,6 +140,7 @@ open class App : Application() {
installModule, installModule,
storageModule, storageModule,
workerModule, workerModule,
restoreUiModule,
appModule appModule
) )

View file

@ -0,0 +1,31 @@
/*
* SPDX-FileCopyrightText: 2020 The Calyx Institute
* SPDX-License-Identifier: Apache-2.0
*/
package com.stevesoltys.seedvault.restore
import com.stevesoltys.seedvault.ui.files.FileSelectionViewModel
import org.calyxos.backup.storage.ui.restore.FileSelectionManager
import org.koin.android.ext.koin.androidApplication
import org.koin.androidx.viewmodel.dsl.viewModel
import org.koin.dsl.module
val restoreUiModule = module {
single { FileSelectionManager() }
viewModel {
RestoreViewModel(
app = androidApplication(),
settingsManager = get(),
keyManager = get(),
backupManager = get(),
restoreCoordinator = get(),
apkRestore = get(),
iconManager = get(),
storageBackup = get(),
pluginManager = get(),
fileSelectionManager = get(),
)
}
viewModel { FileSelectionViewModel(androidApplication(), get()) }
}

View file

@ -64,6 +64,7 @@ internal class RestoreViewModel(
private val iconManager: IconManager, private val iconManager: IconManager,
storageBackup: StorageBackup, storageBackup: StorageBackup,
pluginManager: StoragePluginManager, pluginManager: StoragePluginManager,
override val fileSelectionManager: FileSelectionManager,
private val ioDispatcher: CoroutineDispatcher = Dispatchers.IO, private val ioDispatcher: CoroutineDispatcher = Dispatchers.IO,
) : RequireProvisioningViewModel(app, settingsManager, keyManager, pluginManager), ) : RequireProvisioningViewModel(app, settingsManager, keyManager, pluginManager),
RestorableBackupClickListener, SnapshotViewModel { RestorableBackupClickListener, SnapshotViewModel {
@ -99,8 +100,6 @@ internal class RestoreViewModel(
get() = appDataRestoreManager.restoreBackupResult get() = appDataRestoreManager.restoreBackupResult
override val snapshots = storageBackup.getBackupSnapshots().asLiveData(ioDispatcher) override val snapshots = storageBackup.getBackupSnapshots().asLiveData(ioDispatcher)
override val fileSelectionManager: FileSelectionManager
get() = TODO("Not yet implemented")
internal fun loadRestoreSets() = viewModelScope.launch(ioDispatcher) { internal fun loadRestoreSets() = viewModelScope.launch(ioDispatcher) {
val backups = restoreCoordinator.getAvailableMetadata()?.mapNotNull { (token, metadata) -> val backups = restoreCoordinator.getAvailableMetadata()?.mapNotNull { (token, metadata) ->

View file

@ -18,6 +18,7 @@ import org.calyxos.backup.storage.backup.BackupService
import org.calyxos.backup.storage.backup.NotificationBackupObserver import org.calyxos.backup.storage.backup.NotificationBackupObserver
import org.calyxos.backup.storage.restore.NotificationRestoreObserver import org.calyxos.backup.storage.restore.NotificationRestoreObserver
import org.calyxos.backup.storage.restore.RestoreService import org.calyxos.backup.storage.restore.RestoreService
import org.calyxos.backup.storage.ui.restore.FileSelectionManager
import org.koin.android.ext.android.inject import org.koin.android.ext.android.inject
/* /*
@ -70,6 +71,7 @@ internal class StorageBackupService : BackupService() {
internal class StorageRestoreService : RestoreService() { internal class StorageRestoreService : RestoreService() {
override val storageBackup: StorageBackup by inject() override val storageBackup: StorageBackup by inject()
override val fileSelectionManager: FileSelectionManager by inject()
// use lazy delegate because context isn't available during construction time // use lazy delegate because context isn't available during construction time
override val restoreObserver: RestoreObserver by lazy { override val restoreObserver: RestoreObserver by lazy {

View file

@ -108,10 +108,6 @@
android:layout_marginTop="0dp" android:layout_marginTop="0dp"
android:layout_marginEnd="0dp" android:layout_marginEnd="0dp"
app:layout_behavior="@string/appbar_scrolling_view_behavior" app:layout_behavior="@string/appbar_scrolling_view_behavior"
app:layout_constraintBottom_toTopOf="@+id/button"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/backupNameView"
tools:listitem="@layout/list_item_app_status" /> tools:listitem="@layout/list_item_app_status" />
<Button <Button
@ -122,9 +118,6 @@
android:layout_gravity="bottom|end" android:layout_gravity="bottom|end"
android:layout_marginEnd="16dp" android:layout_marginEnd="16dp"
android:text="@string/restore_backup_button" android:text="@string/restore_backup_button"
app:layout_behavior="com.google.android.material.behavior.HideBottomViewOnScrollBehavior" app:layout_behavior="com.google.android.material.behavior.HideBottomViewOnScrollBehavior" />
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toBottomOf="@+id/appList" />
</androidx.coordinatorlayout.widget.CoordinatorLayout> </androidx.coordinatorlayout.widget.CoordinatorLayout>

View file

@ -12,6 +12,7 @@ import android.util.Log
import de.grobox.storagebackuptester.plugin.TestSafStoragePlugin import de.grobox.storagebackuptester.plugin.TestSafStoragePlugin
import de.grobox.storagebackuptester.settings.SettingsManager import de.grobox.storagebackuptester.settings.SettingsManager
import org.calyxos.backup.storage.api.StorageBackup import org.calyxos.backup.storage.api.StorageBackup
import org.calyxos.backup.storage.ui.restore.FileSelectionManager
class App : Application() { class App : Application() {
@ -20,6 +21,7 @@ class App : Application() {
val plugin = TestSafStoragePlugin(this) { settingsManager.getBackupLocation() } val plugin = TestSafStoragePlugin(this) { settingsManager.getBackupLocation() }
StorageBackup(this, { plugin }) StorageBackup(this, { plugin })
} }
val fileSelectionManager: FileSelectionManager get() = FileSelectionManager()
override fun onCreate() { override fun onCreate() {
super.onCreate() super.onCreate()

View file

@ -14,6 +14,7 @@ import org.calyxos.backup.storage.backup.BackupService
import org.calyxos.backup.storage.backup.NotificationBackupObserver import org.calyxos.backup.storage.backup.NotificationBackupObserver
import org.calyxos.backup.storage.restore.NotificationRestoreObserver import org.calyxos.backup.storage.restore.NotificationRestoreObserver
import org.calyxos.backup.storage.restore.RestoreService import org.calyxos.backup.storage.restore.RestoreService
import org.calyxos.backup.storage.ui.restore.FileSelectionManager
import java.util.concurrent.TimeUnit.HOURS import java.util.concurrent.TimeUnit.HOURS
// debug with: // debug with:
@ -45,6 +46,8 @@ class DemoBackupService : BackupService() {
class DemoRestoreService : RestoreService() { class DemoRestoreService : RestoreService() {
// use lazy delegate because context isn't available during construction time // use lazy delegate because context isn't available during construction time
override val storageBackup: StorageBackup by lazy { (application as App).storageBackup } override val storageBackup: StorageBackup by lazy { (application as App).storageBackup }
override val fileSelectionManager: FileSelectionManager
get() = (application as App).fileSelectionManager
override val restoreObserver: RestoreObserver by lazy { override val restoreObserver: RestoreObserver by lazy {
NotificationRestoreObserver(applicationContext) NotificationRestoreObserver(applicationContext)
} }

View file

@ -24,11 +24,11 @@ import kotlinx.coroutines.withContext
import org.calyxos.backup.storage.api.SnapshotItem import org.calyxos.backup.storage.api.SnapshotItem
import org.calyxos.backup.storage.api.SnapshotResult import org.calyxos.backup.storage.api.SnapshotResult
import org.calyxos.backup.storage.api.StorageBackup import org.calyxos.backup.storage.api.StorageBackup
import org.calyxos.backup.storage.api.StoredSnapshot
import org.calyxos.backup.storage.backup.BackupJobService import org.calyxos.backup.storage.backup.BackupJobService
import org.calyxos.backup.storage.scanner.DocumentScanner import org.calyxos.backup.storage.scanner.DocumentScanner
import org.calyxos.backup.storage.scanner.MediaScanner import org.calyxos.backup.storage.scanner.MediaScanner
import org.calyxos.backup.storage.ui.backup.BackupContentViewModel import org.calyxos.backup.storage.ui.backup.BackupContentViewModel
import org.calyxos.backup.storage.ui.restore.FileSelectionManager
import org.calyxos.backup.storage.ui.restore.SnapshotViewModel import org.calyxos.backup.storage.ui.restore.SnapshotViewModel
private val logEmptyState = """ private val logEmptyState = """
@ -48,7 +48,7 @@ class MainViewModel(application: Application) : BackupContentViewModel(applicati
private val app: App = application as App private val app: App = application as App
private val settingsManager = app.settingsManager private val settingsManager = app.settingsManager
override val storageBackup: StorageBackup = app.storageBackup override val storageBackup: StorageBackup = app.storageBackup
override val fileSelectionManager = FileSelectionManager() override val fileSelectionManager = app.fileSelectionManager
private val _backupLog = MutableLiveData(BackupProgress(0, 0, logEmptyState)) private val _backupLog = MutableLiveData(BackupProgress(0, 0, logEmptyState))
val backupLog: LiveData<BackupProgress> = _backupLog val backupLog: LiveData<BackupProgress> = _backupLog
@ -64,6 +64,7 @@ class MainViewModel(application: Application) : BackupContentViewModel(applicati
override val snapshots: LiveData<SnapshotResult> override val snapshots: LiveData<SnapshotResult>
get() = storageBackup.getBackupSnapshots().asLiveData(Dispatchers.IO) get() = storageBackup.getBackupSnapshots().asLiveData(Dispatchers.IO)
private var storedSnapshot: StoredSnapshot? = null
init { init {
viewModelScope.launch { loadContent() } viewModelScope.launch { loadContent() }
@ -128,11 +129,12 @@ class MainViewModel(application: Application) : BackupContentViewModel(applicati
fun onSnapshotClicked(item: SnapshotItem) { fun onSnapshotClicked(item: SnapshotItem) {
val snapshot = item.snapshot ?: error("${item.storedSnapshot} had null snapshot") val snapshot = item.snapshot ?: error("${item.storedSnapshot} had null snapshot")
fileSelectionManager.onSnapshotChosen(snapshot) fileSelectionManager.onSnapshotChosen(snapshot)
storedSnapshot = item.storedSnapshot
} }
fun onFilesSelected(item: SnapshotItem) { fun onFilesSelected() {
val snapshot = item.snapshot val storedSnapshot = this.storedSnapshot ?: error("No snapshot stored")
check(snapshot != null) val snapshot = fileSelectionManager.getBackupSnapshotAndReset()
// example for how to do restore via foreground service // example for how to do restore via foreground service
// app.startForegroundService(Intent(app, DemoRestoreService::class.java).apply { // app.startForegroundService(Intent(app, DemoRestoreService::class.java).apply {
@ -144,8 +146,9 @@ class MainViewModel(application: Application) : BackupContentViewModel(applicati
_restoreProgressVisible.value = true _restoreProgressVisible.value = true
val restoreObserver = RestoreStats(app, _restoreLog) val restoreObserver = RestoreStats(app, _restoreLog)
viewModelScope.launch { viewModelScope.launch {
storageBackup.restoreBackupSnapshot(item.storedSnapshot, snapshot, restoreObserver) storageBackup.restoreBackupSnapshot(storedSnapshot, snapshot, restoreObserver)
_restoreProgressVisible.value = false _restoreProgressVisible.value = false
this@MainViewModel.storedSnapshot = null
} }
} }

View file

@ -9,8 +9,10 @@ import android.os.Bundle
import android.view.LayoutInflater import android.view.LayoutInflater
import android.view.View import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
import android.view.ViewStub
import androidx.fragment.app.activityViewModels import androidx.fragment.app.activityViewModels
import de.grobox.storagebackuptester.MainViewModel import de.grobox.storagebackuptester.MainViewModel
import de.grobox.storagebackuptester.R
import org.calyxos.backup.storage.ui.restore.FileSelectionFragment import org.calyxos.backup.storage.ui.restore.FileSelectionFragment
class DemoFileSelectionFragment : FileSelectionFragment() { class DemoFileSelectionFragment : FileSelectionFragment() {
@ -23,14 +25,17 @@ class DemoFileSelectionFragment : FileSelectionFragment() {
savedInstanceState: Bundle?, savedInstanceState: Bundle?,
): View { ): View {
val v = super.onCreateView(inflater, container, savedInstanceState) val v = super.onCreateView(inflater, container, savedInstanceState)
// val topStub: ViewStub = v.findViewById(R.id.topStub) val topStub: ViewStub = v.findViewById(R.id.topStub)
// topStub.layoutResource = R.layout.footer_snapshot topStub.layoutResource = R.layout.header_file_select
// val header = topStub.inflate() topStub.inflate()
// header.findViewById<Button>(R.id.button).setOnClickListener {
// requireActivity().onBackPressed()
// }
return v return v
} }
override fun onRestoreButtonClicked() {
viewModel.onFilesSelected()
parentFragmentManager.beginTransaction()
.replace(R.id.container, RestoreFragment.newInstance())
.commit()
}
} }

View file

@ -0,0 +1,20 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
SPDX-FileCopyrightText: 2021 The Calyx Institute
SPDX-License-Identifier: Apache-2.0
-->
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:padding="16dp">
<TextView
android:id="@+id/button"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="De-select folders you don't want to restore. Tap folders to see their contents."
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>

View file

@ -200,7 +200,7 @@ public class StorageBackup(
public suspend fun restoreBackupSnapshot( public suspend fun restoreBackupSnapshot(
storedSnapshot: StoredSnapshot, storedSnapshot: StoredSnapshot,
snapshot: BackupSnapshot? = null, snapshot: BackupSnapshot,
restoreObserver: RestoreObserver? = null, restoreObserver: RestoreObserver? = null,
): Boolean = withContext(dispatcher) { ): Boolean = withContext(dispatcher) {
if (restoreRunning.getAndSet(true)) { if (restoreRunning.getAndSet(true)) {

View file

@ -22,7 +22,6 @@ import org.calyxos.backup.storage.plugin.SnapshotRetriever
import java.io.IOException import java.io.IOException
import java.io.InputStream import java.io.InputStream
import java.security.GeneralSecurityException import java.security.GeneralSecurityException
import kotlin.time.ExperimentalTime
private const val TAG = "Restore" private const val TAG = "Restore"
@ -99,15 +98,12 @@ internal class Restore(
Log.e(TAG, "Decrypting and parsing $numSnapshots snapshots took $time") Log.e(TAG, "Decrypting and parsing $numSnapshots snapshots took $time")
} }
@OptIn(ExperimentalTime::class)
@Throws(IOException::class, GeneralSecurityException::class) @Throws(IOException::class, GeneralSecurityException::class)
suspend fun restoreBackupSnapshot( suspend fun restoreBackupSnapshot(
storedSnapshot: StoredSnapshot, storedSnapshot: StoredSnapshot,
optionalSnapshot: BackupSnapshot? = null, snapshot: BackupSnapshot,
observer: RestoreObserver? = null, observer: RestoreObserver? = null,
) { ) {
val snapshot = optionalSnapshot ?: snapshotRetriever.getSnapshot(streamKey, storedSnapshot)
val filesTotal = snapshot.mediaFilesList.size + snapshot.documentFilesList.size val filesTotal = snapshot.mediaFilesList.size + snapshot.documentFilesList.size
val totalSize = val totalSize =
snapshot.mediaFilesList.sumOf { it.size } + snapshot.documentFilesList.sumOf { it.size } snapshot.mediaFilesList.sumOf { it.size } + snapshot.documentFilesList.sumOf { it.size }

View file

@ -9,8 +9,10 @@ import android.app.Service
import android.content.Intent import android.content.Intent
import android.os.IBinder import android.os.IBinder
import android.util.Log import android.util.Log
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.GlobalScope import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import org.calyxos.backup.storage.api.RestoreObserver import org.calyxos.backup.storage.api.RestoreObserver
import org.calyxos.backup.storage.api.StorageBackup import org.calyxos.backup.storage.api.StorageBackup
import org.calyxos.backup.storage.api.StoredSnapshot import org.calyxos.backup.storage.api.StoredSnapshot
@ -18,6 +20,7 @@ import org.calyxos.backup.storage.restore.RestoreService.Companion.EXTRA_TIMESTA
import org.calyxos.backup.storage.restore.RestoreService.Companion.EXTRA_USER_ID import org.calyxos.backup.storage.restore.RestoreService.Companion.EXTRA_USER_ID
import org.calyxos.backup.storage.ui.NOTIFICATION_ID_RESTORE import org.calyxos.backup.storage.ui.NOTIFICATION_ID_RESTORE
import org.calyxos.backup.storage.ui.Notifications import org.calyxos.backup.storage.ui.Notifications
import org.calyxos.backup.storage.ui.restore.FileSelectionManager
/** /**
* Start to trigger restore as a foreground service. Ensure that you provide the snapshot * Start to trigger restore as a foreground service. Ensure that you provide the snapshot
@ -36,6 +39,7 @@ public abstract class RestoreService : Service() {
private val n by lazy { Notifications(applicationContext) } private val n by lazy { Notifications(applicationContext) }
protected abstract val storageBackup: StorageBackup protected abstract val storageBackup: StorageBackup
protected abstract val fileSelectionManager: FileSelectionManager
protected abstract val restoreObserver: RestoreObserver? protected abstract val restoreObserver: RestoreObserver?
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int { override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
@ -47,8 +51,11 @@ public abstract class RestoreService : Service() {
startForeground(NOTIFICATION_ID_RESTORE, n.getRestoreNotification()) startForeground(NOTIFICATION_ID_RESTORE, n.getRestoreNotification())
GlobalScope.launch { GlobalScope.launch {
val snapshot = withContext(Dispatchers.Main) {
fileSelectionManager.getBackupSnapshotAndReset()
}
// TODO offer a way to try again if failed, or do an automatic retry here // TODO offer a way to try again if failed, or do an automatic retry here
storageBackup.restoreBackupSnapshot(storedSnapshot, null, restoreObserver) storageBackup.restoreBackupSnapshot(storedSnapshot, snapshot, restoreObserver)
stopSelf(startId) stopSelf(startId)
} }
return START_STICKY_COMPATIBILITY return START_STICKY_COMPATIBILITY

View file

@ -33,6 +33,9 @@ public abstract class FileSelectionFragment : Fragment() {
val v = inflater.inflate(R.layout.fragment_select_files, container, false) val v = inflater.inflate(R.layout.fragment_select_files, container, false)
list = v.findViewById(R.id.list) list = v.findViewById(R.id.list)
v.findViewById<View>(R.id.fab).setOnClickListener {
onRestoreButtonClicked()
}
return v return v
} }
@ -51,8 +54,10 @@ public abstract class FileSelectionFragment : Fragment() {
} }
} }
protected abstract fun onRestoreButtonClicked()
@CallSuper @CallSuper
public open fun onFileItemsChanged(filesItems: List<FilesItem>) { protected open fun onFileItemsChanged(filesItems: List<FilesItem>) {
adapter.submitList(filesItems) adapter.submitList(filesItems)
} }
} }

View file

@ -54,6 +54,7 @@ public class FileSelectionManager {
private val allFolders = HashMap<String, FolderItem>() private val allFolders = HashMap<String, FolderItem>()
private val allFiles = HashMap<String, MutableList<FileItem>>() private val allFiles = HashMap<String, MutableList<FileItem>>()
private var snapshot: BackupSnapshot? = null
private var expandedFolder: String? = null private var expandedFolder: String? = null
private val mFiles = MutableStateFlow<List<FilesItem>>(emptyList()) private val mFiles = MutableStateFlow<List<FilesItem>>(emptyList())
@ -61,6 +62,12 @@ public class FileSelectionManager {
@UiThread @UiThread
public fun onSnapshotChosen(snapshot: BackupSnapshot) { public fun onSnapshotChosen(snapshot: BackupSnapshot) {
// clear previous state if existing
clearState()
// store snapshot for later
this.snapshot = snapshot
// cache files from snapshot within [RestorableFile] (for easier processing)
snapshot.mediaFilesList.forEach { mediaFile -> snapshot.mediaFilesList.forEach { mediaFile ->
cacheFileItem(RestorableFile(mediaFile)) cacheFileItem(RestorableFile(mediaFile))
} }
@ -126,6 +133,28 @@ public class FileSelectionManager {
mFiles.value = rebuildListFromCache() mFiles.value = rebuildListFromCache()
} }
@UiThread
public fun getBackupSnapshotAndReset(): BackupSnapshot {
val snapshot = this.snapshot ?: error("No snapshot stored")
// clear previous media files from snapshot
val snapshotBuilder = snapshot.toBuilder()
.clearMediaFiles()
.clearDocumentFiles()
// add only selected files back to snapshot
allFiles.values.forEach { fileList ->
fileList.forEach { file ->
if (file.selected && file.file.mediaFile != null) {
snapshotBuilder.addMediaFiles(file.file.mediaFile)
} else if (file.selected && file.file.docFile != null) {
snapshotBuilder.addDocumentFiles(file.file.docFile)
}
}
}
// clear state to free up memory
clearState()
return snapshotBuilder.build()
}
private fun cacheFileItem(restorableFile: RestorableFile) { private fun cacheFileItem(restorableFile: RestorableFile) {
val fileItem = FileItem(restorableFile, 0, true) val fileItem = FileItem(restorableFile, 0, true)
allFiles.getOrPut(restorableFile.dir) { allFiles.getOrPut(restorableFile.dir) {
@ -215,4 +244,12 @@ public class FileSelectionManager {
) )
} }
private fun clearState() {
snapshot = null
expandedFolder = null
allFolders.clear()
allFiles.clear()
mFiles.value = emptyList()
}
} }

View file

@ -16,7 +16,7 @@
<ViewStub <ViewStub
android:id="@+id/topStub" android:id="@+id/topStub"
android:layout_width="0dp" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:inflatedId="@+id/topStub" android:inflatedId="@+id/topStub"
app:layout_constraintEnd_toEndOf="parent" app:layout_constraintEnd_toEndOf="parent"
@ -34,10 +34,7 @@
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent" android:layout_height="match_parent"
app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager" app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager"
app:layout_constraintBottom_toTopOf="@+id/bottomStub" app:layout_behavior="@string/appbar_scrolling_view_behavior"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/topStub"
tools:listitem="@layout/item_file" /> tools:listitem="@layout/item_file" />
<com.google.android.material.floatingactionbutton.ExtendedFloatingActionButton <com.google.android.material.floatingactionbutton.ExtendedFloatingActionButton
@ -51,8 +48,6 @@
app:backgroundTint="?colorAccent" app:backgroundTint="?colorAccent"
app:icon="@drawable/ic_cloud_restore" app:icon="@drawable/ic_cloud_restore"
app:iconTint="#ffffff" app:iconTint="#ffffff"
app:layout_behavior="com.google.android.material.appbar.AppBarLayout$ScrollingViewBehavior" app:layout_behavior="com.google.android.material.behavior.HideBottomViewOnScrollBehavior" />
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent" />
</androidx.coordinatorlayout.widget.CoordinatorLayout> </androidx.coordinatorlayout.widget.CoordinatorLayout>