mirror of
https://github.com/tachiyomiorg/tachiyomi.git
synced 2024-11-13 07:15:10 +01:00
Refactor backup service
This commit is contained in:
parent
6069bcd058
commit
ce492ab7ac
@ -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"
|
||||||
|
@ -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
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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 {
|
||||||
|
@ -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)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -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)
|
||||||
|
@ -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
|
||||||
*
|
*
|
||||||
|
@ -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 {
|
||||||
|
@ -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
|
||||||
|
@ -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.
|
||||||
*/
|
*/
|
||||||
|
@ -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>
|
||||||
|
Loading…
Reference in New Issue
Block a user