浏览代码

Convert BackupRestoreService to a WorkManager job

Co-authored-by: Jays2Kings <[email protected]>
arkon 2 年之前
父节点
当前提交
cdc160afc2

+ 0 - 4
app/src/main/AndroidManifest.xml

@@ -209,10 +209,6 @@
             android:name=".data.updater.AppUpdateService"
             android:exported="false" />
 
-        <service
-            android:name=".data.backup.BackupRestoreService"
-            android:exported="false" />
-
         <service android:name=".extension.util.ExtensionInstallService"
             android:exported="false" />
 

+ 8 - 8
app/src/main/java/eu/kanade/presentation/more/settings/screen/SettingsBackupScreen.kt

@@ -41,9 +41,9 @@ import eu.kanade.presentation.more.settings.Preference
 import eu.kanade.presentation.util.collectAsState
 import eu.kanade.tachiyomi.R
 import eu.kanade.tachiyomi.data.backup.BackupConst
-import eu.kanade.tachiyomi.data.backup.BackupCreatorJob
+import eu.kanade.tachiyomi.data.backup.BackupCreateJob
 import eu.kanade.tachiyomi.data.backup.BackupFileValidator
-import eu.kanade.tachiyomi.data.backup.BackupRestoreService
+import eu.kanade.tachiyomi.data.backup.BackupRestoreJob
 import eu.kanade.tachiyomi.data.backup.models.Backup
 import eu.kanade.tachiyomi.util.storage.DiskUtil
 import eu.kanade.tachiyomi.util.system.DeviceUtil
@@ -93,7 +93,7 @@ object SettingsBackupScreen : SearchableSettings {
                     Intent.FLAG_GRANT_READ_URI_PERMISSION or
                         Intent.FLAG_GRANT_WRITE_URI_PERMISSION,
                 )
-                BackupCreatorJob.startNow(context, it, flag)
+                BackupCreateJob.startNow(context, it, flag)
             }
             flag = 0
         }
@@ -119,7 +119,7 @@ object SettingsBackupScreen : SearchableSettings {
             subtitle = stringResource(R.string.pref_create_backup_summ),
             onClick = {
                 scope.launch {
-                    if (!BackupCreatorJob.isManualJobRunning(context)) {
+                    if (!BackupCreateJob.isManualJobRunning(context)) {
                         if (DeviceUtil.isMiui && DeviceUtil.isMiuiOptimizationDisabled()) {
                             context.toast(R.string.restore_miui_warning, Toast.LENGTH_LONG)
                         }
@@ -271,7 +271,7 @@ object SettingsBackupScreen : SearchableSettings {
                         confirmButton = {
                             TextButton(
                                 onClick = {
-                                    BackupRestoreService.start(context, err.uri)
+                                    BackupRestoreJob.start(context, err.uri)
                                     onDismissRequest()
                                 },
                             ) {
@@ -301,7 +301,7 @@ object SettingsBackupScreen : SearchableSettings {
                 }
 
                 if (results.missingSources.isEmpty() && results.missingTrackers.isEmpty()) {
-                    BackupRestoreService.start(context, it)
+                    BackupRestoreJob.start(context, it)
                     return@rememberLauncherForActivityResult
                 }
 
@@ -313,7 +313,7 @@ object SettingsBackupScreen : SearchableSettings {
             title = stringResource(R.string.pref_restore_backup),
             subtitle = stringResource(R.string.pref_restore_backup_summ),
             onClick = {
-                if (!BackupRestoreService.isRunning(context)) {
+                if (!BackupRestoreJob.isRunning(context)) {
                     if (DeviceUtil.isMiui && DeviceUtil.isMiuiOptimizationDisabled()) {
                         context.toast(R.string.restore_miui_warning, Toast.LENGTH_LONG)
                     }
@@ -364,7 +364,7 @@ object SettingsBackupScreen : SearchableSettings {
                         168 to stringResource(R.string.update_weekly),
                     ),
                     onValueChanged = {
-                        BackupCreatorJob.setupTask(context, it)
+                        BackupCreateJob.setupTask(context, it)
                         true
                     },
                 ),

+ 3 - 3
app/src/main/java/eu/kanade/presentation/util/Resources.kt

@@ -11,11 +11,11 @@ import androidx.core.content.ContextCompat
 import androidx.core.graphics.drawable.toBitmap
 
 /**
- * Create a BitmapPainter from an drawable resource.
- *
- * > Only use this if [androidx.compose.ui.res.painterResource] doesn't work.
+ * Create a BitmapPainter from a drawable resource.
+ * Use this only if [androidx.compose.ui.res.painterResource] doesn't work.
  *
  * @param id the resource identifier
+ *
  * @return the bitmap associated with the resource
  */
 @Composable

+ 5 - 5
app/src/main/java/eu/kanade/tachiyomi/Migrations.kt

@@ -8,7 +8,7 @@ import eu.kanade.domain.base.BasePreferences
 import eu.kanade.domain.source.service.SourcePreferences
 import eu.kanade.domain.ui.UiPreferences
 import eu.kanade.tachiyomi.core.security.SecurityPreferences
-import eu.kanade.tachiyomi.data.backup.BackupCreatorJob
+import eu.kanade.tachiyomi.data.backup.BackupCreateJob
 import eu.kanade.tachiyomi.data.library.LibraryUpdateJob
 import eu.kanade.tachiyomi.data.preference.PreferenceValues
 import eu.kanade.tachiyomi.data.track.TrackManager
@@ -57,7 +57,7 @@ object Migrations {
 
             // Always set up background tasks to ensure they're running
             LibraryUpdateJob.setupTask(context)
-            BackupCreatorJob.setupTask(context)
+            BackupCreateJob.setupTask(context)
 
             // Fresh install
             if (oldVersion == 0) {
@@ -99,7 +99,7 @@ object Migrations {
             if (oldVersion < 43) {
                 // Restore jobs after migrating from Evernote's job scheduler to WorkManager.
                 LibraryUpdateJob.setupTask(context)
-                BackupCreatorJob.setupTask(context)
+                BackupCreateJob.setupTask(context)
             }
             if (oldVersion < 44) {
                 // Reset sorting preference if using removed sort by source
@@ -249,7 +249,7 @@ object Migrations {
                 }
             }
             if (oldVersion < 76) {
-                BackupCreatorJob.setupTask(context)
+                BackupCreateJob.setupTask(context)
             }
             if (oldVersion < 77) {
                 val oldReaderTap = prefs.getBoolean("reader_tap", false)
@@ -284,7 +284,7 @@ object Migrations {
                 }
                 if (backupPreferences.backupInterval().get() == 0) {
                     backupPreferences.backupInterval().set(12)
-                    BackupCreatorJob.setupTask(context)
+                    BackupCreateJob.setupTask(context)
                 }
             }
             if (oldVersion < 85) {

+ 3 - 4
app/src/main/java/eu/kanade/tachiyomi/data/backup/BackupCreatorJob.kt → app/src/main/java/eu/kanade/tachiyomi/data/backup/BackupCreateJob.kt

@@ -23,7 +23,7 @@ import uy.kohesive.injekt.Injekt
 import uy.kohesive.injekt.api.get
 import java.util.concurrent.TimeUnit
 
-class BackupCreatorJob(private val context: Context, workerParams: WorkerParameters) :
+class BackupCreateJob(private val context: Context, workerParams: WorkerParameters) :
     CoroutineWorker(context, workerParams) {
 
     private val notifier = BackupNotifier(context)
@@ -41,7 +41,6 @@ class BackupCreatorJob(private val context: Context, workerParams: WorkerParamet
             logcat(LogPriority.ERROR, e) { "Not allowed to run on foreground service" }
         }
 
-        context.notificationManager.notify(Notifications.ID_BACKUP_PROGRESS, notifier.showBackupProgress().build())
         return try {
             val location = BackupManager(context).createBackup(uri, flags, isAutoBackup)
             if (!isAutoBackup) notifier.showBackupComplete(UniFile.fromUri(context, location.toUri()))
@@ -70,7 +69,7 @@ class BackupCreatorJob(private val context: Context, workerParams: WorkerParamet
             val interval = prefInterval ?: backupPreferences.backupInterval().get()
             val workManager = WorkManager.getInstance(context)
             if (interval > 0) {
-                val request = PeriodicWorkRequestBuilder<BackupCreatorJob>(
+                val request = PeriodicWorkRequestBuilder<BackupCreateJob>(
                     interval.toLong(),
                     TimeUnit.HOURS,
                     10,
@@ -92,7 +91,7 @@ class BackupCreatorJob(private val context: Context, workerParams: WorkerParamet
                 LOCATION_URI_KEY to uri.toString(),
                 BACKUP_FLAGS_KEY to flags,
             )
-            val request = OneTimeWorkRequestBuilder<BackupCreatorJob>()
+            val request = OneTimeWorkRequestBuilder<BackupCreateJob>()
                 .addTag(TAG_MANUAL)
                 .setInputData(inputData)
                 .build()

+ 1 - 7
app/src/main/java/eu/kanade/tachiyomi/data/backup/BackupNotifier.kt

@@ -67,9 +67,7 @@ class BackupNotifier(private val context: Context) {
             setContentTitle(context.getString(R.string.backup_created))
             setContentText(unifile.filePath ?: unifile.name)
 
-            // Clear old actions if they exist
             clearActions()
-
             addAction(
                 R.drawable.ic_share_24dp,
                 context.getString(R.string.action_share),
@@ -91,12 +89,10 @@ class BackupNotifier(private val context: Context) {
             setProgress(maxAmount, progress, false)
             setOnlyAlertOnce(true)
 
-            // Clear old actions if they exist
             clearActions()
-
             addAction(
                 R.drawable.ic_close_24dp,
-                context.getString(R.string.action_stop),
+                context.getString(R.string.action_cancel),
                 NotificationReceiver.cancelRestorePendingBroadcast(context, Notifications.ID_RESTORE_PROGRESS),
             )
         }
@@ -132,9 +128,7 @@ class BackupNotifier(private val context: Context) {
             setContentTitle(context.getString(R.string.restore_completed))
             setContentText(context.resources.getQuantityString(R.plurals.restore_completed_message, errorCount, timeString, errorCount))
 
-            // Clear old actions if they exist
             clearActions()
-
             if (errorCount > 0 && !path.isNullOrEmpty() && !file.isNullOrEmpty()) {
                 val destFile = File(path, file)
                 val uri = destFile.getUriCompat(context)

+ 87 - 0
app/src/main/java/eu/kanade/tachiyomi/data/backup/BackupRestoreJob.kt

@@ -0,0 +1,87 @@
+package eu.kanade.tachiyomi.data.backup
+
+import android.content.Context
+import android.net.Uri
+import androidx.core.net.toUri
+import androidx.work.CoroutineWorker
+import androidx.work.ExistingWorkPolicy
+import androidx.work.ForegroundInfo
+import androidx.work.OneTimeWorkRequestBuilder
+import androidx.work.WorkInfo
+import androidx.work.WorkManager
+import androidx.work.WorkerParameters
+import androidx.work.workDataOf
+import eu.kanade.tachiyomi.R
+import eu.kanade.tachiyomi.data.notification.Notifications
+import eu.kanade.tachiyomi.util.system.notificationManager
+import kotlinx.coroutines.CancellationException
+import logcat.LogPriority
+import tachiyomi.core.util.system.logcat
+
+class BackupRestoreJob(private val context: Context, workerParams: WorkerParameters) :
+    CoroutineWorker(context, workerParams) {
+
+    private val notifier = BackupNotifier(context)
+
+    override suspend fun doWork(): Result {
+        val uri = inputData.getString(LOCATION_URI_KEY)?.toUri()
+            ?: return Result.failure()
+
+        try {
+            setForeground(getForegroundInfo())
+        } catch (e: IllegalStateException) {
+            logcat(LogPriority.ERROR, e) { "Not allowed to run on foreground service" }
+        }
+
+        return try {
+            val restorer = BackupRestorer(context, notifier)
+            restorer.restoreBackup(uri)
+            Result.success()
+        } catch (e: Exception) {
+            if (e is CancellationException) {
+                notifier.showRestoreError(context.getString(R.string.restoring_backup_canceled))
+                Result.success()
+            } else {
+                logcat(LogPriority.ERROR, e)
+                notifier.showRestoreError(e.message)
+                Result.failure()
+            }
+        } finally {
+            context.notificationManager.cancel(Notifications.ID_RESTORE_PROGRESS)
+        }
+    }
+
+    override suspend fun getForegroundInfo(): ForegroundInfo {
+        return ForegroundInfo(
+            Notifications.ID_RESTORE_PROGRESS,
+            notifier.showRestoreProgress().build(),
+        )
+    }
+
+    companion object {
+        fun isRunning(context: Context): Boolean {
+            val list = WorkManager.getInstance(context).getWorkInfosByTag(TAG).get()
+            return list.find { it.state == WorkInfo.State.RUNNING } != null
+        }
+
+        fun start(context: Context, uri: Uri) {
+            val inputData = workDataOf(
+                LOCATION_URI_KEY to uri.toString(),
+            )
+            val request = OneTimeWorkRequestBuilder<BackupRestoreJob>()
+                .addTag(TAG)
+                .setInputData(inputData)
+                .build()
+            WorkManager.getInstance(context)
+                .enqueueUniqueWork(TAG, ExistingWorkPolicy.KEEP, request)
+        }
+
+        fun stop(context: Context) {
+            WorkManager.getInstance(context).cancelUniqueWork(TAG)
+        }
+    }
+}
+
+private const val TAG = "BackupRestore"
+
+private const val LOCATION_URI_KEY = "location_uri" // String

+ 0 - 141
app/src/main/java/eu/kanade/tachiyomi/data/backup/BackupRestoreService.kt

@@ -1,141 +0,0 @@
-package eu.kanade.tachiyomi.data.backup
-
-import android.app.Service
-import android.content.Context
-import android.content.Intent
-import android.net.Uri
-import android.os.IBinder
-import android.os.PowerManager
-import androidx.core.content.ContextCompat
-import eu.kanade.tachiyomi.R
-import eu.kanade.tachiyomi.data.notification.Notifications
-import eu.kanade.tachiyomi.util.system.acquireWakeLock
-import eu.kanade.tachiyomi.util.system.getParcelableExtraCompat
-import eu.kanade.tachiyomi.util.system.isServiceRunning
-import kotlinx.coroutines.CoroutineExceptionHandler
-import kotlinx.coroutines.CoroutineScope
-import kotlinx.coroutines.Dispatchers
-import kotlinx.coroutines.SupervisorJob
-import kotlinx.coroutines.cancel
-import kotlinx.coroutines.launch
-import logcat.LogPriority
-import tachiyomi.core.util.system.logcat
-
-/**
- * Restores backup.
- */
-class BackupRestoreService : Service() {
-
-    companion object {
-
-        /**
-         * Returns the status of the service.
-         *
-         * @param context the application context.
-         * @return true if the service is running, false otherwise.
-         */
-        fun isRunning(context: Context): Boolean =
-            context.isServiceRunning(BackupRestoreService::class.java)
-
-        /**
-         * Starts a service to restore a backup from Json
-         *
-         * @param context context of application
-         * @param uri path of Uri
-         */
-        fun start(context: Context, uri: Uri) {
-            if (isRunning(context)) return
-
-            val intent = Intent(context, BackupRestoreService::class.java).apply {
-                putExtra(BackupConst.EXTRA_URI, uri)
-            }
-            ContextCompat.startForegroundService(context, intent)
-        }
-
-        /**
-         * Stops the service.
-         *
-         * @param context the application context.
-         */
-        fun stop(context: Context) {
-            context.stopService(Intent(context, BackupRestoreService::class.java))
-
-            BackupNotifier(context).showRestoreError(context.getString(R.string.restoring_backup_canceled))
-        }
-    }
-
-    /**
-     * Wake lock that will be held until the service is destroyed.
-     */
-    private lateinit var wakeLock: PowerManager.WakeLock
-
-    private lateinit var scope: CoroutineScope
-    private var restorer: BackupRestorer? = null
-    private lateinit var notifier: BackupNotifier
-
-    override fun onCreate() {
-        scope = CoroutineScope(SupervisorJob() + Dispatchers.IO)
-        notifier = BackupNotifier(this)
-        wakeLock = acquireWakeLock(javaClass.name)
-
-        startForeground(Notifications.ID_RESTORE_PROGRESS, notifier.showRestoreProgress().build())
-    }
-
-    override fun stopService(name: Intent?): Boolean {
-        destroyJob()
-        return super.stopService(name)
-    }
-
-    override fun onDestroy() {
-        destroyJob()
-    }
-
-    private fun destroyJob() {
-        restorer?.job?.cancel()
-        scope.cancel()
-        if (wakeLock.isHeld) {
-            wakeLock.release()
-        }
-    }
-
-    /**
-     * This method needs to be implemented, but it's not used/needed.
-     */
-    override fun onBind(intent: Intent): IBinder? = null
-
-    /**
-     * Method called when the service receives an intent.
-     *
-     * @param intent the start intent from.
-     * @param flags the flags of the command.
-     * @param startId the start id of this command.
-     * @return the start value of the command.
-     */
-    override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
-        val uri = intent?.getParcelableExtraCompat<Uri>(BackupConst.EXTRA_URI) ?: return START_NOT_STICKY
-
-        // Cancel any previous job if needed.
-        restorer?.job?.cancel()
-
-        restorer = BackupRestorer(this, notifier)
-
-        val handler = CoroutineExceptionHandler { _, exception ->
-            logcat(LogPriority.ERROR, exception)
-            restorer?.writeErrorLog()
-
-            notifier.showRestoreError(exception.message)
-            stopSelf(startId)
-        }
-        val job = scope.launch(handler) {
-            if (restorer?.restoreBackup(uri) == false) {
-                notifier.showRestoreError(getString(R.string.restoring_backup_canceled))
-            }
-        }
-        job.invokeOnCompletion {
-            stopSelf(startId)
-        }
-        restorer?.job = job
-
-        return START_NOT_STICKY
-    }
-}

+ 13 - 15
app/src/main/java/eu/kanade/tachiyomi/data/backup/BackupRestorer.kt

@@ -9,8 +9,8 @@ import eu.kanade.tachiyomi.data.backup.models.BackupManga
 import eu.kanade.tachiyomi.data.backup.models.BackupSource
 import eu.kanade.tachiyomi.util.BackupUtil
 import eu.kanade.tachiyomi.util.system.createFileInCacheDir
-import kotlinx.coroutines.Job
-import okio.source
+import kotlinx.coroutines.coroutineScope
+import kotlinx.coroutines.isActive
 import tachiyomi.domain.chapter.model.Chapter
 import tachiyomi.domain.manga.model.Manga
 import tachiyomi.domain.track.model.Track
@@ -24,8 +24,6 @@ class BackupRestorer(
     private val notifier: BackupNotifier,
 ) {
 
-    var job: Job? = null
-
     private var backupManager = BackupManager(context)
 
     private var restoreAmount = 0
@@ -56,7 +54,7 @@ class BackupRestorer(
         return true
     }
 
-    fun writeErrorLog(): File {
+    private fun writeErrorLog(): File {
         try {
             if (errors.isNotEmpty()) {
                 val file = context.createFileInCacheDir("tachiyomi_restore.txt")
@@ -90,18 +88,18 @@ class BackupRestorer(
         val backupMaps = backup.backupBrokenSources.map { BackupSource(it.name, it.sourceId) } + backup.backupSources
         sourceMapping = backupMaps.associate { it.sourceId to it.name }
 
-        // Restore individual manga
-        backup.backupManga.forEach {
-            if (job?.isActive != true) {
-                return false
-            }
+        return coroutineScope {
+            // Restore individual manga
+            backup.backupManga.forEach {
+                if (!isActive) {
+                    return@coroutineScope false
+                }
 
-            restoreManga(it, backup.backupCategories)
+                restoreManga(it, backup.backupCategories)
+            }
+            // TODO: optionally trigger online library + tracker update
+            true
         }
-
-        // TODO: optionally trigger online library + tracker update
-
-        return true
     }
 
     private suspend fun restoreCategories(backupCategories: List<BackupCategory>) {

+ 4 - 10
app/src/main/java/eu/kanade/tachiyomi/data/notification/NotificationReceiver.kt

@@ -6,10 +6,9 @@ import android.content.Context
 import android.content.Intent
 import android.net.Uri
 import android.os.Build
-import androidx.core.content.ContextCompat
 import androidx.core.net.toUri
 import eu.kanade.tachiyomi.R
-import eu.kanade.tachiyomi.data.backup.BackupRestoreService
+import eu.kanade.tachiyomi.data.backup.BackupRestoreJob
 import eu.kanade.tachiyomi.data.download.DownloadManager
 import eu.kanade.tachiyomi.data.library.LibraryUpdateJob
 import eu.kanade.tachiyomi.data.updater.AppUpdateService
@@ -82,10 +81,7 @@ class NotificationReceiver : BroadcastReceiver() {
                     "application/x-protobuf+gzip",
                     intent.getIntExtra(EXTRA_NOTIFICATION_ID, -1),
                 )
-            ACTION_CANCEL_RESTORE -> cancelRestore(
-                context,
-                intent.getIntExtra(EXTRA_NOTIFICATION_ID, -1),
-            )
+            ACTION_CANCEL_RESTORE -> cancelRestore(context)
             // Cancel library update and dismiss notification
             ACTION_CANCEL_LIBRARY_UPDATE -> cancelLibraryUpdate(context)
             // Cancel downloading app update
@@ -206,11 +202,9 @@ class NotificationReceiver : BroadcastReceiver() {
      * Method called when user wants to stop a backup restore job.
      *
      * @param context context of application
-     * @param notificationId id of notification
      */
-    private fun cancelRestore(context: Context, notificationId: Int) {
-        BackupRestoreService.stop(context)
-        ContextCompat.getMainExecutor(context).execute { dismissNotification(context, notificationId) }
+    private fun cancelRestore(context: Context) {
+        BackupRestoreJob.stop(context)
     }
 
     /**

+ 0 - 1
i18n/src/main/res/values/strings.xml

@@ -86,7 +86,6 @@
     <string name="delete_category">Delete category</string>
     <string name="action_edit_cover">Edit cover</string>
     <string name="action_view_chapters">View chapters</string>
-    <string name="action_stop">Stop</string>
     <string name="action_pause">Pause</string>
     <string name="action_previous_chapter">Previous chapter</string>
     <string name="action_next_chapter">Next chapter</string>