Run periodic backups without launching services

This commit is contained in:
inorichi 2018-02-18 20:14:12 +01:00
parent 49eb638e15
commit fa4d61eaf0
4 changed files with 100 additions and 115 deletions

View File

@ -4,17 +4,8 @@ import android.app.IntentService
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.github.salomonbrys.kotson.set
import com.google.gson.JsonArray import com.google.gson.JsonArray
import com.google.gson.JsonObject
import com.hippo.unifile.UniFile
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.MANGAS
import eu.kanade.tachiyomi.data.backup.models.Backup.VERSION
import eu.kanade.tachiyomi.data.database.models.Manga import eu.kanade.tachiyomi.data.database.models.Manga
import eu.kanade.tachiyomi.util.sendLocalBroadcast
import timber.log.Timber
import eu.kanade.tachiyomi.BuildConfig.APPLICATION_ID as ID import eu.kanade.tachiyomi.BuildConfig.APPLICATION_ID as ID
/** /**
@ -26,8 +17,6 @@ class BackupCreateService : IntentService(NAME) {
// Name of class // Name of class
private const val NAME = "BackupCreateService" private const val NAME = "BackupCreateService"
// Backup called from job
private const val EXTRA_IS_JOB = "$ID.$NAME.EXTRA_IS_JOB"
// Options for backup // Options for backup
private const val EXTRA_FLAGS = "$ID.$NAME.EXTRA_FLAGS" private const val EXTRA_FLAGS = "$ID.$NAME.EXTRA_FLAGS"
@ -48,12 +37,10 @@ class BackupCreateService : IntentService(NAME) {
* @param context context of application * @param context context of application
* @param uri path of Uri * @param uri path of Uri
* @param flags determines what to backup * @param flags determines what to backup
* @param isJob backup called from job
*/ */
fun makeBackup(context: Context, uri: Uri, flags: Int, isJob: Boolean = false) { fun makeBackup(context: Context, uri: Uri, flags: Int) {
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_IS_JOB, isJob)
putExtra(EXTRA_FLAGS, flags) putExtra(EXTRA_FLAGS, flags)
} }
context.startService(intent) context.startService(intent)
@ -68,95 +55,9 @@ class BackupCreateService : IntentService(NAME) {
// Get values // Get values
val uri = intent.getParcelableExtra<Uri>(BackupConst.EXTRA_URI) val uri = intent.getParcelableExtra<Uri>(BackupConst.EXTRA_URI)
val isJob = intent.getBooleanExtra(EXTRA_IS_JOB, false)
val flags = intent.getIntExtra(EXTRA_FLAGS, 0) val flags = intent.getIntExtra(EXTRA_FLAGS, 0)
// Create backup // Create backup
createBackupFromApp(uri, flags, isJob) backupManager.createBackup(uri, flags, false)
} }
/**
* Create backup Json file from database
*
* @param uri path of Uri
* @param isJob backup called from job
*/
private fun createBackupFromApp(uri: Uri, flags: Int, isJob: Boolean) {
// Create root object
val root = JsonObject()
// Create manga array
val mangaEntries = JsonArray()
// Create category array
val categoryEntries = JsonArray()
// Add value's to root
root[VERSION] = Backup.CURRENT_VERSION
root[MANGAS] = mangaEntries
root[CATEGORIES] = categoryEntries
backupManager.databaseHelper.inTransaction {
// Get manga from database
val mangas = backupManager.getFavoriteManga()
// Backup library manga and its dependencies
mangas.forEach { manga ->
mangaEntries.add(backupManager.backupMangaObject(manga, flags))
}
// Backup categories
if ((flags and BACKUP_CATEGORY_MASK) == BACKUP_CATEGORY) {
backupManager.backupCategories(categoryEntries)
}
}
try {
// When BackupCreatorJob
if (isJob) {
// Get dir of file and create
var dir = UniFile.fromUri(this, uri)
dir = dir.createDirectory("automatic")
// Delete older backups
val numberOfBackups = backupManager.numberOfBackups()
val backupRegex = Regex("""tachiyomi_\d+-\d+-\d+_\d+-\d+.json""")
dir.listFiles { _, filename -> backupRegex.matches(filename) }
.orEmpty()
.sortedByDescending { it.name }
.drop(numberOfBackups - 1)
.forEach { it.delete() }
// Create new file to place backup
val newFile = dir.createFile(Backup.getDefaultFilename())
?: throw Exception("Couldn't create backup file")
newFile.openOutputStream().bufferedWriter().use {
backupManager.parser.toJson(root, it)
}
} else {
val file = UniFile.fromUri(this, uri)
?: throw Exception("Couldn't create backup file")
file.openOutputStream().bufferedWriter().use {
backupManager.parser.toJson(root, it)
}
// Show completed dialog
val intent = Intent(BackupConst.INTENT_FILTER).apply {
putExtra(BackupConst.ACTION, BackupConst.ACTION_BACKUP_COMPLETED_DIALOG)
putExtra(BackupConst.EXTRA_URI, file.uri.toString())
}
sendLocalBroadcast(intent)
}
} catch (e: Exception) {
Timber.e(e)
if (!isJob) {
// 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)
}
sendLocalBroadcast(intent)
}
}
}
} }

View File

@ -13,9 +13,10 @@ class BackupCreatorJob : Job() {
override fun onRunJob(params: Params): Result { override fun onRunJob(params: Params): Result {
val preferences = Injekt.get<PreferencesHelper>() val preferences = Injekt.get<PreferencesHelper>()
val backupManager = Injekt.get<BackupManager>()
val uri = Uri.parse(preferences.backupsDirectory().getOrDefault()) val uri = Uri.parse(preferences.backupsDirectory().getOrDefault())
val flags = BackupCreateService.BACKUP_ALL val flags = BackupCreateService.BACKUP_ALL
BackupCreateService.makeBackup(context, uri, flags, true) backupManager.createBackup(uri, flags, true)
return Result.SUCCESS return Result.SUCCESS
} }

View File

@ -1,8 +1,11 @@
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 com.github.salomonbrys.kotson.* import com.github.salomonbrys.kotson.*
import com.google.gson.* import com.google.gson.*
import com.hippo.unifile.UniFile
import eu.kanade.tachiyomi.data.backup.BackupCreateService.Companion.BACKUP_CATEGORY import eu.kanade.tachiyomi.data.backup.BackupCreateService.Companion.BACKUP_CATEGORY
import eu.kanade.tachiyomi.data.backup.BackupCreateService.Companion.BACKUP_CATEGORY_MASK import eu.kanade.tachiyomi.data.backup.BackupCreateService.Companion.BACKUP_CATEGORY_MASK
import eu.kanade.tachiyomi.data.backup.BackupCreateService.Companion.BACKUP_CHAPTER import eu.kanade.tachiyomi.data.backup.BackupCreateService.Companion.BACKUP_CHAPTER
@ -11,6 +14,7 @@ import eu.kanade.tachiyomi.data.backup.BackupCreateService.Companion.BACKUP_HIST
import eu.kanade.tachiyomi.data.backup.BackupCreateService.Companion.BACKUP_HISTORY_MASK import eu.kanade.tachiyomi.data.backup.BackupCreateService.Companion.BACKUP_HISTORY_MASK
import eu.kanade.tachiyomi.data.backup.BackupCreateService.Companion.BACKUP_TRACK import eu.kanade.tachiyomi.data.backup.BackupCreateService.Companion.BACKUP_TRACK
import eu.kanade.tachiyomi.data.backup.BackupCreateService.Companion.BACKUP_TRACK_MASK import eu.kanade.tachiyomi.data.backup.BackupCreateService.Companion.BACKUP_TRACK_MASK
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
@ -26,8 +30,10 @@ import eu.kanade.tachiyomi.data.preference.getOrDefault
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.source.SourceManager import eu.kanade.tachiyomi.source.SourceManager
import eu.kanade.tachiyomi.util.sendLocalBroadcast
import eu.kanade.tachiyomi.util.syncChaptersWithSource import eu.kanade.tachiyomi.util.syncChaptersWithSource
import rx.Observable import rx.Observable
import timber.log.Timber
import uy.kohesive.injekt.injectLazy import uy.kohesive.injekt.injectLazy
class BackupManager(val context: Context, version: Int = CURRENT_VERSION) { class BackupManager(val context: Context, version: Int = CURRENT_VERSION) {
@ -85,6 +91,92 @@ class BackupManager(val context: Context, version: Int = CURRENT_VERSION) {
else -> throw Exception("Json version unknown") else -> throw Exception("Json version unknown")
} }
/**
* Create backup Json file from database
*
* @param uri path of Uri
* @param isJob backup called from job
*/
fun createBackup(uri: Uri, flags: Int, isJob: Boolean) {
// Create root object
val root = JsonObject()
// Create manga array
val mangaEntries = JsonArray()
// Create category array
val categoryEntries = JsonArray()
// Add value's to root
root[Backup.VERSION] = Backup.CURRENT_VERSION
root[Backup.MANGAS] = mangaEntries
root[CATEGORIES] = categoryEntries
databaseHelper.inTransaction {
// Get manga from database
val mangas = getFavoriteManga()
// Backup library manga and its dependencies
mangas.forEach { manga ->
mangaEntries.add(backupMangaObject(manga, flags))
}
// Backup categories
if ((flags and BACKUP_CATEGORY_MASK) == BACKUP_CATEGORY) {
backupCategories(categoryEntries)
}
}
try {
// When BackupCreatorJob
if (isJob) {
// Get dir of file and create
var dir = UniFile.fromUri(context, uri)
dir = dir.createDirectory("automatic")
// Delete older backups
val numberOfBackups = numberOfBackups()
val backupRegex = Regex("""tachiyomi_\d+-\d+-\d+_\d+-\d+.json""")
dir.listFiles { _, filename -> backupRegex.matches(filename) }
.orEmpty()
.sortedByDescending { it.name }
.drop(numberOfBackups - 1)
.forEach { it.delete() }
// Create new file to place backup
val newFile = dir.createFile(Backup.getDefaultFilename())
?: throw Exception("Couldn't create backup file")
newFile.openOutputStream().bufferedWriter().use {
parser.toJson(root, it)
}
} else {
val file = UniFile.fromUri(context, uri)
?: throw Exception("Couldn't create backup file")
file.openOutputStream().bufferedWriter().use {
parser.toJson(root, it)
}
// Show completed dialog
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) {
Timber.e(e)
if (!isJob) {
// 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)
}
}
}
/** /**
* Backup the categories of library * Backup the categories of library
* *

View File

@ -3,11 +3,7 @@ package eu.kanade.tachiyomi.ui.setting
import android.Manifest.permission.WRITE_EXTERNAL_STORAGE 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.*
import android.content.BroadcastReceiver
import android.content.Context
import android.content.Intent
import android.content.IntentFilter
import android.net.Uri import android.net.Uri
import android.os.Build import android.os.Build
import android.os.Bundle import android.os.Bundle
@ -15,22 +11,17 @@ import android.support.v7.preference.PreferenceScreen
import android.view.View import android.view.View
import com.afollestad.materialdialogs.MaterialDialog import com.afollestad.materialdialogs.MaterialDialog
import com.hippo.unifile.UniFile import com.hippo.unifile.UniFile
import com.nononsenseapps.filepicker.FilePickerActivity
import eu.kanade.tachiyomi.R import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.data.backup.BackupConst 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
import eu.kanade.tachiyomi.data.backup.models.Backup import eu.kanade.tachiyomi.data.backup.models.Backup
import eu.kanade.tachiyomi.data.preference.PreferencesHelper
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.ui.base.controller.popControllerWithTag import eu.kanade.tachiyomi.ui.base.controller.popControllerWithTag
import eu.kanade.tachiyomi.ui.base.controller.requestPermissionsSafe import eu.kanade.tachiyomi.ui.base.controller.requestPermissionsSafe
import eu.kanade.tachiyomi.util.* import eu.kanade.tachiyomi.util.*
import eu.kanade.tachiyomi.widget.CustomLayoutPickerActivity
import uy.kohesive.injekt.Injekt
import uy.kohesive.injekt.api.get
import java.io.File import java.io.File
import java.util.concurrent.TimeUnit import java.util.concurrent.TimeUnit
import eu.kanade.tachiyomi.data.preference.PreferenceKeys as Keys import eu.kanade.tachiyomi.data.preference.PreferenceKeys as Keys