From 342bd2068a1df84b4ccae281fcd54abc1612594f Mon Sep 17 00:00:00 2001 From: Torsten Grote Date: Tue, 20 Apr 2021 11:29:16 -0300 Subject: [PATCH] Clear existing storage snapshots from storage medium because that scenario isn't supported at the moment --- .../java/com/stevesoltys/seedvault/App.kt | 2 +- .../ui/storage/BackupStorageViewModel.kt | 5 +++ .../ui/storage/StorageCheckFragment.kt | 4 ++- .../ui/storage/StorageRootsFragment.kt | 3 ++ .../seedvault/ui/storage/StorageViewModel.kt | 2 ++ .../res/layout/fragment_storage_check.xml | 17 ++++++++-- .../main/res/layout/fragment_storage_root.xml | 10 +++--- app/src/main/res/values/strings.xml | 2 ++ storage/README.md | 5 +++ .../storagebackuptester/MainViewModel.kt | 7 ++++- .../backup/storage/api/StorageBackup.kt | 31 ++++++++++++++++--- .../calyxos/backup/storage/db/FilesCache.kt | 3 ++ .../storage/plugin/SnapshotRetriever.kt | 7 ++++- 13 files changed, 82 insertions(+), 16 deletions(-) diff --git a/app/src/main/java/com/stevesoltys/seedvault/App.kt b/app/src/main/java/com/stevesoltys/seedvault/App.kt index b2d3e0e5..6aaeeb26 100644 --- a/app/src/main/java/com/stevesoltys/seedvault/App.kt +++ b/app/src/main/java/com/stevesoltys/seedvault/App.kt @@ -47,7 +47,7 @@ open class App : Application() { viewModel { SettingsViewModel(this@App, get(), get(), get(), get(), get(), get()) } viewModel { RecoveryCodeViewModel(this@App, get(), get(), get()) } - viewModel { BackupStorageViewModel(this@App, get(), get(), get()) } + viewModel { BackupStorageViewModel(this@App, get(), get(), get(), get()) } viewModel { RestoreStorageViewModel(this@App, get(), get()) } viewModel { RestoreViewModel(this@App, get(), get(), get(), get(), get(), get()) } viewModel { FileSelectionViewModel(this@App, get()) } diff --git a/app/src/main/java/com/stevesoltys/seedvault/ui/storage/BackupStorageViewModel.kt b/app/src/main/java/com/stevesoltys/seedvault/ui/storage/BackupStorageViewModel.kt index 617ab24a..aa8a0bb6 100644 --- a/app/src/main/java/com/stevesoltys/seedvault/ui/storage/BackupStorageViewModel.kt +++ b/app/src/main/java/com/stevesoltys/seedvault/ui/storage/BackupStorageViewModel.kt @@ -16,6 +16,7 @@ import com.stevesoltys.seedvault.transport.backup.BackupCoordinator import com.stevesoltys.seedvault.transport.requestBackup import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.launch +import org.calyxos.backup.storage.api.StorageBackup import java.io.IOException private val TAG = BackupStorageViewModel::class.java.simpleName @@ -24,6 +25,7 @@ internal class BackupStorageViewModel( private val app: Application, private val backupManager: IBackupManager, private val backupCoordinator: BackupCoordinator, + private val storageBackup: StorageBackup, settingsManager: SettingsManager ) : StorageViewModel(app, settingsManager) { @@ -32,6 +34,9 @@ internal class BackupStorageViewModel( override fun onLocationSet(uri: Uri) { val isUsb = saveStorage(uri) viewModelScope.launch(Dispatchers.IO) { + // remove old storage snapshots and clear cache + storageBackup.deleteAllSnapshots() + storageBackup.clearCache() try { // will also generate a new backup token for the new restore set backupCoordinator.startNewRestoreSet() diff --git a/app/src/main/java/com/stevesoltys/seedvault/ui/storage/StorageCheckFragment.kt b/app/src/main/java/com/stevesoltys/seedvault/ui/storage/StorageCheckFragment.kt index 3c8c60d1..566d03fd 100644 --- a/app/src/main/java/com/stevesoltys/seedvault/ui/storage/StorageCheckFragment.kt +++ b/app/src/main/java/com/stevesoltys/seedvault/ui/storage/StorageCheckFragment.kt @@ -3,6 +3,7 @@ package com.stevesoltys.seedvault.ui.storage import android.os.Bundle import android.view.LayoutInflater import android.view.View +import android.view.View.GONE import android.view.View.INVISIBLE import android.view.View.VISIBLE import android.view.ViewGroup @@ -37,7 +38,7 @@ class StorageCheckFragment : Fragment() { inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle? - ): View? { + ): View { val v: View = inflater.inflate(R.layout.fragment_storage_check, container, false) titleView = v.findViewById(R.id.titleView) @@ -55,6 +56,7 @@ class StorageCheckFragment : Fragment() { val errorMsg = requireArguments().getString(ERROR_MSG) if (errorMsg != null) { + view.findViewById(R.id.patienceView).visibility = GONE progressBar.visibility = INVISIBLE errorView.text = errorMsg errorView.visibility = VISIBLE diff --git a/app/src/main/java/com/stevesoltys/seedvault/ui/storage/StorageRootsFragment.kt b/app/src/main/java/com/stevesoltys/seedvault/ui/storage/StorageRootsFragment.kt index ae872ad1..b50b24de 100644 --- a/app/src/main/java/com/stevesoltys/seedvault/ui/storage/StorageRootsFragment.kt +++ b/app/src/main/java/com/stevesoltys/seedvault/ui/storage/StorageRootsFragment.kt @@ -83,6 +83,9 @@ internal class StorageRootsFragment : Fragment(), StorageRootClickedListener { backView.setOnClickListener { requireActivity().finishAfterTransition() } } else { warningIcon.visibility = VISIBLE + if (viewModel.hasStorageSet) { + warningText.setText(R.string.storage_fragment_warning_delete) + } warningText.visibility = VISIBLE divider.visibility = VISIBLE } diff --git a/app/src/main/java/com/stevesoltys/seedvault/ui/storage/StorageViewModel.kt b/app/src/main/java/com/stevesoltys/seedvault/ui/storage/StorageViewModel.kt index 0f12b0f5..f04eaa8d 100644 --- a/app/src/main/java/com/stevesoltys/seedvault/ui/storage/StorageViewModel.kt +++ b/app/src/main/java/com/stevesoltys/seedvault/ui/storage/StorageViewModel.kt @@ -41,6 +41,8 @@ internal abstract class StorageViewModel( private var storageRoot: StorageRoot? = null internal var isSetupWizard: Boolean = false + internal val hasStorageSet: Boolean + get() = settingsManager.getStorage() != null abstract val isRestoreOperation: Boolean companion object { diff --git a/app/src/main/res/layout/fragment_storage_check.xml b/app/src/main/res/layout/fragment_storage_check.xml index 4e772795..8965997c 100644 --- a/app/src/main/res/layout/fragment_storage_check.xml +++ b/app/src/main/res/layout/fragment_storage_check.xml @@ -29,6 +29,17 @@ app:layout_constraintTop_toBottomOf="@+id/imageView" tools:text="@string/storage_check_fragment_backup_title" /> + + diff --git a/app/src/main/res/layout/fragment_storage_root.xml b/app/src/main/res/layout/fragment_storage_root.xml index a6267bb1..78d4acb2 100644 --- a/app/src/main/res/layout/fragment_storage_root.xml +++ b/app/src/main/res/layout/fragment_storage_root.xml @@ -22,24 +22,24 @@ android:layout_width="0dp" android:layout_height="wrap_content" android:layout_margin="16dp" + android:gravity="center" android:text="@string/storage_fragment_backup_title" android:textSize="24sp" - android:gravity="center" - tools:text="Choose where to store backup (is a short title, but it can be longer)" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toStartOf="parent" - app:layout_constraintTop_toBottomOf="@+id/imageView" /> + app:layout_constraintTop_toBottomOf="@+id/imageView" + tools:text="Choose where to store backup (is a short title, but it can be longer)" /> diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 33242e8b..d7eaa927 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -46,6 +46,7 @@ Choose where to store backups Where to find your backups? People with access to your storage location can learn which apps you use, but do not get access to the apps\' data. + Existing backups in this location will be deleted. USB flash drive Needs to be plugged in %1$s free @@ -54,6 +55,7 @@ Tap to set up account Account not available. Set one up (or disable passcode). Initializing backup location… + This may take some time… Looking for backups… An error occurred while accessing the backup location. Unable to get the permission to write to the backup location. diff --git a/storage/README.md b/storage/README.md index 56f7260c..94de78e1 100644 --- a/storage/README.md +++ b/storage/README.md @@ -8,3 +8,8 @@ Please see the [design document](doc/design.md) for more information. There is also a [demo app](demo) that illustrates the working of the library and does not need to be a system app with elevated permissions. It can be built and installed as a regular app requesting permissions at runtime. + +## Limitations + +The design document mentions several limitations of this initial implementation. +One of them is that you cannot backup more than one device to the same storage location. diff --git a/storage/demo/src/main/java/de/grobox/storagebackuptester/MainViewModel.kt b/storage/demo/src/main/java/de/grobox/storagebackuptester/MainViewModel.kt index f0c23945..bd55b1e4 100644 --- a/storage/demo/src/main/java/de/grobox/storagebackuptester/MainViewModel.kt +++ b/storage/demo/src/main/java/de/grobox/storagebackuptester/MainViewModel.kt @@ -89,7 +89,12 @@ class MainViewModel(application: Application) : BackupContentViewModel(applicati } fun setBackupLocation(uri: Uri?) { - if (uri != null) clearDb() + if (uri != null) { + viewModelScope.launch(Dispatchers.IO) { + storageBackup.deleteAllSnapshots() + storageBackup.clearCache() + } + } settingsManager.setBackupLocation(uri) } diff --git a/storage/lib/src/main/java/org/calyxos/backup/storage/api/StorageBackup.kt b/storage/lib/src/main/java/org/calyxos/backup/storage/api/StorageBackup.kt index 6664a650..969c9851 100644 --- a/storage/lib/src/main/java/org/calyxos/backup/storage/api/StorageBackup.kt +++ b/storage/lib/src/main/java/org/calyxos/backup/storage/api/StorageBackup.kt @@ -26,6 +26,7 @@ import org.calyxos.backup.storage.scanner.DocumentScanner import org.calyxos.backup.storage.scanner.FileScanner import org.calyxos.backup.storage.scanner.MediaScanner import org.calyxos.backup.storage.toStoredUri +import java.io.IOException import java.util.concurrent.atomic.AtomicBoolean private const val TAG = "StorageBackup" @@ -33,7 +34,7 @@ private const val TAG = "StorageBackup" @Suppress("BlockingMethodInNonBlockingContext") public class StorageBackup( private val context: Context, - plugin: StoragePlugin, + private val plugin: StoragePlugin, private val dispatcher: CoroutineDispatcher = Dispatchers.IO, ) { @@ -98,9 +99,31 @@ public class StorageBackup( list.joinToString(", ", limit = 5) } - @Deprecated("TODO remove for release") - public fun clearCache() { - db.clearAllTables() + /** + * Run this on a new storage location to ensure that there are no old snapshots + * (potentially encrypted with an old key) laying around. + * Using a storage location with existing data is not supported. + */ + public suspend fun deleteAllSnapshots(): Unit = withContext(dispatcher) { + try { + plugin.getAvailableBackupSnapshots().forEach { + try { + plugin.deleteBackupSnapshot(it) + } catch (e: IOException) { + Log.e(TAG, "Error deleting snapshot $it", e) + } + } + } catch (e: IOException) { + Log.e(TAG, "Error deleting all snapshots", e) + } + } + + /** + * It is advised to clear existing cache when selecting a new storage location. + */ + public suspend fun clearCache(): Unit = withContext(dispatcher) { + db.getChunksCache().clear() + db.getFilesCache().clear() } public suspend fun runBackup(backupObserver: BackupObserver?): Boolean = diff --git a/storage/lib/src/main/java/org/calyxos/backup/storage/db/FilesCache.kt b/storage/lib/src/main/java/org/calyxos/backup/storage/db/FilesCache.kt index ae4f72e6..99d8eba3 100644 --- a/storage/lib/src/main/java/org/calyxos/backup/storage/db/FilesCache.kt +++ b/storage/lib/src/main/java/org/calyxos/backup/storage/db/FilesCache.kt @@ -37,6 +37,9 @@ internal interface FilesCache { @Update fun update(file: CachedFile) + @Query("DELETE FROM CachedFile") + fun clear() + @Query("UPDATE CachedFile SET last_seen = :now WHERE uri IN (:uris)") fun updateLastSeen(uris: Collection, now: Long = System.currentTimeMillis()) diff --git a/storage/lib/src/main/java/org/calyxos/backup/storage/plugin/SnapshotRetriever.kt b/storage/lib/src/main/java/org/calyxos/backup/storage/plugin/SnapshotRetriever.kt index 943c5358..050b3be9 100644 --- a/storage/lib/src/main/java/org/calyxos/backup/storage/plugin/SnapshotRetriever.kt +++ b/storage/lib/src/main/java/org/calyxos/backup/storage/plugin/SnapshotRetriever.kt @@ -1,5 +1,6 @@ package org.calyxos.backup.storage.plugin +import com.google.protobuf.InvalidProtocolBufferException import org.calyxos.backup.storage.api.StoragePlugin import org.calyxos.backup.storage.backup.BackupSnapshot import org.calyxos.backup.storage.crypto.StreamCrypto @@ -13,7 +14,11 @@ internal class SnapshotRetriever( private val streamCrypto: StreamCrypto = StreamCrypto, ) { - @Throws(IOException::class, GeneralSecurityException::class) + @Throws( + IOException::class, + GeneralSecurityException::class, + InvalidProtocolBufferException::class, + ) suspend fun getSnapshot(streamKey: ByteArray, timestamp: Long): BackupSnapshot { return storagePlugin.getBackupSnapshotInputStream(timestamp).use { inputStream -> val version = inputStream.readVersion()