Refactor backup service

This commit is contained in:
arkon 2020-05-06 22:23:15 -04:00 committed by Jay
parent 6069bcd058
commit ce492ab7ac
11 changed files with 265 additions and 113 deletions

View File

@ -3,6 +3,8 @@ import eu.kanade.tachiyomi.BuildConfig.APPLICATION_ID as ID
object BackupConst { object BackupConst {
private const val NAME = "BackupRestoreServices"
const val EXTRA_FLAGS = "$ID.$NAME.EXTRA_FLAGS"
const val INTENT_FILTER = "SettingsBackupFragment" const val INTENT_FILTER = "SettingsBackupFragment"
const val ACTION_BACKUP_COMPLETED_DIALOG = "$ID.$INTENT_FILTER.ACTION_BACKUP_COMPLETED_DIALOG" const val ACTION_BACKUP_COMPLETED_DIALOG = "$ID.$INTENT_FILTER.ACTION_BACKUP_COMPLETED_DIALOG"
const val ACTION_ERROR_BACKUP_DIALOG = "$ID.$INTENT_FILTER.ACTION_ERROR_BACKUP_DIALOG" const val ACTION_ERROR_BACKUP_DIALOG = "$ID.$INTENT_FILTER.ACTION_ERROR_BACKUP_DIALOG"

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.data.database.models.Manga import android.os.IBinder
import eu.kanade.tachiyomi.BuildConfig.APPLICATION_ID as ID 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,25 +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) {
val intent = Intent(context, BackupCreateService::class.java).apply { if (!isRunning(context)) {
putExtra(BackupConst.EXTRA_URI, uri) val intent = Intent(context, BackupCreateService::class.java).apply {
putExtra(EXTRA_FLAGS, flags) 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?) { private lateinit var backupManager: BackupManager
if (intent == null) return private lateinit var notifier: BackupNotifier
// Get values override fun onCreate() {
val uri = intent.getParcelableExtra<Uri>(BackupConst.EXTRA_URI) super.onCreate()
val flags = intent.getIntExtra(EXTRA_FLAGS, 0) notifier = BackupNotifier(this)
// Create backup
if (uri != null) startForeground(Notifications.ID_BACKUP_PROGRESS, notifier.showBackupProgress().build())
backupManager.createBackup(uri, flags, false)
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 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
} }
} }

View File

@ -21,8 +21,12 @@ class BackupCreatorJob(private val context: Context, workerParams: WorkerParamet
val backupManager = BackupManager(context) val backupManager = BackupManager(context)
val uri = Uri.parse(preferences.backupsDirectory().getOrDefault()) val uri = Uri.parse(preferences.backupsDirectory().getOrDefault())
val flags = BackupCreateService.BACKUP_ALL val flags = BackupCreateService.BACKUP_ALL
backupManager.createBackup(uri, flags, true) return try {
return Result.success() backupManager.createBackup(uri, flags, true)
Result.success()
} catch (e: Exception) {
Result.failure()
}
} }
companion object { companion object {

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
@ -25,6 +24,7 @@ import eu.kanade.tachiyomi.data.backup.models.Backup
import eu.kanade.tachiyomi.data.backup.models.Backup.CATEGORIES import eu.kanade.tachiyomi.data.backup.models.Backup.CATEGORIES
import eu.kanade.tachiyomi.data.backup.models.Backup.CHAPTERS import eu.kanade.tachiyomi.data.backup.models.Backup.CHAPTERS
import eu.kanade.tachiyomi.data.backup.models.Backup.CURRENT_VERSION import eu.kanade.tachiyomi.data.backup.models.Backup.CURRENT_VERSION
import eu.kanade.tachiyomi.data.backup.models.Backup.EXTENSIONS
import eu.kanade.tachiyomi.data.backup.models.Backup.HISTORY import eu.kanade.tachiyomi.data.backup.models.Backup.HISTORY
import eu.kanade.tachiyomi.data.backup.models.Backup.MANGA import eu.kanade.tachiyomi.data.backup.models.Backup.MANGA
import eu.kanade.tachiyomi.data.backup.models.Backup.TRACK import eu.kanade.tachiyomi.data.backup.models.Backup.TRACK
@ -51,7 +51,6 @@ import eu.kanade.tachiyomi.source.Source
import eu.kanade.tachiyomi.source.SourceManager import eu.kanade.tachiyomi.source.SourceManager
import eu.kanade.tachiyomi.source.fetchMangaDetailsAsync import eu.kanade.tachiyomi.source.fetchMangaDetailsAsync
import eu.kanade.tachiyomi.util.chapter.syncChaptersWithSource import eu.kanade.tachiyomi.util.chapter.syncChaptersWithSource
import eu.kanade.tachiyomi.util.system.sendLocalBroadcast
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext import kotlinx.coroutines.withContext
import rx.Observable import rx.Observable
@ -120,7 +119,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) { fun createBackup(uri: Uri, flags: Int, isJob: Boolean): String? {
// Create root object // Create root object
val root = JsonObject() val root = JsonObject()
@ -130,8 +129,11 @@ class BackupManager(val context: Context, version: Int = CURRENT_VERSION) {
// Create category array // Create category array
val categoryEntries = JsonArray() val categoryEntries = JsonArray()
// Create extension ID/name mapping
val extensionEntries = JsonArray()
// Add value's to root // Add value's to root
root[Backup.VERSION] = Backup.CURRENT_VERSION root[Backup.VERSION] = CURRENT_VERSION
root[Backup.MANGAS] = mangaEntries root[Backup.MANGAS] = mangaEntries
root[CATEGORIES] = categoryEntries root[CATEGORIES] = categoryEntries
@ -173,6 +175,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")
@ -180,23 +184,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_DIALOG)
putExtra(BackupConst.EXTRA_URI, file.uri.toString())
}
context.sendLocalBroadcast(intent)
} }
} 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_ERROR_BACKUP_DIALOG)
putExtra(BackupConst.EXTRA_ERROR_MESSAGE, e.message)
}
context.sendLocalBroadcast(intent)
}
} }
} }

View File

@ -0,0 +1,84 @@
package eu.kanade.tachiyomi.data.backup
import android.content.Context
import android.graphics.BitmapFactory
import androidx.core.app.NotificationCompat
import com.hippo.unifile.UniFile
import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.data.notification.NotificationReceiver
import eu.kanade.tachiyomi.data.notification.Notifications
import eu.kanade.tachiyomi.data.preference.PreferencesHelper
import eu.kanade.tachiyomi.util.system.notificationBuilder
import eu.kanade.tachiyomi.util.system.notificationManager
import uy.kohesive.injekt.injectLazy
internal class BackupNotifier(private val context: Context) {
private val preferences: PreferencesHelper by injectLazy()
private val progressNotificationBuilder = context.notificationBuilder(Notifications.CHANNEL_BACKUP_RESTORE) {
setLargeIcon(BitmapFactory.decodeResource(context.resources, R.mipmap.ic_launcher))
setSmallIcon(R.drawable.ic_tachi)
setAutoCancel(false)
setOngoing(true)
}
private val completeNotificationBuilder = context.notificationBuilder(Notifications.CHANNEL_BACKUP_RESTORE) {
setLargeIcon(BitmapFactory.decodeResource(context.resources, R.mipmap.ic_launcher))
setSmallIcon(R.drawable.ic_tachi)
setAutoCancel(false)
}
private fun NotificationCompat.Builder.show(id: Int) {
context.notificationManager.notify(id, build())
}
fun showBackupProgress(): NotificationCompat.Builder {
val builder = with(progressNotificationBuilder) {
setContentTitle(context.getString(R.string.creating_backup))
setProgress(0, 0, true)
setOnlyAlertOnce(true)
}
builder.show(Notifications.ID_BACKUP_PROGRESS)
return builder
}
fun showBackupError(error: String?) {
context.notificationManager.cancel(Notifications.ID_BACKUP_PROGRESS)
with(completeNotificationBuilder) {
setContentTitle(context.getString(R.string.backup_failed))
setContentText(error)
show(Notifications.ID_BACKUP_COMPLETE)
}
}
fun showBackupComplete(unifile: UniFile) {
context.notificationManager.cancel(Notifications.ID_BACKUP_PROGRESS)
with(completeNotificationBuilder) {
setContentTitle(context.getString(R.string.backup_created))
if (unifile.filePath != null) {
setContentText(unifile.filePath)
}
// Clear old actions if they exist
if (mActions.isNotEmpty()) {
mActions.clear()
}
addAction(
R.drawable.ic_share_grey_24dp,
context.getString(R.string.share),
NotificationReceiver.shareBackupPendingBroadcast(context, unifile.uri, Notifications.ID_BACKUP_COMPLETE)
)
show(Notifications.ID_BACKUP_COMPLETE)
}
}
}

View File

@ -335,7 +335,7 @@ class BackupRestoreService : Service() {
* keep a partially constructed progress notification for resuse * keep a partially constructed progress notification for resuse
*/ */
private val progressNotification by lazy { private val progressNotification by lazy {
NotificationCompat.Builder(this, Notifications.CHANNEL_RESTORE) NotificationCompat.Builder(this, Notifications.CHANNEL_BACKUP_RESTORE)
.setContentTitle(getString(R.string.app_name)) .setContentTitle(getString(R.string.app_name))
.setSmallIcon(R.drawable.ic_tachi) .setSmallIcon(R.drawable.ic_tachi)
.setOngoing(true) .setOngoing(true)
@ -392,7 +392,7 @@ class BackupRestoreService : Service() {
val restoreString = content.joinToString("\n") val restoreString = content.joinToString("\n")
val resultNotification = NotificationCompat.Builder(this, Notifications.CHANNEL_RESTORE) val resultNotification = NotificationCompat.Builder(this, Notifications.CHANNEL_BACKUP_RESTORE)
.setContentTitle(getString(R.string.restore_completed)) .setContentTitle(getString(R.string.restore_completed))
.setContentText(restoreString) .setContentText(restoreString)
.setStyle(NotificationCompat.BigTextStyle().bigText(restoreString)) .setStyle(NotificationCompat.BigTextStyle().bigText(restoreString))
@ -410,7 +410,7 @@ class BackupRestoreService : Service() {
* *
*/ */
private fun showErrorNotification(errorMessage: String) { private fun showErrorNotification(errorMessage: String) {
val resultNotification = NotificationCompat.Builder(this, Notifications.CHANNEL_RESTORE) val resultNotification = NotificationCompat.Builder(this, Notifications.CHANNEL_BACKUP_RESTORE)
.setContentTitle(getString(R.string.restore_error)) .setContentTitle(getString(R.string.restore_error))
.setContentText(errorMessage) .setContentText(errorMessage)
.setSmallIcon(R.drawable.ic_error_grey) .setSmallIcon(R.drawable.ic_error_grey)

View File

@ -66,6 +66,12 @@ class NotificationReceiver : BroadcastReceiver() {
// Cancel library update and dismiss notification // Cancel library update and dismiss notification
ACTION_CANCEL_LIBRARY_UPDATE -> cancelLibraryUpdate(context) ACTION_CANCEL_LIBRARY_UPDATE -> cancelLibraryUpdate(context)
ACTION_CANCEL_RESTORE -> cancelRestoreUpdate(context) ACTION_CANCEL_RESTORE -> cancelRestoreUpdate(context)
// Share backup file
ACTION_SHARE_BACKUP ->
shareBackup(
context, intent.getParcelableExtra(EXTRA_URI),
intent.getIntExtra(EXTRA_NOTIFICATION_ID, -1)
)
// Open reader activity // Open reader activity
ACTION_OPEN_CHAPTER -> { ACTION_OPEN_CHAPTER -> {
openChapter(context, intent.getLongExtra(EXTRA_MANGA_ID, -1), openChapter(context, intent.getLongExtra(EXTRA_MANGA_ID, -1),
@ -112,6 +118,25 @@ class NotificationReceiver : BroadcastReceiver() {
// Close Navigation Shade // Close Navigation Shade
} }
/**
* Called to start share intent to share backup file
*
* @param context context of application
* @param path path of file
* @param notificationId id of notification
*/
private fun shareBackup(context: Context, uri: Uri, notificationId: Int) {
val sendIntent = Intent(Intent.ACTION_SEND).apply {
putExtra(Intent.EXTRA_STREAM, uri)
type = "application/json"
flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_GRANT_READ_URI_PERMISSION
}
// Dismiss notification
dismissNotification(context, notificationId)
// Launch share activity
context.startActivity(sendIntent)
}
/** /**
* Starts reader activity * Starts reader activity
* *
@ -204,6 +229,9 @@ class NotificationReceiver : BroadcastReceiver() {
// Called to delete image. // Called to delete image.
private const val ACTION_DELETE_IMAGE = "$ID.$NAME.DELETE_IMAGE" private const val ACTION_DELETE_IMAGE = "$ID.$NAME.DELETE_IMAGE"
// Called to launch send intent.
private const val ACTION_SHARE_BACKUP = "$ID.$NAME.SEND_BACKUP"
// Called to cancel library update. // Called to cancel library update.
private const val ACTION_CANCEL_LIBRARY_UPDATE = "$ID.$NAME.CANCEL_LIBRARY_UPDATE" private const val ACTION_CANCEL_LIBRARY_UPDATE = "$ID.$NAME.CANCEL_LIBRARY_UPDATE"
@ -231,6 +259,9 @@ class NotificationReceiver : BroadcastReceiver() {
// Called to dismiss notification. // Called to dismiss notification.
private const val ACTION_DISMISS_NOTIFICATION = "$ID.$NAME.ACTION_DISMISS_NOTIFICATION" private const val ACTION_DISMISS_NOTIFICATION = "$ID.$NAME.ACTION_DISMISS_NOTIFICATION"
// Value containing uri.
private const val EXTRA_URI = "$ID.$NAME.URI"
// Value containing notification id. // Value containing notification id.
private const val EXTRA_NOTIFICATION_ID = "$ID.$NAME.NOTIFICATION_ID" private const val EXTRA_NOTIFICATION_ID = "$ID.$NAME.NOTIFICATION_ID"
@ -468,6 +499,23 @@ class NotificationReceiver : BroadcastReceiver() {
return PendingIntent.getBroadcast(context, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT) return PendingIntent.getBroadcast(context, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT)
} }
/**
* Returns [PendingIntent] that starts a share activity for a backup file.
*
* @param context context of application
* @param uri uri of backup file
* @param notificationId id of notification
* @return [PendingIntent]
*/
internal fun shareBackupPendingBroadcast(context: Context, uri: Uri, notificationId: Int): PendingIntent {
val intent = Intent(context, NotificationReceiver::class.java).apply {
action = ACTION_SHARE_BACKUP
putExtra(EXTRA_URI, uri)
putExtra(EXTRA_NOTIFICATION_ID, notificationId)
}
return PendingIntent.getBroadcast(context, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT)
}
/** /**
* Returns [PendingIntent] that starts a service which stops the restore service * Returns [PendingIntent] that starts a service which stops the restore service
* *

View File

@ -45,10 +45,12 @@ object Notifications {
const val CHANNEL_UPDATES_TO_EXTS = "updates_ext_channel" const val CHANNEL_UPDATES_TO_EXTS = "updates_ext_channel"
const val ID_UPDATES_TO_EXTS = -401 const val ID_UPDATES_TO_EXTS = -401
const val CHANNEL_RESTORE = "backup_restore_channel" const val CHANNEL_BACKUP_RESTORE = "backup_restore_channel"
const val ID_RESTORE_PROGRESS = -501 const val ID_RESTORE_PROGRESS = -501
const val ID_RESTORE_COMPLETE = -502 const val ID_RESTORE_COMPLETE = -502
const val ID_RESTORE_ERROR = -503 const val ID_BACKUP_PROGRESS = -502
const val ID_BACKUP_COMPLETE = -503
const val ID_RESTORE_ERROR = -504
/** /**
* Creates the notification channels introduced in Android Oreo. * Creates the notification channels introduced in Android Oreo.
@ -83,7 +85,7 @@ object Notifications {
context.getString(R.string.new_chapters), context.getString(R.string.new_chapters),
NotificationManager.IMPORTANCE_DEFAULT NotificationManager.IMPORTANCE_DEFAULT
), NotificationChannel( ), NotificationChannel(
CHANNEL_RESTORE, CHANNEL_BACKUP_RESTORE,
context.getString(R.string.restoring_backup), context.getString(R.string.restoring_backup),
NotificationManager.IMPORTANCE_LOW NotificationManager.IMPORTANCE_LOW
).apply { ).apply {

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,9 +20,7 @@ import eu.kanade.tachiyomi.data.backup.models.Backup
import eu.kanade.tachiyomi.data.preference.getOrDefault import eu.kanade.tachiyomi.data.preference.getOrDefault
import eu.kanade.tachiyomi.ui.base.controller.DialogController import eu.kanade.tachiyomi.ui.base.controller.DialogController
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 eu.kanade.tachiyomi.util.view.requestPermissionsSafe import eu.kanade.tachiyomi.util.view.requestPermissionsSafe
import eu.kanade.tachiyomi.data.preference.PreferenceKeys as Keys import eu.kanade.tachiyomi.data.preference.PreferenceKeys as Keys
@ -37,22 +31,11 @@ class SettingsBackupController : SettingsController() {
*/ */
private var backupFlags = 0 private var backupFlags = 0
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
@ -170,7 +153,9 @@ class SettingsBackupController : SettingsController() {
val file = UniFile.fromUri(activity, uri) val file = UniFile.fromUri(activity, uri)
BackupCreateService.makeBackup(activity, file.uri, backupFlags) activity.toast(R.string.creating_backup)
BackupCreateService.start(activity, file.uri, backupFlags)
} }
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
@ -212,7 +197,7 @@ class SettingsBackupController : SettingsController() {
.title(R.string.create_backup) .title(R.string.create_backup)
.message(R.string.what_should_backup) .message(R.string.what_should_backup)
.listItemsMultiChoice(items = options, disabledIndices = intArrayOf(0), .listItemsMultiChoice(items = options, disabledIndices = intArrayOf(0),
initialSelection = intArrayOf(0)) { _, positions, _ -> initialSelection = intArrayOf(0, 1, 2, 3, 4)) { _, positions, _ ->
var flags = 0 var flags = 0
for (i in 1 until positions.size) { for (i in 1 until positions.size) {
when (positions[i]) { when (positions[i]) {
@ -230,33 +215,6 @@ class SettingsBackupController : SettingsController() {
} }
} }
class CreatedBackupDialog(bundle: Bundle? = null) : DialogController(bundle) {
constructor(uri: Uri) : this(Bundle().apply {
putParcelable(KEY_URI, uri)
})
override fun onCreateDialog(savedViewState: Bundle?): Dialog {
val activity = activity!!
val uniFile = UniFile.fromUri(activity, args.getParcelable(KEY_URI))
return MaterialDialog(activity).apply {
title(R.string.backup_created)
if (uniFile.filePath != null)
message(text = resources?.getString(R.string.file_saved_at_, uniFile.filePath))
positiveButton(R.string.close)
negativeButton(R.string.share) {
val sendIntent = Intent(Intent.ACTION_SEND)
sendIntent.type = "application/json"
sendIntent.putExtra(Intent.EXTRA_STREAM, uniFile.uri)
startActivity(Intent.createChooser(sendIntent, ""))
}
}
}
private companion object {
const val KEY_URI = "BackupCreatedDialog.uri"
}
}
class RestoreBackupDialog(bundle: Bundle? = null) : DialogController(bundle) { class RestoreBackupDialog(bundle: Bundle? = null) : DialogController(bundle) {
constructor(uri: Uri) : this(Bundle().apply { constructor(uri: Uri) : this(Bundle().apply {
putParcelable(KEY_URI, uri) putParcelable(KEY_URI, uri)
@ -280,20 +238,6 @@ class SettingsBackupController : SettingsController() {
} }
} }
inner class BackupBroadcastReceiver : BroadcastReceiver() {
override fun onReceive(context: Context, intent: Intent) {
when (intent.getStringExtra(BackupConst.ACTION)) {
BackupConst.ACTION_BACKUP_COMPLETED_DIALOG -> {
val uri = Uri.parse(intent.getStringExtra(BackupConst.EXTRA_URI))
CreatedBackupDialog(uri).showDialog(router)
}
BackupConst.ACTION_ERROR_BACKUP_DIALOG -> {
context.toast(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

View File

@ -136,6 +136,22 @@ val Float.dpToPxEnd: Float
val Resources.isLTR val Resources.isLTR
get() = configuration.layoutDirection == View.LAYOUT_DIRECTION_LTR get() = configuration.layoutDirection == View.LAYOUT_DIRECTION_LTR
/**
* Helper method to create a notification builder.
*
* @param id the channel id.
* @param block the function that will execute inside the builder.
* @return a notification to be displayed or updated.
*/
fun Context.notificationBuilder(channelId: String, block: (NotificationCompat.Builder.() -> Unit)? = null): NotificationCompat.Builder {
val builder = NotificationCompat.Builder(this, channelId)
.setColor(ContextCompat.getColor(this, R.color.colorAccent))
if (block != null) {
builder.block()
}
return builder
}
/** /**
* Property to get the notification manager from the context. * Property to get the notification manager from the context.
*/ */

View File

@ -476,13 +476,14 @@
<string name="service">Service</string> <string name="service">Service</string>
<string name="backup_frequency">Backup frequency</string> <string name="backup_frequency">Backup frequency</string>
<string name="max_auto_backups">Max automatic backups</string> <string name="max_auto_backups">Max automatic backups</string>
<string name="backup_failed">Backup failed</string>
<string name="backup_created">Backup created</string> <string name="backup_created">Backup created</string>
<string name="restore_completed">Restore completed</string> <string name="restore_completed">Restore completed</string>
<string name="restore_error">Restore error</string> <string name="restore_error">Restore error</string>
<string name="restore_completed_content">%1$s Restored. %2$s errors found</string> <string name="restore_completed_content">%1$s Restored. %2$s errors found</string>
<string name="restore_content_skipped">%1$d skipped</string> <string name="restore_content_skipped">%1$d skipped</string>
<string name="restore_message">Restore uses the network 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="restore_message">Restore uses the network 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_at_">File saved at %1$s</string> <string name="creating_backup">Creating backup</string>
<string name="what_should_backup">What do you want to backup?</string> <string name="what_should_backup">What do you want to backup?</string>
<string name="restoring_backup">Restoring backup</string> <string name="restoring_backup">Restoring backup</string>
<string name="restoring_progress">Restoring (%1$d/%2$d)</string> <string name="restoring_progress">Restoring (%1$d/%2$d)</string>