Don't allow running app data check while backup is running

This commit is contained in:
Torsten Grote 2024-10-29 18:22:36 -03:00
parent 45d710270a
commit 15e8850e5e
No known key found for this signature in database
GPG key ID: 3E5F77D92CF891FF
6 changed files with 58 additions and 30 deletions

View file

@ -16,6 +16,7 @@ import com.stevesoltys.seedvault.storage.StorageBackupService
import com.stevesoltys.seedvault.transport.ConfigurableBackupTransportService import com.stevesoltys.seedvault.transport.ConfigurableBackupTransportService
import com.stevesoltys.seedvault.worker.AppBackupPruneWorker import com.stevesoltys.seedvault.worker.AppBackupPruneWorker
import com.stevesoltys.seedvault.worker.AppBackupWorker.Companion.UNIQUE_WORK_NAME import com.stevesoltys.seedvault.worker.AppBackupWorker.Companion.UNIQUE_WORK_NAME
import com.stevesoltys.seedvault.worker.AppCheckerWorker
import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.combine import kotlinx.coroutines.flow.combine
@ -32,18 +33,28 @@ class BackupStateManager(
flow = ConfigurableBackupTransportService.isRunning, flow = ConfigurableBackupTransportService.isRunning,
flow2 = StorageBackupService.isRunning, flow2 = StorageBackupService.isRunning,
flow3 = workManager.getWorkInfosForUniqueWorkFlow(UNIQUE_WORK_NAME), flow3 = workManager.getWorkInfosForUniqueWorkFlow(UNIQUE_WORK_NAME),
flow4 = workManager.getWorkInfosForUniqueWorkFlow(AppBackupPruneWorker.UNIQUE_WORK_NAME), ) { appBackupRunning, filesBackupRunning, workInfo1 ->
) { appBackupRunning, filesBackupRunning, workInfo1, workInfo2 ->
val workInfoState1 = workInfo1.getOrNull(0)?.state val workInfoState1 = workInfo1.getOrNull(0)?.state
val workInfoState2 = workInfo2.getOrNull(0)?.state
Log.i( Log.i(
TAG, "appBackupRunning: $appBackupRunning, " + TAG, "appBackupRunning: $appBackupRunning, " +
"filesBackupRunning: $filesBackupRunning, " + "filesBackupRunning: $filesBackupRunning, " +
"appBackupWorker: ${workInfoState1?.name}, " + "appBackupWorker: ${workInfoState1?.name}"
"pruneBackupWorker: ${workInfoState2?.name}"
) )
appBackupRunning || filesBackupRunning || appBackupRunning || filesBackupRunning || workInfoState1 == RUNNING
workInfoState1 == RUNNING || workInfoState2 == RUNNING }
val isCheckOrPruneRunning: Flow<Boolean> = combine(
flow = workManager.getWorkInfosForUniqueWorkFlow(AppBackupPruneWorker.UNIQUE_WORK_NAME),
flow2 = workManager.getWorkInfosForUniqueWorkFlow(AppCheckerWorker.UNIQUE_WORK_NAME),
) { pruneInfo, checkInfo ->
val pruneInfoState = pruneInfo.getOrNull(0)?.state
val checkInfoState = checkInfo.getOrNull(0)?.state
Log.i(
TAG,
"pruneBackupWorker: ${pruneInfoState?.name}, " +
"appCheckerWorker: ${checkInfoState?.name}"
)
pruneInfoState == RUNNING || checkInfoState == RUNNING
} }
val isAutoRestoreEnabled: Boolean val isAutoRestoreEnabled: Boolean

View file

@ -49,6 +49,7 @@ class SettingsFragment : PreferenceFragmentCompat() {
private lateinit var backupLocation: Preference private lateinit var backupLocation: Preference
private lateinit var backupStatus: Preference private lateinit var backupStatus: Preference
private lateinit var backupScheduling: Preference private lateinit var backupScheduling: Preference
private lateinit var backupAppCheck: Preference
private lateinit var backupStorage: TwoStatePreference private lateinit var backupStorage: TwoStatePreference
private lateinit var backupRecoveryCode: Preference private lateinit var backupRecoveryCode: Preference
@ -92,15 +93,9 @@ class SettingsFragment : PreferenceFragmentCompat() {
backupLocation = findPreference("backup_location")!! backupLocation = findPreference("backup_location")!!
backupLocation.setOnPreferenceClickListener { backupLocation.setOnPreferenceClickListener {
if (viewModel.isBackupRunning.value) {
// don't allow changing backup destination while backup is running
// TODO we could show toast or snackbar here
false
} else {
viewModel.chooseBackupLocation() viewModel.chooseBackupLocation()
true true
} }
}
autoRestore = findPreference(PREF_KEY_AUTO_RESTORE)!! autoRestore = findPreference(PREF_KEY_AUTO_RESTORE)!!
autoRestore.onPreferenceChangeListener = OnPreferenceChangeListener { _, newValue -> autoRestore.onPreferenceChangeListener = OnPreferenceChangeListener { _, newValue ->
@ -117,6 +112,7 @@ class SettingsFragment : PreferenceFragmentCompat() {
backupStatus = findPreference("backup_status")!! backupStatus = findPreference("backup_status")!!
backupScheduling = findPreference("backup_scheduling")!! backupScheduling = findPreference("backup_scheduling")!!
backupAppCheck = findPreference("backup_app_check")!!
backupStorage = findPreference("backup_storage")!! backupStorage = findPreference("backup_storage")!!
backupStorage.onPreferenceChangeListener = OnPreferenceChangeListener { _, newValue -> backupStorage.onPreferenceChangeListener = OnPreferenceChangeListener { _, newValue ->
@ -148,6 +144,8 @@ class SettingsFragment : PreferenceFragmentCompat() {
viewModel.backupPossible.observe(viewLifecycleOwner) { possible -> viewModel.backupPossible.observe(viewLifecycleOwner) { possible ->
toolbar.menu.findItem(R.id.action_backup)?.isEnabled = possible toolbar.menu.findItem(R.id.action_backup)?.isEnabled = possible
toolbar.menu.findItem(R.id.action_restore)?.isEnabled = possible toolbar.menu.findItem(R.id.action_restore)?.isEnabled = possible
backupLocation.isEnabled = possible
backupAppCheck.isEnabled = possible
} }
viewModel.lastBackupTime.observe(viewLifecycleOwner) { time -> viewModel.lastBackupTime.observe(viewLifecycleOwner) { time ->

View file

@ -83,7 +83,8 @@ internal class SettingsViewModel(
override val isRestoreOperation = false override val isRestoreOperation = false
val isFirstStart get() = settingsManager.isFirstStart val isFirstStart get() = settingsManager.isFirstStart
val isBackupRunning: StateFlow<Boolean> private val isBackupRunning: StateFlow<Boolean>
private val isCheckOrPruneRunning: StateFlow<Boolean>
private val mBackupPossible = MutableLiveData(false) private val mBackupPossible = MutableLiveData(false)
val backupPossible: LiveData<Boolean> = mBackupPossible val backupPossible: LiveData<Boolean> = mBackupPossible
@ -144,12 +145,23 @@ internal class SettingsViewModel(
started = SharingStarted.Eagerly, started = SharingStarted.Eagerly,
initialValue = false, initialValue = false,
) )
isCheckOrPruneRunning = backupStateManager.isCheckOrPruneRunning.stateIn(
scope = viewModelScope,
started = SharingStarted.Eagerly,
initialValue = false,
)
scope.launch { scope.launch {
// update running state // update running state
isBackupRunning.collect { isBackupRunning.collect {
onBackupRunningStateChanged() onBackupRunningStateChanged()
} }
} }
scope.launch {
// update running state
isCheckOrPruneRunning.collect {
onBackupRunningStateChanged()
}
}
onStoragePropertiesChanged() onStoragePropertiesChanged()
loadFilesSummary() loadFilesSummary()
} }
@ -172,11 +184,11 @@ internal class SettingsViewModel(
} }
private fun onBackupRunningStateChanged() { private fun onBackupRunningStateChanged() {
if (isBackupRunning.value) mBackupPossible.postValue(false) val backupAllowed = !isBackupRunning.value && !isCheckOrPruneRunning.value
else viewModelScope.launch(Dispatchers.IO) { if (backupAllowed) viewModelScope.launch(Dispatchers.IO) {
val canDo = !isBackupRunning.value && !backendManager.isOnUnavailableUsb() val canDo = !backendManager.isOnUnavailableUsb()
mBackupPossible.postValue(canDo) mBackupPossible.postValue(canDo)
} } else mBackupPossible.postValue(false)
} }
private fun onStoragePropertiesChanged() { private fun onStoragePropertiesChanged() {

View file

@ -22,12 +22,14 @@ import androidx.work.OutOfQuotaPolicy.RUN_AS_NON_EXPEDITED_WORK_REQUEST
import androidx.work.PeriodicWorkRequestBuilder import androidx.work.PeriodicWorkRequestBuilder
import androidx.work.WorkManager import androidx.work.WorkManager
import androidx.work.WorkerParameters import androidx.work.WorkerParameters
import com.stevesoltys.seedvault.BackupStateManager
import com.stevesoltys.seedvault.R import com.stevesoltys.seedvault.R
import com.stevesoltys.seedvault.backend.BackendManager import com.stevesoltys.seedvault.backend.BackendManager
import com.stevesoltys.seedvault.repo.AppBackupManager import com.stevesoltys.seedvault.repo.AppBackupManager
import com.stevesoltys.seedvault.settings.SettingsManager import com.stevesoltys.seedvault.settings.SettingsManager
import com.stevesoltys.seedvault.ui.notification.BackupNotificationManager import com.stevesoltys.seedvault.ui.notification.BackupNotificationManager
import com.stevesoltys.seedvault.ui.notification.NOTIFICATION_ID_OBSERVER import com.stevesoltys.seedvault.ui.notification.NOTIFICATION_ID_OBSERVER
import kotlinx.coroutines.flow.first
import org.koin.core.component.KoinComponent import org.koin.core.component.KoinComponent
import org.koin.core.component.inject import org.koin.core.component.inject
import java.util.concurrent.TimeUnit import java.util.concurrent.TimeUnit
@ -100,6 +102,7 @@ class AppBackupWorker(
} }
} }
private val backupStateManager: BackupStateManager by inject()
private val backupRequester: BackupRequester by inject() private val backupRequester: BackupRequester by inject()
private val settingsManager: SettingsManager by inject() private val settingsManager: SettingsManager by inject()
private val apkBackupManager: ApkBackupManager by inject() private val apkBackupManager: ApkBackupManager by inject()
@ -109,6 +112,10 @@ class AppBackupWorker(
override suspend fun doWork(): Result { override suspend fun doWork(): Result {
Log.i(TAG, "Start worker $this ($id)") Log.i(TAG, "Start worker $this ($id)")
if (backupStateManager.isCheckOrPruneRunning.first()) {
Log.i(TAG, "isCheckOrPruneRunning was true, so retrying later...")
return Result.retry()
}
try { try {
setForeground(createForegroundInfo()) setForeground(createForegroundInfo())
} catch (e: Exception) { } catch (e: Exception) {

View file

@ -17,10 +17,12 @@ import androidx.work.OneTimeWorkRequestBuilder
import androidx.work.OutOfQuotaPolicy.RUN_AS_NON_EXPEDITED_WORK_REQUEST import androidx.work.OutOfQuotaPolicy.RUN_AS_NON_EXPEDITED_WORK_REQUEST
import androidx.work.WorkManager import androidx.work.WorkManager
import androidx.work.WorkerParameters import androidx.work.WorkerParameters
import com.stevesoltys.seedvault.BackupStateManager
import com.stevesoltys.seedvault.repo.Checker import com.stevesoltys.seedvault.repo.Checker
import com.stevesoltys.seedvault.ui.notification.BackupNotificationManager import com.stevesoltys.seedvault.ui.notification.BackupNotificationManager
import com.stevesoltys.seedvault.ui.notification.NOTIFICATION_ID_CHECKING import com.stevesoltys.seedvault.ui.notification.NOTIFICATION_ID_CHECKING
import io.github.oshai.kotlinlogging.KotlinLogging import io.github.oshai.kotlinlogging.KotlinLogging
import kotlinx.coroutines.flow.first
import org.koin.core.component.KoinComponent import org.koin.core.component.KoinComponent
import org.koin.core.component.inject import org.koin.core.component.inject
import java.time.Duration import java.time.Duration
@ -50,12 +52,16 @@ class AppCheckerWorker(
} }
private val log = KotlinLogging.logger {} private val log = KotlinLogging.logger {}
private val backupStateManager: BackupStateManager by inject()
private val checker: Checker by inject() private val checker: Checker by inject()
private val nm: BackupNotificationManager by inject() private val nm: BackupNotificationManager by inject()
override suspend fun doWork(): Result { override suspend fun doWork(): Result {
// TODO don't let backup/restore happen while we check
log.info { "Start worker $this ($id)" } log.info { "Start worker $this ($id)" }
if (backupStateManager.isBackupRunning.first()) {
Log.i(TAG, "isBackupRunning was true, so retrying later...")
return Result.retry()
}
try { try {
setForeground(createForegroundInfo()) setForeground(createForegroundInfo())
} catch (e: Exception) { } catch (e: Exception) {
@ -64,14 +70,8 @@ class AppCheckerWorker(
val percent = inputData.getInt(PERCENT, -1) val percent = inputData.getInt(PERCENT, -1)
check(percent in 0..100) { "Percent $percent out of bounds." } check(percent in 0..100) { "Percent $percent out of bounds." }
return try {
checker.check(percent) checker.check(percent)
Result.success() return Result.success()
} catch (e: Exception) {
// TODO maybe show error notification
log.error(e) { "Error while checking data: " }
Result.retry()
}
} }
private fun createForegroundInfo() = ForegroundInfo( private fun createForegroundInfo() = ForegroundInfo(

View file

@ -53,7 +53,7 @@
<androidx.preference.Preference <androidx.preference.Preference
app:fragment="com.stevesoltys.seedvault.ui.check.AppCheckFragment" app:fragment="com.stevesoltys.seedvault.ui.check.AppCheckFragment"
app:icon="@drawable/ic_cloud_search" app:icon="@drawable/ic_cloud_search"
app:key="backup_scheduling" app:key="backup_app_check"
app:summary="@string/settings_backup_app_check_summary" app:summary="@string/settings_backup_app_check_summary"
app:title="@string/settings_backup_app_check_title" /> app:title="@string/settings_backup_app_check_title" />