Refactor backup service

This commit is contained in:
arkon 2020-04-26 16:07:07 -04:00
parent 52474e39d9
commit d19d787f6e
8 changed files with 109 additions and 114 deletions

View File

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

View File

@ -1,25 +1,22 @@
package eu.kanade.tachiyomi.data.backup package eu.kanade.tachiyomi.data.backup
import android.app.IntentService import android.app.Service
import android.content.Context import android.content.Context
import android.content.Intent import android.content.Intent
import android.net.Uri import android.net.Uri
import com.google.gson.JsonArray import android.os.Build
import eu.kanade.tachiyomi.BuildConfig.APPLICATION_ID as ID import android.os.IBinder
import eu.kanade.tachiyomi.data.database.models.Manga 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 { companion object {
// Name of class
private const val NAME = "BackupCreateService"
// Options for backup
private const val EXTRA_FLAGS = "$ID.$NAME.EXTRA_FLAGS"
// Filter options // Filter options
internal const val BACKUP_CATEGORY = 0x1 internal const val BACKUP_CATEGORY = 0x1
internal const val BACKUP_CATEGORY_MASK = 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_TRACK_MASK = 0x8
internal const val BACKUP_ALL = 0xF 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 * Make a backup from library
* *
@ -38,24 +44,78 @@ class BackupCreateService : IntentService(NAME) {
* @param uri path of Uri * @param uri path of Uri
* @param flags determines what to backup * @param flags determines what to backup
*/ */
fun makeBackup(context: Context, uri: Uri, flags: Int) { fun start(context: Context, uri: Uri, flags: Int) {
if (!isRunning(context)) {
val intent = Intent(context, BackupCreateService::class.java).apply { val intent = Intent(context, BackupCreateService::class.java).apply {
putExtra(BackupConst.EXTRA_URI, uri) putExtra(BackupConst.EXTRA_URI, uri)
putExtra(EXTRA_FLAGS, flags) putExtra(BackupConst.EXTRA_FLAGS, flags)
} }
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O) {
context.startService(intent) context.startService(intent)
} else {
context.startForegroundService(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?) { private lateinit var backupManager: BackupManager
if (intent == null) return private lateinit var notifier: BackupNotifier
// Get values 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<Uri>(BackupConst.EXTRA_URI) val uri = intent.getParcelableExtra<Uri>(BackupConst.EXTRA_URI)
val flags = intent.getIntExtra(EXTRA_FLAGS, 0) val backupFlags = intent.getIntExtra(BackupConst.EXTRA_FLAGS, 0)
// Create backup backupManager = BackupManager(this)
backupManager.createBackup(uri, flags, false)
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
} }
} }

View File

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

View File

@ -1,7 +1,6 @@
package eu.kanade.tachiyomi.data.backup package eu.kanade.tachiyomi.data.backup
import android.content.Context import android.content.Context
import android.content.Intent
import android.net.Uri import android.net.Uri
import com.github.salomonbrys.kotson.fromJson import com.github.salomonbrys.kotson.fromJson
import com.github.salomonbrys.kotson.registerTypeAdapter 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.Source
import eu.kanade.tachiyomi.source.SourceManager import eu.kanade.tachiyomi.source.SourceManager
import eu.kanade.tachiyomi.util.chapter.syncChaptersWithSource import eu.kanade.tachiyomi.util.chapter.syncChaptersWithSource
import eu.kanade.tachiyomi.util.system.sendLocalBroadcast
import kotlin.math.max import kotlin.math.max
import rx.Observable import rx.Observable
import timber.log.Timber import timber.log.Timber
@ -102,7 +100,7 @@ class BackupManager(val context: Context, version: Int = CURRENT_VERSION) {
* @param uri path of Uri * @param uri path of Uri
* @param isJob backup called from job * @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 // Create root object
val root = JsonObject() val root = JsonObject()
@ -155,6 +153,8 @@ class BackupManager(val context: Context, version: Int = CURRENT_VERSION) {
newFile.openOutputStream().bufferedWriter().use { newFile.openOutputStream().bufferedWriter().use {
parser.toJson(root, it) parser.toJson(root, it)
} }
return newFile.uri.toString()
} else { } else {
val file = UniFile.fromUri(context, uri) val file = UniFile.fromUri(context, uri)
?: throw Exception("Couldn't create backup file") ?: throw Exception("Couldn't create backup file")
@ -162,25 +162,11 @@ class BackupManager(val context: Context, version: Int = CURRENT_VERSION) {
parser.toJson(root, it) parser.toJson(root, it)
} }
// Show completed dialog return file.uri.toString()
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 true
} catch (e: Exception) { } catch (e: Exception) {
Timber.e(e) Timber.e(e)
if (!isJob) { throw e
// 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
} }
} }

View File

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

View File

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

View File

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

View File

@ -321,7 +321,6 @@
<string name="restore_duration">%02d min, %02d sec</string> <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="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="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_in_progress">Backup is already in progress</string>
<string name="backup_choice">What do you want to backup?</string> <string name="backup_choice">What do you want to backup?</string>
<string name="creating_backup">Creating backup</string> <string name="creating_backup">Creating backup</string>