/* * 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.CoroutineWorker 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.BackupStateManager import com.stevesoltys.seedvault.R import com.stevesoltys.seedvault.backend.BackendManager import com.stevesoltys.seedvault.repo.AppBackupManager import com.stevesoltys.seedvault.ui.notification.BackupNotificationManager 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.inject /** * Backup worker that does the preliminaries for app backup * and then hands over to the system for the actual backup. */ class AppBackupWorker( appContext: Context, workerParams: WorkerParameters, ) : CoroutineWorker(appContext, workerParams), KoinComponent { companion object { private val TAG = AppBackupWorker::class.simpleName internal const val UNIQUE_WORK_NAME = "com.stevesoltys.seedvault.APP_BACKUP" private const val MIN_FREE_SPACE = 1024 * 1024 * 500 // 500 MB fun scheduleNow(context: Context) { val workRequest = OneTimeWorkRequestBuilder() .setExpedited(RUN_AS_NON_EXPEDITED_WORK_REQUEST) .build() val workManager = WorkManager.getInstance(context) Log.i(TAG, "Asking to do app backup now...") workManager.enqueueUniqueWork(UNIQUE_WORK_NAME, REPLACE, workRequest) } } private val backupStateManager: BackupStateManager by inject() private val backupRequester: BackupRequester by inject() private val apkBackupManager: ApkBackupManager by inject() private val appBackupManager: AppBackupManager by inject() private val backendManager: BackendManager by inject() private val nm: BackupNotificationManager by inject() override suspend fun doWork(): Result { Log.i(TAG, "Start worker $this ($id)") if (backupStateManager.isCheckOrPruneRunning.first()) { Log.i(TAG, "isCheckOrPruneRunning was true, so retrying later...") return Result.retry() } try { setForeground(createForegroundInfo()) } catch (e: Exception) { Log.e(TAG, "Error while running setForeground: ", e) } return if (isStopped || !backendManager.canDoBackupNow()) { Result.retry() } else { val freeSpace = backendManager.getFreeSpace() Log.i(TAG, "freeSpace: $freeSpace") if (freeSpace != null && freeSpace < MIN_FREE_SPACE) { nm.onInsufficientSpaceError() return Result.failure() } val result = doBackup() // show error notification if backup wasn't successful if (result != Result.success()) { nm.onBackupError(meteredNetwork = !backendManager.canDoBackupNow()) } return result } } private suspend fun doBackup(): Result { if (!isStopped && backendManager.canDoBackupNow()) { Log.i(TAG, "Initializing backup info...") try { appBackupManager.beforeBackup() } catch (e: Exception) { Log.e(TAG, "Error during 'beforeBackup': ", e) return Result.retry() } } else { Log.i(TAG, "Stopping, because s:$isStopped c:${backendManager.canDoBackupNow()}") } try { if (!isStopped && backendManager.canDoBackupNow()) { Log.i(TAG, "Starting APK backup...") apkBackupManager.backup() } else { Log.i(TAG, "Stopping, because s:$isStopped c:${backendManager.canDoBackupNow()}") } } catch (e: Exception) { Log.e(TAG, "Error backing up APKs: ", e) return Result.retry() } Log.i(TAG, "Requesting app data backup... (stopped: $isStopped)") if (!isStopped && backupRequester.isBackupEnabled && backendManager.canDoBackupNow()) { Log.i(TAG, "Backup is enabled, request backup...") if (!backupRequester.requestBackup()) return Result.retry() } else { Log.i(TAG, "Stopping, because s:$isStopped c:${backendManager.canDoBackupNow()}") } return Result.success() } private fun createForegroundInfo() = ForegroundInfo( NOTIFICATION_ID_OBSERVER, nm.getBackupNotification(applicationContext.getString(R.string.notification_init_text)), FOREGROUND_SERVICE_TYPE_DATA_SYNC, ) }