From d19d787f6e286d7e7b8cea9f27306d7520f7d489 Mon Sep 17 00:00:00 2001 From: arkon Date: Sun, 26 Apr 2020 16:07:07 -0400 Subject: [PATCH] Refactor backup service --- .../tachiyomi/data/backup/BackupConst.kt | 9 +- .../data/backup/BackupCreateService.kt | 110 ++++++++++++++---- .../tachiyomi/data/backup/BackupCreatorJob.kt | 5 +- .../tachiyomi/data/backup/BackupManager.kt | 24 +--- .../setting => data}/backup/BackupNotifier.kt | 15 +-- .../data/backup/BackupRestoreService.kt | 11 +- .../ui/setting/SettingsBackupController.kt | 48 +------- app/src/main/res/values/strings.xml | 1 - 8 files changed, 109 insertions(+), 114 deletions(-) rename app/src/main/java/eu/kanade/tachiyomi/{ui/setting => data}/backup/BackupNotifier.kt (92%) diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/backup/BackupConst.kt b/app/src/main/java/eu/kanade/tachiyomi/data/backup/BackupConst.kt index 6d7859898d..df76e47d11 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/data/backup/BackupConst.kt +++ b/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" } diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/backup/BackupCreateService.kt b/app/src/main/java/eu/kanade/tachiyomi/data/backup/BackupCreateService.kt index b202ee9d39..a212db9de4 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/data/backup/BackupCreateService.kt +++ b/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 - override fun onHandleIntent(intent: Intent?) { - if (intent == null) return + private lateinit var backupManager: BackupManager + private lateinit var notifier: BackupNotifier - // Get values - val uri = intent.getParcelableExtra(BackupConst.EXTRA_URI) - val flags = intent.getIntExtra(EXTRA_FLAGS, 0) - // Create backup - backupManager.createBackup(uri, flags, false) + 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 + + try { + val uri = intent.getParcelableExtra(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) + } + + stopSelf(startId) + return START_NOT_STICKY } } diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/backup/BackupCreatorJob.kt b/app/src/main/java/eu/kanade/tachiyomi/data/backup/BackupCreatorJob.kt index d30182b250..1e3f4026d4 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/data/backup/BackupCreatorJob.kt +++ b/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() } } diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/backup/BackupManager.kt b/app/src/main/java/eu/kanade/tachiyomi/data/backup/BackupManager.kt index 142f6faa3e..0afb499266 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/data/backup/BackupManager.kt +++ b/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 } } diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/setting/backup/BackupNotifier.kt b/app/src/main/java/eu/kanade/tachiyomi/data/backup/BackupNotifier.kt similarity index 92% rename from app/src/main/java/eu/kanade/tachiyomi/ui/setting/backup/BackupNotifier.kt rename to app/src/main/java/eu/kanade/tachiyomi/data/backup/BackupNotifier.kt index 365117020b..1871ec5ea4 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/setting/backup/BackupNotifier.kt +++ b/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 diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/backup/BackupRestoreService.kt b/app/src/main/java/eu/kanade/tachiyomi/data/backup/BackupRestoreService.kt index 674cace24e..d15b00fe1e 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/data/backup/BackupRestoreService.kt +++ b/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>() 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() } diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/setting/SettingsBackupController.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/setting/SettingsBackupController.kt index 1f9b5f8448..2ae61edcae 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/setting/SettingsBackupController.kt +++ b/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 } } diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index bfbecccd8e..46105064b3 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -321,7 +321,6 @@ %02d min, %02d sec Done in %1$s with %2$s errors 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. - File saved at %1$s Backup is already in progress What do you want to backup? Creating backup