Create AppCheckerWorker and wire it up
This commit is contained in:
parent
060bb425da
commit
85ce587b14
6 changed files with 118 additions and 3 deletions
|
@ -8,6 +8,7 @@ package com.stevesoltys.seedvault.repo
|
||||||
import androidx.annotation.WorkerThread
|
import androidx.annotation.WorkerThread
|
||||||
import com.stevesoltys.seedvault.backend.BackendManager
|
import com.stevesoltys.seedvault.backend.BackendManager
|
||||||
import com.stevesoltys.seedvault.crypto.Crypto
|
import com.stevesoltys.seedvault.crypto.Crypto
|
||||||
|
import kotlinx.coroutines.delay
|
||||||
import org.calyxos.seedvault.core.backends.AppBackupFileType
|
import org.calyxos.seedvault.core.backends.AppBackupFileType
|
||||||
import org.calyxos.seedvault.core.backends.TopLevelFolder
|
import org.calyxos.seedvault.core.backends.TopLevelFolder
|
||||||
|
|
||||||
|
@ -36,4 +37,9 @@ internal class Checker(
|
||||||
return sizeMap.values.sumOf { it.toLong() }
|
return sizeMap.values.sumOf { it.toLong() }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
suspend fun check(percent: Int) {
|
||||||
|
check(percent in 0..100) { "Percent $percent out of bounds." }
|
||||||
|
delay(20_000)
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -58,7 +58,8 @@ class AppCheckFragment : Fragment() {
|
||||||
}
|
}
|
||||||
|
|
||||||
v.requireViewById<Button>(R.id.startButton).setOnClickListener {
|
v.requireViewById<Button>(R.id.startButton).setOnClickListener {
|
||||||
|
viewModel.checkAppBackups(slider.value.toInt())
|
||||||
|
requireActivity().onBackPressedDispatcher.onBackPressed()
|
||||||
}
|
}
|
||||||
return v
|
return v
|
||||||
}
|
}
|
||||||
|
|
|
@ -45,6 +45,7 @@ import com.stevesoltys.seedvault.ui.MutableLiveEvent
|
||||||
import com.stevesoltys.seedvault.ui.RequireProvisioningViewModel
|
import com.stevesoltys.seedvault.ui.RequireProvisioningViewModel
|
||||||
import com.stevesoltys.seedvault.worker.AppBackupWorker
|
import com.stevesoltys.seedvault.worker.AppBackupWorker
|
||||||
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 com.stevesoltys.seedvault.worker.BackupRequester.Companion.requestFilesAndAppBackup
|
import com.stevesoltys.seedvault.worker.BackupRequester.Companion.requestFilesAndAppBackup
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
import kotlinx.coroutines.flow.SharingStarted
|
import kotlinx.coroutines.flow.SharingStarted
|
||||||
|
@ -319,6 +320,10 @@ internal class SettingsViewModel(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun checkAppBackups(percent: Int) {
|
||||||
|
AppCheckerWorker.scheduleNow(app, percent)
|
||||||
|
}
|
||||||
|
|
||||||
fun onLogcatUriReceived(uri: Uri?) = viewModelScope.launch(Dispatchers.IO) {
|
fun onLogcatUriReceived(uri: Uri?) = viewModelScope.launch(Dispatchers.IO) {
|
||||||
if (uri == null) {
|
if (uri == null) {
|
||||||
onLogcatError()
|
onLogcatError()
|
||||||
|
|
|
@ -42,6 +42,7 @@ private const val CHANNEL_ID_ERROR = "NotificationError"
|
||||||
private const val CHANNEL_ID_RESTORE = "NotificationRestore"
|
private const val CHANNEL_ID_RESTORE = "NotificationRestore"
|
||||||
private const val CHANNEL_ID_RESTORE_ERROR = "NotificationRestoreError"
|
private const val CHANNEL_ID_RESTORE_ERROR = "NotificationRestoreError"
|
||||||
private const val CHANNEL_ID_PRUNING = "NotificationPruning"
|
private const val CHANNEL_ID_PRUNING = "NotificationPruning"
|
||||||
|
private const val CHANNEL_ID_CHECKING = "NotificationChecking"
|
||||||
internal const val NOTIFICATION_ID_OBSERVER = 1
|
internal const val NOTIFICATION_ID_OBSERVER = 1
|
||||||
private const val NOTIFICATION_ID_SUCCESS = 2
|
private const val NOTIFICATION_ID_SUCCESS = 2
|
||||||
private const val NOTIFICATION_ID_ERROR = 3
|
private const val NOTIFICATION_ID_ERROR = 3
|
||||||
|
@ -49,7 +50,8 @@ private const val NOTIFICATION_ID_SPACE_ERROR = 4
|
||||||
internal const val NOTIFICATION_ID_RESTORE = 5
|
internal const val NOTIFICATION_ID_RESTORE = 5
|
||||||
private const val NOTIFICATION_ID_RESTORE_ERROR = 6
|
private const val NOTIFICATION_ID_RESTORE_ERROR = 6
|
||||||
internal const val NOTIFICATION_ID_PRUNING = 7
|
internal const val NOTIFICATION_ID_PRUNING = 7
|
||||||
private const val NOTIFICATION_ID_NO_MAIN_KEY_ERROR = 8
|
internal const val NOTIFICATION_ID_CHECKING = 8
|
||||||
|
private const val NOTIFICATION_ID_NO_MAIN_KEY_ERROR = 9
|
||||||
|
|
||||||
private val TAG = BackupNotificationManager::class.java.simpleName
|
private val TAG = BackupNotificationManager::class.java.simpleName
|
||||||
|
|
||||||
|
@ -62,6 +64,7 @@ internal class BackupNotificationManager(private val context: Context) {
|
||||||
createNotificationChannel(getRestoreChannel())
|
createNotificationChannel(getRestoreChannel())
|
||||||
createNotificationChannel(getRestoreErrorChannel())
|
createNotificationChannel(getRestoreErrorChannel())
|
||||||
createNotificationChannel(getPruningChannel())
|
createNotificationChannel(getPruningChannel())
|
||||||
|
createNotificationChannel(getCheckingChannel())
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun getObserverChannel(): NotificationChannel {
|
private fun getObserverChannel(): NotificationChannel {
|
||||||
|
@ -98,6 +101,11 @@ internal class BackupNotificationManager(private val context: Context) {
|
||||||
return NotificationChannel(CHANNEL_ID_PRUNING, title, IMPORTANCE_LOW)
|
return NotificationChannel(CHANNEL_ID_PRUNING, title, IMPORTANCE_LOW)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun getCheckingChannel(): NotificationChannel {
|
||||||
|
val title = context.getString(R.string.notification_checking_channel_title)
|
||||||
|
return NotificationChannel(CHANNEL_ID_CHECKING, title, IMPORTANCE_LOW)
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This should get called for each APK we are backing up.
|
* This should get called for each APK we are backing up.
|
||||||
*/
|
*/
|
||||||
|
@ -314,7 +322,7 @@ internal class BackupNotificationManager(private val context: Context) {
|
||||||
}
|
}
|
||||||
|
|
||||||
fun getPruningNotification(): Notification {
|
fun getPruningNotification(): Notification {
|
||||||
return Builder(context, CHANNEL_ID_OBSERVER).apply {
|
return Builder(context, CHANNEL_ID_PRUNING).apply {
|
||||||
setSmallIcon(R.drawable.ic_auto_delete)
|
setSmallIcon(R.drawable.ic_auto_delete)
|
||||||
setContentTitle(context.getString(R.string.notification_pruning_title))
|
setContentTitle(context.getString(R.string.notification_pruning_title))
|
||||||
setOngoing(true)
|
setOngoing(true)
|
||||||
|
@ -324,6 +332,16 @@ internal class BackupNotificationManager(private val context: Context) {
|
||||||
}.build()
|
}.build()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun getCheckNotification(): Notification {
|
||||||
|
return Builder(context, CHANNEL_ID_CHECKING).apply {
|
||||||
|
setSmallIcon(R.drawable.ic_cloud_search)
|
||||||
|
setContentTitle(context.getString(R.string.notification_checking_title))
|
||||||
|
setOngoing(true)
|
||||||
|
setShowWhen(false)
|
||||||
|
foregroundServiceBehavior = FOREGROUND_SERVICE_IMMEDIATE
|
||||||
|
}.build()
|
||||||
|
}
|
||||||
|
|
||||||
@SuppressLint("RestrictedApi")
|
@SuppressLint("RestrictedApi")
|
||||||
fun onNoMainKeyError() {
|
fun onNoMainKeyError() {
|
||||||
val intent = Intent(context, SettingsActivity::class.java)
|
val intent = Intent(context, SettingsActivity::class.java)
|
||||||
|
|
|
@ -0,0 +1,82 @@
|
||||||
|
/*
|
||||||
|
* SPDX-FileCopyrightText: 2024 The Calyx Institute
|
||||||
|
* SPDX-License-Identifier: Apache-2.0
|
||||||
|
*/
|
||||||
|
|
||||||
|
package com.stevesoltys.seedvault.worker
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
|
import android.content.pm.ServiceInfo.FOREGROUND_SERVICE_TYPE_DATA_SYNC
|
||||||
|
import android.util.Log
|
||||||
|
import androidx.work.BackoffPolicy.EXPONENTIAL
|
||||||
|
import androidx.work.CoroutineWorker
|
||||||
|
import androidx.work.Data
|
||||||
|
import androidx.work.ExistingWorkPolicy.REPLACE
|
||||||
|
import androidx.work.ForegroundInfo
|
||||||
|
import androidx.work.OneTimeWorkRequestBuilder
|
||||||
|
import androidx.work.OutOfQuotaPolicy.RUN_AS_NON_EXPEDITED_WORK_REQUEST
|
||||||
|
import androidx.work.WorkManager
|
||||||
|
import androidx.work.WorkerParameters
|
||||||
|
import com.stevesoltys.seedvault.repo.Checker
|
||||||
|
import com.stevesoltys.seedvault.ui.notification.BackupNotificationManager
|
||||||
|
import com.stevesoltys.seedvault.ui.notification.NOTIFICATION_ID_CHECKING
|
||||||
|
import io.github.oshai.kotlinlogging.KotlinLogging
|
||||||
|
import org.koin.core.component.KoinComponent
|
||||||
|
import org.koin.core.component.inject
|
||||||
|
import java.time.Duration
|
||||||
|
|
||||||
|
class AppCheckerWorker(
|
||||||
|
appContext: Context,
|
||||||
|
workerParams: WorkerParameters,
|
||||||
|
) : CoroutineWorker(appContext, workerParams), KoinComponent {
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
private val TAG = AppCheckerWorker::class.simpleName
|
||||||
|
private const val PERCENT = "percent"
|
||||||
|
internal const val UNIQUE_WORK_NAME = "com.stevesoltys.seedvault.APP_BACKUP_CHECK"
|
||||||
|
|
||||||
|
fun scheduleNow(context: Context, percent: Int) {
|
||||||
|
check(percent in 0..100) { "Percent $percent out of bounds." }
|
||||||
|
val data = Data.Builder().putInt(PERCENT, percent).build()
|
||||||
|
val workRequest = OneTimeWorkRequestBuilder<AppCheckerWorker>()
|
||||||
|
.setExpedited(RUN_AS_NON_EXPEDITED_WORK_REQUEST)
|
||||||
|
.setBackoffCriteria(EXPONENTIAL, Duration.ofSeconds(10))
|
||||||
|
.setInputData(data)
|
||||||
|
.build()
|
||||||
|
val workManager = WorkManager.getInstance(context)
|
||||||
|
Log.i(TAG, "Asking to check $percent% of app backups now...")
|
||||||
|
workManager.enqueueUniqueWork(UNIQUE_WORK_NAME, REPLACE, workRequest)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private val log = KotlinLogging.logger {}
|
||||||
|
private val checker: Checker by inject()
|
||||||
|
private val nm: BackupNotificationManager by inject()
|
||||||
|
|
||||||
|
override suspend fun doWork(): Result {
|
||||||
|
// TODO don't let backup/restore happen while we check
|
||||||
|
log.info { "Start worker $this ($id)" }
|
||||||
|
try {
|
||||||
|
setForeground(createForegroundInfo())
|
||||||
|
} catch (e: Exception) {
|
||||||
|
log.error(e) { "Error while running setForeground: " }
|
||||||
|
}
|
||||||
|
val percent = inputData.getInt(PERCENT, -1)
|
||||||
|
check(percent in 0..100) { "Percent $percent out of bounds." }
|
||||||
|
|
||||||
|
return try {
|
||||||
|
checker.check(percent)
|
||||||
|
Result.success()
|
||||||
|
} catch (e: Exception) {
|
||||||
|
// TODO maybe show error notification
|
||||||
|
log.error(e) { "Error while checking data: " }
|
||||||
|
Result.retry()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun createForegroundInfo() = ForegroundInfo(
|
||||||
|
NOTIFICATION_ID_CHECKING,
|
||||||
|
nm.getCheckNotification(),
|
||||||
|
FOREGROUND_SERVICE_TYPE_DATA_SYNC,
|
||||||
|
)
|
||||||
|
}
|
|
@ -187,6 +187,9 @@
|
||||||
<string name="notification_pruning_channel_title">Removing old backups notification</string>
|
<string name="notification_pruning_channel_title">Removing old backups notification</string>
|
||||||
<string name="notification_pruning_title">Removing old backups…</string>
|
<string name="notification_pruning_title">Removing old backups…</string>
|
||||||
|
|
||||||
|
<string name="notification_checking_channel_title">App backup integrity check</string>
|
||||||
|
<string name="notification_checking_title">Checking app backups…</string>
|
||||||
|
|
||||||
<!-- App Backup and Restore State -->
|
<!-- App Backup and Restore State -->
|
||||||
|
|
||||||
<string name="backup_section_system">System data</string>
|
<string name="backup_section_system">System data</string>
|
||||||
|
|
Loading…
Reference in a new issue