Explorar o código

Refactor backup service

arkon %!s(int64=5) %!d(string=hai) anos
pai
achega
d19d787f6e

+ 3 - 6
app/src/main/java/eu/kanade/tachiyomi/data/backup/BackupConst.kt

@@ -4,10 +4,7 @@ import eu.kanade.tachiyomi.BuildConfig.APPLICATION_ID as ID
 
 object BackupConst {
 
-    const val INTENT_FILTER = "SettingsBackupFragment"
-    const val ACTION_BACKUP_COMPLETED = "$ID.$INTENT_FILTER.ACTION_BACKUP_COMPLETED"
-    const val ACTION_BACKUP_ERROR = "$ID.$INTENT_FILTER.ACTION_BACKUP_ERROR"
-    const val ACTION = "$ID.$INTENT_FILTER.ACTION"
-    const val EXTRA_ERROR_MESSAGE = "$ID.$INTENT_FILTER.EXTRA_ERROR_MESSAGE"
-    const val EXTRA_URI = "$ID.$INTENT_FILTER.EXTRA_URI"
+    private const val NAME = "BackupRestoreServices"
+    const val EXTRA_URI = "$ID.$NAME.EXTRA_URI"
+    const val EXTRA_FLAGS = "$ID.$NAME.EXTRA_FLAGS"
 }

+ 85 - 25
app/src/main/java/eu/kanade/tachiyomi/data/backup/BackupCreateService.kt

@@ -1,25 +1,22 @@
 package eu.kanade.tachiyomi.data.backup
 
-import android.app.IntentService
+import android.app.Service
 import android.content.Context
 import android.content.Intent
 import android.net.Uri
-import com.google.gson.JsonArray
-import eu.kanade.tachiyomi.BuildConfig.APPLICATION_ID as ID
-import eu.kanade.tachiyomi.data.database.models.Manga
+import android.os.Build
+import android.os.IBinder
+import android.os.PowerManager
+import com.hippo.unifile.UniFile
+import eu.kanade.tachiyomi.data.notification.Notifications
+import eu.kanade.tachiyomi.util.system.isServiceRunning
 
 /**
- * [IntentService] used to backup [Manga] information to [JsonArray]
+ * Service for backing up library information to a JSON file.
  */
-class BackupCreateService : IntentService(NAME) {
+class BackupCreateService : Service() {
 
     companion object {
-        // Name of class
-        private const val NAME = "BackupCreateService"
-
-        // Options for backup
-        private const val EXTRA_FLAGS = "$ID.$NAME.EXTRA_FLAGS"
-
         // Filter options
         internal const val BACKUP_CATEGORY = 0x1
         internal const val BACKUP_CATEGORY_MASK = 0x1
@@ -31,6 +28,15 @@ class BackupCreateService : IntentService(NAME) {
         internal const val BACKUP_TRACK_MASK = 0x8
         internal const val BACKUP_ALL = 0xF
 
+        /**
+         * 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(BackupCreateService::class.java)
+
         /**
          * Make a backup from library
          *
@@ -38,24 +44,78 @@ class BackupCreateService : IntentService(NAME) {
          * @param uri path of Uri
          * @param flags determines what to backup
          */
-        fun makeBackup(context: Context, uri: Uri, flags: Int) {
-            val intent = Intent(context, BackupCreateService::class.java).apply {
-                putExtra(BackupConst.EXTRA_URI, uri)
-                putExtra(EXTRA_FLAGS, flags)
+        fun start(context: Context, uri: Uri, flags: Int) {
+            if (!isRunning(context)) {
+                val intent = Intent(context, BackupCreateService::class.java).apply {
+                    putExtra(BackupConst.EXTRA_URI, uri)
+                    putExtra(BackupConst.EXTRA_FLAGS, flags)
+                }
+                if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O) {
+                    context.startService(intent)
+                } else {
+                    context.startForegroundService(intent)
+                }
             }
-            context.startService(intent)
         }
     }
 
-    private val backupManager by lazy { BackupManager(this) }
+    /**
+     * Wake lock that will be held until the service is destroyed.
+     */
+    private lateinit var wakeLock: PowerManager.WakeLock
+
+    private lateinit var backupManager: BackupManager
+    private lateinit var notifier: BackupNotifier
+
+    override fun onCreate() {
+        super.onCreate()
+        notifier = BackupNotifier(this)
+
+        startForeground(Notifications.ID_BACKUP_PROGRESS, notifier.showBackupProgress().build())
+
+        wakeLock = (getSystemService(Context.POWER_SERVICE) as PowerManager).newWakeLock(
+            PowerManager.PARTIAL_WAKE_LOCK, "${javaClass.name}:WakeLock"
+        )
+        wakeLock.acquire()
+    }
+
+    override fun stopService(name: Intent?): Boolean {
+        destroyJob()
+        return super.stopService(name)
+    }
+
+    override fun onDestroy() {
+        destroyJob()
+        super.onDestroy()
+    }
+
+    private fun destroyJob() {
+        if (wakeLock.isHeld) {
+            wakeLock.release()
+        }
+    }
+
+    /**
+     * This method needs to be implemented, but it's not used/needed.
+     */
+    override fun onBind(intent: Intent): IBinder? = null
+
+    override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
+        if (intent == null) return START_NOT_STICKY
 
-    override fun onHandleIntent(intent: Intent?) {
-        if (intent == null) return
+        try {
+            val uri = intent.getParcelableExtra<Uri>(BackupConst.EXTRA_URI)
+            val backupFlags = intent.getIntExtra(BackupConst.EXTRA_FLAGS, 0)
+            backupManager = BackupManager(this)
+
+            val backupFileUri = Uri.parse(backupManager.createBackup(uri, backupFlags, false))
+            val unifile = UniFile.fromUri(this, backupFileUri)
+            notifier.showBackupComplete(unifile)
+        } catch (e: Exception) {
+            notifier.showBackupError(e.message)
+        }
 
-        // Get values
-        val uri = intent.getParcelableExtra<Uri>(BackupConst.EXTRA_URI)
-        val flags = intent.getIntExtra(EXTRA_FLAGS, 0)
-        // Create backup
-        backupManager.createBackup(uri, flags, false)
+        stopSelf(startId)
+        return START_NOT_STICKY
     }
 }

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

@@ -20,9 +20,10 @@ class BackupCreatorJob(private val context: Context, workerParams: WorkerParamet
         val backupManager = BackupManager(context)
         val uri = Uri.parse(preferences.backupsDirectory().get())
         val flags = BackupCreateService.BACKUP_ALL
-        return if (backupManager.createBackup(uri, flags, true)) {
+        return try {
+            backupManager.createBackup(uri, flags, true)
             Result.success()
-        } else {
+        } catch (e: Exception) {
             Result.failure()
         }
     }

+ 5 - 19
app/src/main/java/eu/kanade/tachiyomi/data/backup/BackupManager.kt

@@ -1,7 +1,6 @@
 package eu.kanade.tachiyomi.data.backup
 
 import android.content.Context
-import android.content.Intent
 import android.net.Uri
 import com.github.salomonbrys.kotson.fromJson
 import com.github.salomonbrys.kotson.registerTypeAdapter
@@ -49,7 +48,6 @@ import eu.kanade.tachiyomi.data.track.TrackManager
 import eu.kanade.tachiyomi.source.Source
 import eu.kanade.tachiyomi.source.SourceManager
 import eu.kanade.tachiyomi.util.chapter.syncChaptersWithSource
-import eu.kanade.tachiyomi.util.system.sendLocalBroadcast
 import kotlin.math.max
 import rx.Observable
 import timber.log.Timber
@@ -102,7 +100,7 @@ class BackupManager(val context: Context, version: Int = CURRENT_VERSION) {
      * @param uri path of Uri
      * @param isJob backup called from job
      */
-    fun createBackup(uri: Uri, flags: Int, isJob: Boolean): Boolean {
+    fun createBackup(uri: Uri, flags: Int, isJob: Boolean): String? {
         // Create root object
         val root = JsonObject()
 
@@ -155,6 +153,8 @@ class BackupManager(val context: Context, version: Int = CURRENT_VERSION) {
                 newFile.openOutputStream().bufferedWriter().use {
                     parser.toJson(root, it)
                 }
+
+                return newFile.uri.toString()
             } else {
                 val file = UniFile.fromUri(context, uri)
                     ?: throw Exception("Couldn't create backup file")
@@ -162,25 +162,11 @@ class BackupManager(val context: Context, version: Int = CURRENT_VERSION) {
                     parser.toJson(root, it)
                 }
 
-                // Show completed dialog
-                val intent = Intent(BackupConst.INTENT_FILTER).apply {
-                    putExtra(BackupConst.ACTION, BackupConst.ACTION_BACKUP_COMPLETED)
-                    putExtra(BackupConst.EXTRA_URI, file.uri.toString())
-                }
-                context.sendLocalBroadcast(intent)
+                return file.uri.toString()
             }
-            return true
         } catch (e: Exception) {
             Timber.e(e)
-            if (!isJob) {
-                // Show error dialog
-                val intent = Intent(BackupConst.INTENT_FILTER).apply {
-                    putExtra(BackupConst.ACTION, BackupConst.ACTION_BACKUP_ERROR)
-                    putExtra(BackupConst.EXTRA_ERROR_MESSAGE, e.message)
-                }
-                context.sendLocalBroadcast(intent)
-            }
-            return false
+            throw e
         }
     }
 

+ 8 - 7
app/src/main/java/eu/kanade/tachiyomi/ui/setting/backup/BackupNotifier.kt → app/src/main/java/eu/kanade/tachiyomi/data/backup/BackupNotifier.kt

@@ -1,4 +1,4 @@
-package eu.kanade.tachiyomi.ui.setting.backup
+package eu.kanade.tachiyomi.data.backup
 
 import android.content.Context
 import android.graphics.BitmapFactory
@@ -25,16 +25,17 @@ internal class BackupNotifier(private val context: Context) {
         context.notificationManager.notify(id, build())
     }
 
-    fun showBackupProgress() {
-        with(notificationBuilder) {
-            setContentTitle(context.getString(R.string.backup))
-            setContentText(context.getString(R.string.creating_backup))
+    fun showBackupProgress(): NotificationCompat.Builder {
+        val builder = with(notificationBuilder) {
+            setContentTitle(context.getString(R.string.creating_backup))
 
             setProgress(0, 0, true)
             setOngoing(true)
         }
 
-        notificationBuilder.show(Notifications.ID_BACKUP_PROGRESS)
+        builder.show(Notifications.ID_BACKUP_PROGRESS)
+
+        return builder
     }
 
     fun showBackupError(error: String?) {
@@ -59,7 +60,7 @@ internal class BackupNotifier(private val context: Context) {
             setContentTitle(context.getString(R.string.backup_created))
 
             if (unifile.filePath != null) {
-                setContentText(context.getString(R.string.file_saved, unifile.filePath))
+                setContentText(unifile.filePath)
             }
 
             // Remove progress bar

+ 3 - 8
app/src/main/java/eu/kanade/tachiyomi/data/backup/BackupRestoreService.kt

@@ -32,7 +32,6 @@ import eu.kanade.tachiyomi.data.database.models.TrackImpl
 import eu.kanade.tachiyomi.data.notification.Notifications
 import eu.kanade.tachiyomi.data.track.TrackManager
 import eu.kanade.tachiyomi.source.Source
-import eu.kanade.tachiyomi.ui.setting.backup.BackupNotifier
 import eu.kanade.tachiyomi.util.system.isServiceRunning
 import java.io.File
 import java.text.SimpleDateFormat
@@ -47,7 +46,7 @@ import timber.log.Timber
 import uy.kohesive.injekt.injectLazy
 
 /**
- * Restores backup from json file
+ * Restores backup from a JSON file.
  */
 class BackupRestoreService : Service() {
 
@@ -116,16 +115,12 @@ class BackupRestoreService : Service() {
     private val errors = mutableListOf<Pair<Date, String>>()
 
     private lateinit var backupManager: BackupManager
+    private lateinit var notifier: BackupNotifier
 
     private val db: DatabaseHelper by injectLazy()
 
     private val trackManager: TrackManager by injectLazy()
 
-    private lateinit var notifier: BackupNotifier
-
-    /**
-     * Method called when the service is created. It injects dependencies and acquire the wake lock.
-     */
     override fun onCreate() {
         super.onCreate()
         notifier = BackupNotifier(this)
@@ -133,7 +128,7 @@ class BackupRestoreService : Service() {
         startForeground(Notifications.ID_RESTORE_PROGRESS, notifier.showRestoreProgress().build())
 
         wakeLock = (getSystemService(Context.POWER_SERVICE) as PowerManager).newWakeLock(
-            PowerManager.PARTIAL_WAKE_LOCK, "BackupRestoreService:WakeLock"
+            PowerManager.PARTIAL_WAKE_LOCK, "${javaClass.name}:WakeLock"
         )
         wakeLock.acquire()
     }

+ 2 - 46
app/src/main/java/eu/kanade/tachiyomi/ui/setting/SettingsBackupController.kt

@@ -4,10 +4,7 @@ import android.Manifest.permission.WRITE_EXTERNAL_STORAGE
 import android.app.Activity
 import android.app.Dialog
 import android.content.ActivityNotFoundException
-import android.content.BroadcastReceiver
-import android.content.Context
 import android.content.Intent
-import android.content.IntentFilter
 import android.net.Uri
 import android.os.Bundle
 import android.view.View
@@ -16,7 +13,6 @@ import com.afollestad.materialdialogs.MaterialDialog
 import com.afollestad.materialdialogs.list.listItemsMultiChoice
 import com.hippo.unifile.UniFile
 import eu.kanade.tachiyomi.R
-import eu.kanade.tachiyomi.data.backup.BackupConst
 import eu.kanade.tachiyomi.data.backup.BackupCreateService
 import eu.kanade.tachiyomi.data.backup.BackupCreatorJob
 import eu.kanade.tachiyomi.data.backup.BackupRestoreService
@@ -24,7 +20,6 @@ import eu.kanade.tachiyomi.data.backup.models.Backup
 import eu.kanade.tachiyomi.data.preference.PreferenceKeys as Keys
 import eu.kanade.tachiyomi.ui.base.controller.DialogController
 import eu.kanade.tachiyomi.ui.base.controller.requestPermissionsSafe
-import eu.kanade.tachiyomi.ui.setting.backup.BackupNotifier
 import eu.kanade.tachiyomi.util.preference.defaultValue
 import eu.kanade.tachiyomi.util.preference.entriesRes
 import eu.kanade.tachiyomi.util.preference.intListPreference
@@ -35,9 +30,7 @@ import eu.kanade.tachiyomi.util.preference.preferenceCategory
 import eu.kanade.tachiyomi.util.preference.summaryRes
 import eu.kanade.tachiyomi.util.preference.titleRes
 import eu.kanade.tachiyomi.util.system.getFilePicker
-import eu.kanade.tachiyomi.util.system.registerLocalReceiver
 import eu.kanade.tachiyomi.util.system.toast
-import eu.kanade.tachiyomi.util.system.unregisterLocalReceiver
 import kotlinx.coroutines.flow.launchIn
 import kotlinx.coroutines.flow.onEach
 
@@ -48,24 +41,11 @@ class SettingsBackupController : SettingsController() {
      */
     private var backupFlags = 0
 
-    private val notifier by lazy { BackupNotifier(preferences.context) }
-
-    private val receiver = BackupBroadcastReceiver()
-
-    init {
-        preferences.context.registerLocalReceiver(receiver, IntentFilter(BackupConst.INTENT_FILTER))
-    }
-
     override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
         super.onViewCreated(view, savedInstanceState)
         requestPermissionsSafe(arrayOf(WRITE_EXTERNAL_STORAGE), 500)
     }
 
-    override fun onDestroy() {
-        super.onDestroy()
-        preferences.context.unregisterLocalReceiver(receiver)
-    }
-
     override fun setupPreferenceScreen(screen: PreferenceScreen) = with(screen) {
         titleRes = R.string.backup
 
@@ -74,7 +54,7 @@ class SettingsBackupController : SettingsController() {
             summaryRes = R.string.pref_create_backup_summ
 
             onClick {
-                if (!isBackupStarted) {
+                if (!BackupCreateService.isRunning(context)) {
                     val ctrl = CreateBackupDialog()
                     ctrl.targetController = this@SettingsBackupController
                     ctrl.showDialog(router)
@@ -193,11 +173,8 @@ class SettingsBackupController : SettingsController() {
                 val file = UniFile.fromUri(activity, uri)
 
                 activity.toast(R.string.creating_backup)
-                notifier.showBackupProgress()
 
-                BackupCreateService.makeBackup(activity, file.uri, backupFlags)
-
-                isBackupStarted = true
+                BackupCreateService.start(activity, file.uri, backupFlags)
             }
             CODE_BACKUP_RESTORE -> if (data != null && resultCode == Activity.RESULT_OK) {
                 val uri = data.data
@@ -286,30 +263,9 @@ class SettingsBackupController : SettingsController() {
         }
     }
 
-    inner class BackupBroadcastReceiver : BroadcastReceiver() {
-        override fun onReceive(context: Context, intent: Intent) {
-            when (intent.getStringExtra(BackupConst.ACTION)) {
-                BackupConst.ACTION_BACKUP_COMPLETED -> {
-                    isBackupStarted = false
-
-                    val uri = Uri.parse(intent.getStringExtra(BackupConst.EXTRA_URI))
-                    val unifile = UniFile.fromUri(activity, uri)
-                    notifier.showBackupComplete(unifile)
-                }
-                BackupConst.ACTION_BACKUP_ERROR -> {
-                    isBackupStarted = false
-
-                    notifier.showBackupError(intent.getStringExtra(BackupConst.EXTRA_ERROR_MESSAGE))
-                }
-            }
-        }
-    }
-
     private companion object {
         const val CODE_BACKUP_CREATE = 501
         const val CODE_BACKUP_RESTORE = 502
         const val CODE_BACKUP_DIR = 503
-
-        var isBackupStarted = false
     }
 }

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

@@ -321,7 +321,6 @@
     <string name="restore_duration">%02d min, %02d sec</string>
     <string name="restore_completed_content">Done in %1$s with %2$s errors</string>
     <string name="backup_restore_content">Restore uses sources to fetch data, carrier costs may apply.\n\nMake sure you have installed all necessary extensions and are logged in to sources and tracking services before restoring.</string>
-    <string name="file_saved">File saved at %1$s</string>
     <string name="backup_in_progress">Backup is already in progress</string>
     <string name="backup_choice">What do you want to backup?</string>
     <string name="creating_backup">Creating backup</string>