Rename Storage to SafStorage

in preparation for generalization
This commit is contained in:
Torsten Grote 2024-04-10 17:29:13 -03:00
parent 6788d0d25a
commit 2489190824
No known key found for this signature in database
GPG key ID: 3E5F77D92CF891FF
19 changed files with 117 additions and 68 deletions

View file

@ -49,7 +49,7 @@ class PluginTest : KoinComponent {
@Before @Before
fun setup() = runBlocking { fun setup() = runBlocking {
every { mockedSettingsManager.getStorage() } returns settingsManager.getStorage() every { mockedSettingsManager.getSafStorage() } returns settingsManager.getSafStorage()
storage.rootBackupDir?.deleteContents(context) storage.rootBackupDir?.deleteContents(context)
?: error("Select a storage location in the app first!") ?: error("Select a storage location in the app first!")
} }
@ -76,7 +76,7 @@ class PluginTest : KoinComponent {
fun testInitializationAndRestoreSets() = runBlocking(Dispatchers.IO) { fun testInitializationAndRestoreSets() = runBlocking(Dispatchers.IO) {
// no backups available initially // no backups available initially
assertEquals(0, storagePlugin.getAvailableBackups()?.toList()?.size) assertEquals(0, storagePlugin.getAvailableBackups()?.toList()?.size)
val s = settingsManager.getStorage() ?: error("no storage") val s = settingsManager.getSafStorage() ?: error("no storage")
assertFalse(storagePlugin.hasBackup(s)) assertFalse(storagePlugin.hasBackup(s))
// prepare returned tokens requested when initializing device // prepare returned tokens requested when initializing device

View file

@ -17,7 +17,7 @@ internal class BackupRestoreTest : SeedvaultLargeTest() {
confirmCode() confirmCode()
} }
if (settingsManager.getStorage() == null) { if (settingsManager.getSafStorage() == null) {
chooseStorageLocation() chooseStorageLocation()
} else { } else {
changeBackupLocation() changeBackupLocation()

View file

@ -2,7 +2,7 @@ package com.stevesoltys.seedvault.plugins
import android.app.backup.RestoreSet import android.app.backup.RestoreSet
import androidx.annotation.WorkerThread import androidx.annotation.WorkerThread
import com.stevesoltys.seedvault.settings.Storage import com.stevesoltys.seedvault.plugins.saf.SafStorage
import java.io.IOException import java.io.IOException
import java.io.InputStream import java.io.InputStream
import java.io.OutputStream import java.io.OutputStream
@ -59,7 +59,7 @@ interface StoragePlugin {
*/ */
@WorkerThread @WorkerThread
@Throws(IOException::class) @Throws(IOException::class)
suspend fun hasBackup(storage: Storage): Boolean suspend fun hasBackup(safStorage: SafStorage): Boolean
/** /**
* Get the set of all backups currently available for restore. * Get the set of all backups currently available for restore.

View file

@ -9,7 +9,6 @@ import com.stevesoltys.seedvault.plugins.EncryptedMetadata
import com.stevesoltys.seedvault.plugins.StoragePlugin import com.stevesoltys.seedvault.plugins.StoragePlugin
import com.stevesoltys.seedvault.plugins.chunkFolderRegex import com.stevesoltys.seedvault.plugins.chunkFolderRegex
import com.stevesoltys.seedvault.plugins.tokenRegex import com.stevesoltys.seedvault.plugins.tokenRegex
import com.stevesoltys.seedvault.settings.Storage
import java.io.FileNotFoundException import java.io.FileNotFoundException
import java.io.IOException import java.io.IOException
import java.io.InputStream import java.io.InputStream
@ -28,7 +27,7 @@ internal class DocumentsProviderStoragePlugin(
*/ */
private val context: Context private val context: Context
get() = appContext.getStorageContext { get() = appContext.getStorageContext {
storage.storage?.isUsb == true storage.safStorage?.isUsb == true
} }
private val packageManager: PackageManager = appContext.packageManager private val packageManager: PackageManager = appContext.packageManager
@ -79,10 +78,10 @@ internal class DocumentsProviderStoragePlugin(
} }
@Throws(IOException::class) @Throws(IOException::class)
override suspend fun hasBackup(storage: Storage): Boolean { override suspend fun hasBackup(safStorage: SafStorage): Boolean {
// potentially get system user context if needed here // potentially get system user context if needed here
val c = appContext.getStorageContext { storage.isUsb } val c = appContext.getStorageContext { safStorage.isUsb }
val parent = DocumentFile.fromTreeUri(c, storage.uri) ?: throw AssertionError() val parent = DocumentFile.fromTreeUri(c, safStorage.uri) ?: throw AssertionError()
val rootDir = parent.findFileBlocking(c, DIRECTORY_ROOT) ?: return false val rootDir = parent.findFileBlocking(c, DIRECTORY_ROOT) ?: return false
val backupSets = getBackups(c, rootDir) val backupSets = getBackups(c, rootDir)
return backupSets.isNotEmpty() return backupSets.isNotEmpty()

View file

@ -18,7 +18,6 @@ import androidx.annotation.VisibleForTesting
import androidx.documentfile.provider.DocumentFile import androidx.documentfile.provider.DocumentFile
import com.stevesoltys.seedvault.getStorageContext import com.stevesoltys.seedvault.getStorageContext
import com.stevesoltys.seedvault.settings.SettingsManager import com.stevesoltys.seedvault.settings.SettingsManager
import com.stevesoltys.seedvault.settings.Storage
import kotlinx.coroutines.TimeoutCancellationException import kotlinx.coroutines.TimeoutCancellationException
import kotlinx.coroutines.runBlocking import kotlinx.coroutines.runBlocking
import kotlinx.coroutines.suspendCancellableCoroutine import kotlinx.coroutines.suspendCancellableCoroutine
@ -45,9 +44,9 @@ internal class DocumentsStorage(
private val appContext: Context, private val appContext: Context,
private val settingsManager: SettingsManager, private val settingsManager: SettingsManager,
) { ) {
internal var storage: Storage? = null internal var safStorage: SafStorage? = null
get() { get() {
if (field == null) field = settingsManager.getStorage() if (field == null) field = settingsManager.getSafStorage()
return field return field
} }
@ -56,14 +55,14 @@ internal class DocumentsStorage(
*/ */
private val context: Context private val context: Context
get() = appContext.getStorageContext { get() = appContext.getStorageContext {
storage?.isUsb == true safStorage?.isUsb == true
} }
private val contentResolver: ContentResolver get() = context.contentResolver private val contentResolver: ContentResolver get() = context.contentResolver
internal var rootBackupDir: DocumentFile? = null internal var rootBackupDir: DocumentFile? = null
get() = runBlocking { get() = runBlocking {
if (field == null) { if (field == null) {
val parent = storage?.getDocumentFile(context) val parent = safStorage?.getDocumentFile(context)
?: return@runBlocking null ?: return@runBlocking null
field = try { field = try {
parent.createOrGetDirectory(context, DIRECTORY_ROOT).apply { parent.createOrGetDirectory(context, DIRECTORY_ROOT).apply {
@ -104,13 +103,13 @@ internal class DocumentsStorage(
* Resets this storage abstraction, forcing it to re-fetch cached values on next access. * Resets this storage abstraction, forcing it to re-fetch cached values on next access.
*/ */
fun reset(newToken: Long?) { fun reset(newToken: Long?) {
storage = null safStorage = null
currentToken = newToken currentToken = newToken
rootBackupDir = null rootBackupDir = null
currentSetDir = null currentSetDir = null
} }
fun getAuthority(): String? = storage?.uri?.authority fun getAuthority(): String? = safStorage?.uri?.authority
@Throws(IOException::class) @Throws(IOException::class)
suspend fun getSetDir(token: Long = currentToken ?: error("no token")): DocumentFile? { suspend fun getSetDir(token: Long = currentToken ?: error("no token")): DocumentFile? {

View file

@ -0,0 +1,49 @@
/*
* SPDX-FileCopyrightText: 2024 The Calyx Institute
* SPDX-License-Identifier: Apache-2.0
*/
package com.stevesoltys.seedvault.plugins.saf
import android.content.Context
import android.net.ConnectivityManager
import android.net.NetworkCapabilities
import android.net.Uri
import androidx.annotation.WorkerThread
import androidx.documentfile.provider.DocumentFile
data class SafStorage(
val uri: Uri,
val name: String,
val isUsb: Boolean,
val requiresNetwork: Boolean,
) {
fun getDocumentFile(context: Context) = DocumentFile.fromTreeUri(context, uri)
?: throw AssertionError("Should only happen on API < 21.")
/**
* Returns true if this is USB storage that is not available, false otherwise.
*
* Must be run off UI thread (ideally I/O).
*/
@WorkerThread
fun isUnavailableUsb(context: Context): Boolean {
return isUsb && !getDocumentFile(context).isDirectory
}
/**
* Returns true if this is storage that requires network access,
* but it isn't available right now.
*/
fun isUnavailableNetwork(context: Context, allowMetered: Boolean): Boolean {
return requiresNetwork && !hasUnmeteredInternet(context, allowMetered)
}
private fun hasUnmeteredInternet(context: Context, allowMetered: Boolean): Boolean {
val cm = context.getSystemService(ConnectivityManager::class.java) ?: return false
val isMetered = cm.isActiveNetworkMetered
val capabilities = cm.getNetworkCapabilities(cm.activeNetwork) ?: return false
return capabilities.hasCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET) &&
(allowMetered || !isMetered)
}
}

View file

@ -13,7 +13,7 @@ import com.stevesoltys.seedvault.plugins.chunkFolderRegex
import com.stevesoltys.seedvault.plugins.saf.FILE_BACKUP_METADATA import com.stevesoltys.seedvault.plugins.saf.FILE_BACKUP_METADATA
import com.stevesoltys.seedvault.plugins.saf.FILE_NO_MEDIA import com.stevesoltys.seedvault.plugins.saf.FILE_NO_MEDIA
import com.stevesoltys.seedvault.plugins.tokenRegex import com.stevesoltys.seedvault.plugins.tokenRegex
import com.stevesoltys.seedvault.settings.Storage import com.stevesoltys.seedvault.plugins.saf.SafStorage
import okhttp3.HttpUrl.Companion.toHttpUrl import okhttp3.HttpUrl.Companion.toHttpUrl
import java.io.IOException import java.io.IOException
import java.io.InputStream import java.io.InputStream
@ -135,7 +135,7 @@ internal class WebDavStoragePlugin(
} }
@Throws(IOException::class) @Throws(IOException::class)
override suspend fun hasBackup(storage: Storage): Boolean { override suspend fun hasBackup(safStorage: SafStorage): Boolean {
// TODO this requires refactoring // TODO this requires refactoring
return true return true
} }

View file

@ -29,7 +29,7 @@ class SchedulingFragment : PreferenceFragmentCompat(),
override fun onViewCreated(view: View, savedInstanceState: Bundle?) { override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState) super.onViewCreated(view, savedInstanceState)
val storage = settingsManager.getStorage() val storage = settingsManager.getSafStorage()
if (storage?.isUsb == true) { if (storage?.isUsb == true) {
findPreference<PreferenceCategory>("scheduling_category_conditions")?.isEnabled = false findPreference<PreferenceCategory>("scheduling_category_conditions")?.isEnabled = false
} }

View file

@ -22,6 +22,7 @@ import androidx.preference.TwoStatePreference
import androidx.work.WorkInfo import androidx.work.WorkInfo
import com.stevesoltys.seedvault.R import com.stevesoltys.seedvault.R
import com.stevesoltys.seedvault.permitDiskReads import com.stevesoltys.seedvault.permitDiskReads
import com.stevesoltys.seedvault.plugins.saf.SafStorage
import com.stevesoltys.seedvault.restore.RestoreActivity import com.stevesoltys.seedvault.restore.RestoreActivity
import com.stevesoltys.seedvault.ui.toRelativeTime import com.stevesoltys.seedvault.ui.toRelativeTime
import org.koin.android.ext.android.inject import org.koin.android.ext.android.inject
@ -48,7 +49,7 @@ class SettingsFragment : PreferenceFragmentCompat() {
private var menuBackupNow: MenuItem? = null private var menuBackupNow: MenuItem? = null
private var menuRestore: MenuItem? = null private var menuRestore: MenuItem? = null
private var storage: Storage? = null private var safStorage: SafStorage? = null
override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) { override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) {
permitDiskReads { permitDiskReads {
@ -164,7 +165,7 @@ class SettingsFragment : PreferenceFragmentCompat() {
// we need to re-set the title when returning to this fragment // we need to re-set the title when returning to this fragment
activity?.setTitle(R.string.backup) activity?.setTitle(R.string.backup)
storage = settingsManager.getStorage() safStorage = settingsManager.getSafStorage()
setBackupEnabledState() setBackupEnabledState()
setBackupLocationSummary() setBackupLocationSummary()
setAutoRestoreState() setAutoRestoreState()
@ -241,7 +242,7 @@ class SettingsFragment : PreferenceFragmentCompat() {
activity?.contentResolver?.let { activity?.contentResolver?.let {
autoRestore.isChecked = Settings.Secure.getInt(it, BACKUP_AUTO_RESTORE, 1) == 1 autoRestore.isChecked = Settings.Secure.getInt(it, BACKUP_AUTO_RESTORE, 1) == 1
} }
val storage = this.storage val storage = this.safStorage
if (storage?.isUsb == true) { if (storage?.isUsb == true) {
autoRestore.summary = getString(R.string.settings_auto_restore_summary) + "\n\n" + autoRestore.summary = getString(R.string.settings_auto_restore_summary) + "\n\n" +
getString(R.string.settings_auto_restore_summary_usb, storage.name) getString(R.string.settings_auto_restore_summary_usb, storage.name)
@ -252,7 +253,7 @@ class SettingsFragment : PreferenceFragmentCompat() {
private fun setBackupLocationSummary() { private fun setBackupLocationSummary() {
// get name of storage location // get name of storage location
backupLocation.summary = storage?.name ?: getString(R.string.settings_backup_location_none) backupLocation.summary = safStorage?.name ?: getString(R.string.settings_backup_location_none)
} }
private fun setAppBackupStatusSummary(lastBackupInMillis: Long?) { private fun setAppBackupStatusSummary(lastBackupInMillis: Long?) {
@ -271,7 +272,7 @@ class SettingsFragment : PreferenceFragmentCompat() {
* says that nothing is scheduled which can happen when backup destination is on flash drive. * says that nothing is scheduled which can happen when backup destination is on flash drive.
*/ */
private fun setAppBackupSchedulingSummary(workInfo: WorkInfo?) { private fun setAppBackupSchedulingSummary(workInfo: WorkInfo?) {
if (storage?.isUsb == true) { if (safStorage?.isUsb == true) {
backupScheduling.summary = getString(R.string.settings_backup_status_next_backup_usb) backupScheduling.summary = getString(R.string.settings_backup_status_next_backup_usb)
return return
} }

View file

@ -12,6 +12,7 @@ import androidx.documentfile.provider.DocumentFile
import androidx.preference.PreferenceManager import androidx.preference.PreferenceManager
import com.stevesoltys.seedvault.getStorageContext import com.stevesoltys.seedvault.getStorageContext
import com.stevesoltys.seedvault.permitDiskReads import com.stevesoltys.seedvault.permitDiskReads
import com.stevesoltys.seedvault.plugins.saf.SafStorage
import com.stevesoltys.seedvault.transport.backup.BackupCoordinator import com.stevesoltys.seedvault.transport.backup.BackupCoordinator
import java.util.concurrent.ConcurrentSkipListSet import java.util.concurrent.ConcurrentSkipListSet
@ -88,24 +89,24 @@ class SettingsManager(private val context: Context) {
token = newToken token = newToken
} }
// FIXME Storage is currently plugin specific and not generic // FIXME SafStorage is currently plugin specific and not generic
fun setStorage(storage: Storage) { fun setSafStorage(safStorage: SafStorage) {
prefs.edit() prefs.edit()
.putString(PREF_KEY_STORAGE_URI, storage.uri.toString()) .putString(PREF_KEY_STORAGE_URI, safStorage.uri.toString())
.putString(PREF_KEY_STORAGE_NAME, storage.name) .putString(PREF_KEY_STORAGE_NAME, safStorage.name)
.putBoolean(PREF_KEY_STORAGE_IS_USB, storage.isUsb) .putBoolean(PREF_KEY_STORAGE_IS_USB, safStorage.isUsb)
.putBoolean(PREF_KEY_STORAGE_REQUIRES_NETWORK, storage.requiresNetwork) .putBoolean(PREF_KEY_STORAGE_REQUIRES_NETWORK, safStorage.requiresNetwork)
.apply() .apply()
} }
fun getStorage(): Storage? { fun getSafStorage(): SafStorage? {
val uriStr = prefs.getString(PREF_KEY_STORAGE_URI, null) ?: return null val uriStr = prefs.getString(PREF_KEY_STORAGE_URI, null) ?: return null
val uri = Uri.parse(uriStr) val uri = Uri.parse(uriStr)
val name = prefs.getString(PREF_KEY_STORAGE_NAME, null) val name = prefs.getString(PREF_KEY_STORAGE_NAME, null)
?: throw IllegalStateException("no storage name") ?: throw IllegalStateException("no storage name")
val isUsb = prefs.getBoolean(PREF_KEY_STORAGE_IS_USB, false) val isUsb = prefs.getBoolean(PREF_KEY_STORAGE_IS_USB, false)
val requiresNetwork = prefs.getBoolean(PREF_KEY_STORAGE_REQUIRES_NETWORK, false) val requiresNetwork = prefs.getBoolean(PREF_KEY_STORAGE_REQUIRES_NETWORK, false)
return Storage(uri, name, isUsb, requiresNetwork) return SafStorage(uri, name, isUsb, requiresNetwork)
} }
fun setFlashDrive(usb: FlashDrive?) { fun setFlashDrive(usb: FlashDrive?) {
@ -144,7 +145,7 @@ class SettingsManager(private val context: Context) {
*/ */
@WorkerThread @WorkerThread
fun canDoBackupNow(): Boolean { fun canDoBackupNow(): Boolean {
val storage = getStorage() ?: return false val storage = getSafStorage() ?: return false
val systemContext = context.getStorageContext { storage.isUsb } val systemContext = context.getStorageContext { storage.isUsb }
return !storage.isUnavailableUsb(systemContext) && return !storage.isUnavailableUsb(systemContext) &&
!storage.isUnavailableNetwork(context, useMeteredNetwork) !storage.isUnavailableNetwork(context, useMeteredNetwork)

View file

@ -131,7 +131,7 @@ internal class SettingsViewModel(
} }
override fun onStorageLocationChanged() { override fun onStorageLocationChanged() {
val storage = settingsManager.getStorage() ?: return val storage = settingsManager.getSafStorage() ?: return
Log.i(TAG, "onStorageLocationChanged (isUsb: ${storage.isUsb}") Log.i(TAG, "onStorageLocationChanged (isUsb: ${storage.isUsb}")
if (storage.isUsb) { if (storage.isUsb) {
@ -156,7 +156,7 @@ internal class SettingsViewModel(
} }
private fun onStoragePropertiesChanged() { private fun onStoragePropertiesChanged() {
val storage = settingsManager.getStorage() ?: return val storage = settingsManager.getSafStorage() ?: return
Log.d(TAG, "onStoragePropertiesChanged") Log.d(TAG, "onStoragePropertiesChanged")
// register storage observer // register storage observer
@ -200,7 +200,7 @@ internal class SettingsViewModel(
i.putExtra(EXTRA_START_APP_BACKUP, true) i.putExtra(EXTRA_START_APP_BACKUP, true)
startForegroundService(app, i) startForegroundService(app, i)
} else { } else {
val isUsb = settingsManager.getStorage()?.isUsb ?: false val isUsb = settingsManager.getSafStorage()?.isUsb ?: false
AppBackupWorker.scheduleNow(app, reschedule = !isUsb) AppBackupWorker.scheduleNow(app, reschedule = !isUsb)
} }
} }
@ -280,14 +280,14 @@ internal class SettingsViewModel(
} }
fun scheduleAppBackup(existingWorkPolicy: ExistingPeriodicWorkPolicy) { fun scheduleAppBackup(existingWorkPolicy: ExistingPeriodicWorkPolicy) {
val storage = settingsManager.getStorage() ?: error("no storage available") val storage = settingsManager.getSafStorage() ?: error("no storage available")
if (!storage.isUsb && backupManager.isBackupEnabled) { if (!storage.isUsb && backupManager.isBackupEnabled) {
AppBackupWorker.schedule(app, settingsManager, existingWorkPolicy) AppBackupWorker.schedule(app, settingsManager, existingWorkPolicy)
} }
} }
fun scheduleFilesBackup() { fun scheduleFilesBackup() {
val storage = settingsManager.getStorage() ?: error("no storage available") val storage = settingsManager.getSafStorage() ?: error("no storage available")
if (!storage.isUsb && settingsManager.isStorageBackupEnabled()) { if (!storage.isUsb && settingsManager.isStorageBackupEnabled()) {
BackupJobService.scheduleJob( BackupJobService.scheduleJob(
context = app, context = app,

View file

@ -18,7 +18,7 @@ internal class SeedvaultSafStoragePlugin(
*/ */
override val context: Context override val context: Context
get() = appContext.getStorageContext { get() = appContext.getStorageContext {
storage.storage?.isUsb == true storage.safStorage?.isUsb == true
} }
override val root: DocumentFile override val root: DocumentFile
get() = storage.rootBackupDir ?: error("No storage set") get() = storage.rootBackupDir ?: error("No storage set")

View file

@ -43,7 +43,7 @@ internal class StorageBackupService : BackupService() {
override fun onBackupFinished(intent: Intent, success: Boolean) { override fun onBackupFinished(intent: Intent, success: Boolean) {
if (intent.getBooleanExtra(EXTRA_START_APP_BACKUP, false)) { if (intent.getBooleanExtra(EXTRA_START_APP_BACKUP, false)) {
val isUsb = settingsManager.getStorage()?.isUsb ?: false val isUsb = settingsManager.getSafStorage()?.isUsb ?: false
AppBackupWorker.scheduleNow(applicationContext, reschedule = !isUsb) AppBackupWorker.scheduleNow(applicationContext, reschedule = !isUsb)
} }
} }

View file

@ -411,7 +411,7 @@ internal class BackupCoordinator(
val longBackoff = DAYS.toMillis(30) val longBackoff = DAYS.toMillis(30)
// back off if there's no storage set // back off if there's no storage set
val storage = settingsManager.getStorage() ?: return longBackoff val storage = settingsManager.getSafStorage() ?: return longBackoff
return when { return when {
// back off if storage is removable and not available right now // back off if storage is removable and not available right now
storage.isUnavailableUsb(context) -> longBackoff storage.isUnavailableUsb(context) -> longBackoff

View file

@ -169,7 +169,7 @@ internal class RestoreCoordinator(
// check if we even have a backup of that app // check if we even have a backup of that app
if (metadataManager.getPackageMetadata(pmPackageName) != null) { if (metadataManager.getPackageMetadata(pmPackageName) != null) {
// remind user to plug in storage device // remind user to plug in storage device
val storageName = settingsManager.getStorage()?.name val storageName = settingsManager.getSafStorage()?.name
?: context.getString(R.string.settings_backup_location_none) ?: context.getString(R.string.settings_backup_location_none)
notificationManager.onRemovableStorageNotAvailableForRestore( notificationManager.onRemovableStorageNotAvailableForRestore(
pmPackageName, pmPackageName,
@ -365,7 +365,7 @@ internal class RestoreCoordinator(
// TODO this is plugin specific, needs to be factored out when supporting different plugins // TODO this is plugin specific, needs to be factored out when supporting different plugins
private fun isStorageRemovableAndNotAvailable(): Boolean { private fun isStorageRemovableAndNotAvailable(): Boolean {
val storage = settingsManager.getStorage() ?: return false val storage = settingsManager.getSafStorage() ?: return false
return storage.isUnavailableUsb(context) return storage.isUnavailableUsb(context)
} }

View file

@ -74,7 +74,7 @@ internal class BackupStorageViewModel(
} }
private fun scheduleBackupWorkers() { private fun scheduleBackupWorkers() {
val storage = settingsManager.getStorage() ?: error("no storage available") val storage = settingsManager.getSafStorage() ?: error("no storage available")
if (!storage.isUsb) { if (!storage.isUsb) {
if (backupManager.isBackupEnabled) { if (backupManager.isBackupEnabled) {
AppBackupWorker.schedule(app, settingsManager, CANCEL_AND_REENQUEUE) AppBackupWorker.schedule(app, settingsManager, CANCEL_AND_REENQUEUE)

View file

@ -16,10 +16,10 @@ import androidx.lifecycle.viewModelScope
import com.stevesoltys.seedvault.R import com.stevesoltys.seedvault.R
import com.stevesoltys.seedvault.isMassStorage import com.stevesoltys.seedvault.isMassStorage
import com.stevesoltys.seedvault.permitDiskReads import com.stevesoltys.seedvault.permitDiskReads
import com.stevesoltys.seedvault.plugins.saf.SafStorage
import com.stevesoltys.seedvault.settings.BackupManagerSettings import com.stevesoltys.seedvault.settings.BackupManagerSettings
import com.stevesoltys.seedvault.settings.FlashDrive import com.stevesoltys.seedvault.settings.FlashDrive
import com.stevesoltys.seedvault.settings.SettingsManager import com.stevesoltys.seedvault.settings.SettingsManager
import com.stevesoltys.seedvault.settings.Storage
import com.stevesoltys.seedvault.ui.LiveEvent import com.stevesoltys.seedvault.ui.LiveEvent
import com.stevesoltys.seedvault.ui.MutableLiveEvent import com.stevesoltys.seedvault.ui.MutableLiveEvent
import com.stevesoltys.seedvault.ui.storage.StorageOption.SafOption import com.stevesoltys.seedvault.ui.storage.StorageOption.SafOption
@ -47,7 +47,7 @@ internal abstract class StorageViewModel(
internal var isSetupWizard: Boolean = false internal var isSetupWizard: Boolean = false
internal val hasStorageSet: Boolean internal val hasStorageSet: Boolean
get() = settingsManager.getStorage() != null get() = settingsManager.getSafStorage() != null
abstract val isRestoreOperation: Boolean abstract val isRestoreOperation: Boolean
companion object { companion object {
@ -55,7 +55,7 @@ internal abstract class StorageViewModel(
context: Context, context: Context,
settingsManager: SettingsManager, settingsManager: SettingsManager,
): Boolean { ): Boolean {
val storage = settingsManager.getStorage() ?: return false val storage = settingsManager.getSafStorage() ?: return false
if (storage.isUsb) return true if (storage.isUsb) return true
return permitDiskReads { return permitDiskReads {
storage.getDocumentFile(context).isDirectory storage.getDocumentFile(context).isDirectory
@ -106,20 +106,20 @@ internal abstract class StorageViewModel(
return saveStorage(storage) return saveStorage(storage)
} }
protected fun createStorage(uri: Uri): Storage { protected fun createStorage(uri: Uri): SafStorage {
val root = safOption ?: throw IllegalStateException("no storage root") val root = safOption ?: throw IllegalStateException("no storage root")
val name = if (root.isInternal()) { val name = if (root.isInternal()) {
"${root.title} (${app.getString(R.string.settings_backup_location_internal)})" "${root.title} (${app.getString(R.string.settings_backup_location_internal)})"
} else { } else {
root.title root.title
} }
return Storage(uri, name, root.isUsb, root.requiresNetwork) return SafStorage(uri, name, root.isUsb, root.requiresNetwork)
} }
protected fun saveStorage(storage: Storage): Boolean { protected fun saveStorage(safStorage: SafStorage): Boolean {
settingsManager.setStorage(storage) settingsManager.setSafStorage(safStorage)
if (storage.isUsb) { if (safStorage.isUsb) {
Log.d(TAG, "Selected storage is a removable USB device.") Log.d(TAG, "Selected storage is a removable USB device.")
val wasSaved = saveUsbDevice() val wasSaved = saveUsbDevice()
// reset stored flash drive, if we did not update it // reset stored flash drive, if we did not update it
@ -129,9 +129,9 @@ internal abstract class StorageViewModel(
} }
BackupManagerSettings.resetDefaults(app.contentResolver) BackupManagerSettings.resetDefaults(app.contentResolver)
Log.d(TAG, "New storage location saved: ${storage.uri}") Log.d(TAG, "New storage location saved: ${safStorage.uri}")
return storage.isUsb return safStorage.isUsb
} }
private fun saveUsbDevice(): Boolean { private fun saveUsbDevice(): Boolean {

View file

@ -17,7 +17,7 @@ import com.stevesoltys.seedvault.metadata.PackageState.NO_DATA
import com.stevesoltys.seedvault.metadata.PackageState.QUOTA_EXCEEDED import com.stevesoltys.seedvault.metadata.PackageState.QUOTA_EXCEEDED
import com.stevesoltys.seedvault.plugins.StoragePlugin import com.stevesoltys.seedvault.plugins.StoragePlugin
import com.stevesoltys.seedvault.plugins.saf.FILE_BACKUP_METADATA import com.stevesoltys.seedvault.plugins.saf.FILE_BACKUP_METADATA
import com.stevesoltys.seedvault.settings.Storage import com.stevesoltys.seedvault.plugins.saf.SafStorage
import com.stevesoltys.seedvault.ui.notification.BackupNotificationManager import com.stevesoltys.seedvault.ui.notification.BackupNotificationManager
import com.stevesoltys.seedvault.worker.ApkBackup import com.stevesoltys.seedvault.worker.ApkBackup
import io.mockk.Runs import io.mockk.Runs
@ -58,7 +58,7 @@ internal class BackupCoordinatorTest : BackupTest() {
private val metadataOutputStream = mockk<OutputStream>() private val metadataOutputStream = mockk<OutputStream>()
private val fileDescriptor: ParcelFileDescriptor = mockk() private val fileDescriptor: ParcelFileDescriptor = mockk()
private val packageMetadata: PackageMetadata = mockk() private val packageMetadata: PackageMetadata = mockk()
private val storage = Storage( private val safStorage = SafStorage(
uri = Uri.EMPTY, uri = Uri.EMPTY,
name = getRandomString(), name = getRandomString(),
isUsb = false, isUsb = false,
@ -290,7 +290,7 @@ internal class BackupCoordinatorTest : BackupTest() {
) )
} just Runs } just Runs
coEvery { full.cancelFullBackup(token, metadata.salt, false) } just Runs coEvery { full.cancelFullBackup(token, metadata.salt, false) } just Runs
every { settingsManager.getStorage() } returns storage every { settingsManager.getSafStorage() } returns safStorage
every { settingsManager.useMeteredNetwork } returns false every { settingsManager.useMeteredNetwork } returns false
every { metadataOutputStream.close() } just Runs every { metadataOutputStream.close() } just Runs
@ -340,7 +340,7 @@ internal class BackupCoordinatorTest : BackupTest() {
) )
} just Runs } just Runs
coEvery { full.cancelFullBackup(token, metadata.salt, false) } just Runs coEvery { full.cancelFullBackup(token, metadata.salt, false) } just Runs
every { settingsManager.getStorage() } returns storage every { settingsManager.getSafStorage() } returns safStorage
every { settingsManager.useMeteredNetwork } returns false every { settingsManager.useMeteredNetwork } returns false
every { metadataOutputStream.close() } just Runs every { metadataOutputStream.close() } just Runs

View file

@ -16,7 +16,7 @@ import com.stevesoltys.seedvault.metadata.MetadataReader
import com.stevesoltys.seedvault.metadata.PackageMetadata import com.stevesoltys.seedvault.metadata.PackageMetadata
import com.stevesoltys.seedvault.plugins.EncryptedMetadata import com.stevesoltys.seedvault.plugins.EncryptedMetadata
import com.stevesoltys.seedvault.plugins.StoragePlugin import com.stevesoltys.seedvault.plugins.StoragePlugin
import com.stevesoltys.seedvault.settings.Storage import com.stevesoltys.seedvault.plugins.saf.SafStorage
import com.stevesoltys.seedvault.transport.TransportTest import com.stevesoltys.seedvault.transport.TransportTest
import com.stevesoltys.seedvault.ui.notification.BackupNotificationManager import com.stevesoltys.seedvault.ui.notification.BackupNotificationManager
import io.mockk.Runs import io.mockk.Runs
@ -57,7 +57,7 @@ internal class RestoreCoordinatorTest : TransportTest() {
) )
private val inputStream = mockk<InputStream>() private val inputStream = mockk<InputStream>()
private val storage: Storage = mockk() private val safStorage: SafStorage = mockk()
private val packageInfo2 = PackageInfo().apply { packageName = "org.example2" } private val packageInfo2 = PackageInfo().apply { packageName = "org.example2" }
private val packageInfoArray = arrayOf(packageInfo) private val packageInfoArray = arrayOf(packageInfo)
private val packageInfoArray2 = arrayOf(packageInfo, packageInfo2) private val packageInfoArray2 = arrayOf(packageInfo, packageInfo2)
@ -164,10 +164,10 @@ internal class RestoreCoordinatorTest : TransportTest() {
@Test @Test
fun `startRestore() optimized auto-restore with removed storage shows notification`() = fun `startRestore() optimized auto-restore with removed storage shows notification`() =
runBlocking { runBlocking {
every { settingsManager.getStorage() } returns storage every { settingsManager.getSafStorage() } returns safStorage
every { storage.isUnavailableUsb(context) } returns true every { safStorage.isUnavailableUsb(context) } returns true
every { metadataManager.getPackageMetadata(packageName) } returns PackageMetadata(42L) every { metadataManager.getPackageMetadata(packageName) } returns PackageMetadata(42L)
every { storage.name } returns storageName every { safStorage.name } returns storageName
every { every {
notificationManager.onRemovableStorageNotAvailableForRestore( notificationManager.onRemovableStorageNotAvailableForRestore(
packageName, packageName,
@ -188,8 +188,8 @@ internal class RestoreCoordinatorTest : TransportTest() {
@Test @Test
fun `startRestore() optimized auto-restore with available storage shows no notification`() = fun `startRestore() optimized auto-restore with available storage shows no notification`() =
runBlocking { runBlocking {
every { settingsManager.getStorage() } returns storage every { settingsManager.getSafStorage() } returns safStorage
every { storage.isUnavailableUsb(context) } returns false every { safStorage.isUnavailableUsb(context) } returns false
restore.beforeStartRestore(metadata) restore.beforeStartRestore(metadata)
assertEquals(TRANSPORT_OK, restore.startRestore(token, pmPackageInfoArray)) assertEquals(TRANSPORT_OK, restore.startRestore(token, pmPackageInfoArray))
@ -204,8 +204,8 @@ internal class RestoreCoordinatorTest : TransportTest() {
@Test @Test
fun `startRestore() with removed storage shows no notification`() = runBlocking { fun `startRestore() with removed storage shows no notification`() = runBlocking {
every { settingsManager.getStorage() } returns storage every { settingsManager.getSafStorage() } returns safStorage
every { storage.isUnavailableUsb(context) } returns true every { safStorage.isUnavailableUsb(context) } returns true
every { metadataManager.getPackageMetadata(packageName) } returns null every { metadataManager.getPackageMetadata(packageName) } returns null
assertEquals(TRANSPORT_ERROR, restore.startRestore(token, pmPackageInfoArray)) assertEquals(TRANSPORT_ERROR, restore.startRestore(token, pmPackageInfoArray))