mirror of
https://github.com/null2264/yokai.git
synced 2025-06-21 10:44:42 +00:00
More updates to backup logic from upstream
Removed BackupCreateService among other things
This commit is contained in:
parent
3587feb8fa
commit
ce3a774e05
9 changed files with 173 additions and 238 deletions
|
@ -235,10 +235,6 @@
|
|||
android:name=".data.updater.AppUpdateService"
|
||||
android:exported="false" />
|
||||
|
||||
<service
|
||||
android:name=".data.backup.BackupCreateService"
|
||||
android:exported="false"/>
|
||||
|
||||
<service
|
||||
android:name=".data.backup.BackupRestoreService"
|
||||
android:exported="false"/>
|
||||
|
|
|
@ -23,7 +23,7 @@ abstract class AbstractBackupManager(protected val context: Context) {
|
|||
protected val preferences: PreferencesHelper by injectLazy()
|
||||
protected val customMangaManager: CustomMangaManager by injectLazy()
|
||||
|
||||
abstract fun createBackup(uri: Uri, flags: Int, isJob: Boolean): String?
|
||||
abstract fun createBackup(uri: Uri, flags: Int, isAutoBackup: Boolean): String?
|
||||
|
||||
/**
|
||||
* Returns manga
|
||||
|
|
|
@ -11,4 +11,17 @@ object BackupConst {
|
|||
|
||||
const val BACKUP_TYPE_LEGACY = 0
|
||||
const val BACKUP_TYPE_FULL = 1
|
||||
|
||||
// Filter options
|
||||
internal const val BACKUP_CATEGORY = 0x1
|
||||
internal const val BACKUP_CATEGORY_MASK = 0x1
|
||||
internal const val BACKUP_CHAPTER = 0x2
|
||||
internal const val BACKUP_CHAPTER_MASK = 0x2
|
||||
internal const val BACKUP_HISTORY = 0x4
|
||||
internal const val BACKUP_HISTORY_MASK = 0x4
|
||||
internal const val BACKUP_TRACK = 0x8
|
||||
internal const val BACKUP_TRACK_MASK = 0x8
|
||||
internal const val BACKUP_CUSTOM_INFO = 0x10
|
||||
internal const val BACKUP_CUSTOM_INFO_MASK = 0x10
|
||||
internal const val BACKUP_ALL = 0x1F
|
||||
}
|
||||
|
|
|
@ -1,116 +0,0 @@
|
|||
package eu.kanade.tachiyomi.data.backup
|
||||
|
||||
import android.app.Service
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.net.Uri
|
||||
import android.os.IBinder
|
||||
import android.os.PowerManager
|
||||
import androidx.core.content.ContextCompat
|
||||
import androidx.core.net.toUri
|
||||
import com.hippo.unifile.UniFile
|
||||
import eu.kanade.tachiyomi.data.backup.full.FullBackupManager
|
||||
import eu.kanade.tachiyomi.data.notification.Notifications
|
||||
import eu.kanade.tachiyomi.util.system.acquireWakeLock
|
||||
import eu.kanade.tachiyomi.util.system.isServiceRunning
|
||||
|
||||
/**
|
||||
* Service for backing up library information to a JSON file.
|
||||
*/
|
||||
class BackupCreateService : Service() {
|
||||
|
||||
companion object {
|
||||
// Filter options
|
||||
internal const val BACKUP_CATEGORY = 0x1
|
||||
internal const val BACKUP_CATEGORY_MASK = 0x1
|
||||
internal const val BACKUP_CHAPTER = 0x2
|
||||
internal const val BACKUP_CHAPTER_MASK = 0x2
|
||||
internal const val BACKUP_HISTORY = 0x4
|
||||
internal const val BACKUP_HISTORY_MASK = 0x4
|
||||
internal const val BACKUP_TRACK = 0x8
|
||||
internal const val BACKUP_TRACK_MASK = 0x8
|
||||
internal const val BACKUP_CUSTOM_INFO = 0x10
|
||||
internal const val BACKUP_CUSTOM_INFO_MASK = 0x10
|
||||
internal const val BACKUP_ALL = 0x1F
|
||||
|
||||
/**
|
||||
* 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
|
||||
*
|
||||
* @param context context of application
|
||||
* @param uri path of Uri
|
||||
* @param flags determines what to backup
|
||||
*/
|
||||
fun start(context: Context, uri: Uri, flags: Int) {
|
||||
if (!isRunning(context)) {
|
||||
val intent = Intent(context, BackupCreateService::class.java).apply {
|
||||
putExtra(BackupConst.EXTRA_URI, uri)
|
||||
putExtra(BackupConst.EXTRA_FLAGS, flags)
|
||||
}
|
||||
ContextCompat.startForegroundService(context, intent)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Wake lock that will be held until the service is destroyed.
|
||||
*/
|
||||
private lateinit var wakeLock: PowerManager.WakeLock
|
||||
|
||||
private lateinit var notifier: BackupNotifier
|
||||
|
||||
override fun onCreate() {
|
||||
super.onCreate()
|
||||
|
||||
notifier = BackupNotifier(this)
|
||||
wakeLock = acquireWakeLock()
|
||||
|
||||
startForeground(Notifications.ID_BACKUP_PROGRESS, notifier.showBackupProgress().build())
|
||||
}
|
||||
|
||||
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)
|
||||
val backupFileUri = FullBackupManager(this).createBackup(uri!!, backupFlags, false)?.toUri()
|
||||
val unifile = UniFile.fromUri(this, backupFileUri)
|
||||
notifier.showBackupComplete(unifile)
|
||||
} catch (e: Exception) {
|
||||
notifier.showBackupError(e.message)
|
||||
}
|
||||
|
||||
stopSelf(startId)
|
||||
return START_NOT_STICKY
|
||||
}
|
||||
}
|
|
@ -1,14 +1,23 @@
|
|||
package eu.kanade.tachiyomi.data.backup
|
||||
|
||||
import android.content.Context
|
||||
import android.net.Uri
|
||||
import androidx.core.net.toUri
|
||||
import androidx.work.ExistingPeriodicWorkPolicy
|
||||
import androidx.work.ExistingWorkPolicy
|
||||
import androidx.work.OneTimeWorkRequestBuilder
|
||||
import androidx.work.PeriodicWorkRequestBuilder
|
||||
import androidx.work.WorkInfo
|
||||
import androidx.work.WorkManager
|
||||
import androidx.work.Worker
|
||||
import androidx.work.WorkerParameters
|
||||
import androidx.work.workDataOf
|
||||
import com.hippo.unifile.UniFile
|
||||
import eu.kanade.tachiyomi.data.backup.full.FullBackupManager
|
||||
import eu.kanade.tachiyomi.data.notification.Notifications
|
||||
import eu.kanade.tachiyomi.data.preference.PreferencesHelper
|
||||
import eu.kanade.tachiyomi.util.system.notificationManager
|
||||
import timber.log.Timber
|
||||
import uy.kohesive.injekt.Injekt
|
||||
import uy.kohesive.injekt.api.get
|
||||
import java.util.concurrent.TimeUnit
|
||||
|
@ -18,36 +27,71 @@ class BackupCreatorJob(private val context: Context, workerParams: WorkerParamet
|
|||
|
||||
override fun doWork(): Result {
|
||||
val preferences = Injekt.get<PreferencesHelper>()
|
||||
val uri = preferences.backupsDirectory().get().toUri()
|
||||
val flags = BackupCreateService.BACKUP_ALL
|
||||
val notifier = BackupNotifier(context)
|
||||
val uri = inputData.getString(LOCATION_URI_KEY)?.let { Uri.parse(it) }
|
||||
?: preferences.backupsDirectory().get().toUri()
|
||||
val flags = inputData.getInt(BACKUP_FLAGS_KEY, BackupConst.BACKUP_ALL)
|
||||
val isAutoBackup = inputData.getBoolean(IS_AUTO_BACKUP_KEY, true)
|
||||
|
||||
context.notificationManager.notify(Notifications.ID_BACKUP_PROGRESS, notifier.showBackupProgress().build())
|
||||
return try {
|
||||
FullBackupManager(context).createBackup(uri, flags, true)
|
||||
val location = FullBackupManager(context).createBackup(uri, flags, isAutoBackup)
|
||||
if (!isAutoBackup) notifier.showBackupComplete(UniFile.fromUri(context, location.toUri()))
|
||||
Result.success()
|
||||
} catch (e: Exception) {
|
||||
Timber.e(e)
|
||||
if (!isAutoBackup) notifier.showBackupError(e.message)
|
||||
Result.failure()
|
||||
} finally {
|
||||
context.notificationManager.cancel(Notifications.ID_BACKUP_PROGRESS)
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
private const val TAG = "BackupCreator"
|
||||
fun isManualJobRunning(context: Context): Boolean {
|
||||
val list = WorkManager.getInstance(context).getWorkInfosByTag(TAG_MANUAL).get()
|
||||
return list.find { it.state == WorkInfo.State.RUNNING } != null
|
||||
}
|
||||
|
||||
fun setupTask(context: Context, prefInterval: Int? = null) {
|
||||
val preferences = Injekt.get<PreferencesHelper>()
|
||||
val interval = prefInterval ?: preferences.backupInterval().get()
|
||||
val workManager = WorkManager.getInstance(context)
|
||||
if (interval > 0) {
|
||||
val request = PeriodicWorkRequestBuilder<BackupCreatorJob>(
|
||||
interval.toLong(),
|
||||
TimeUnit.HOURS,
|
||||
10,
|
||||
TimeUnit.MINUTES
|
||||
TimeUnit.MINUTES,
|
||||
)
|
||||
.addTag(TAG)
|
||||
.addTag(TAG_AUTO)
|
||||
.setInputData(workDataOf(IS_AUTO_BACKUP_KEY to true))
|
||||
.build()
|
||||
|
||||
WorkManager.getInstance(context).enqueueUniquePeriodicWork(TAG, ExistingPeriodicWorkPolicy.REPLACE, request)
|
||||
workManager.enqueueUniquePeriodicWork(TAG_AUTO, ExistingPeriodicWorkPolicy.REPLACE, request)
|
||||
} else {
|
||||
WorkManager.getInstance(context).cancelAllWorkByTag(TAG)
|
||||
workManager.cancelUniqueWork(TAG_AUTO)
|
||||
}
|
||||
}
|
||||
|
||||
fun startNow(context: Context, uri: Uri, flags: Int) {
|
||||
val inputData = workDataOf(
|
||||
IS_AUTO_BACKUP_KEY to false,
|
||||
LOCATION_URI_KEY to uri.toString(),
|
||||
BACKUP_FLAGS_KEY to flags,
|
||||
)
|
||||
val request = OneTimeWorkRequestBuilder<BackupCreatorJob>()
|
||||
.addTag(TAG_MANUAL)
|
||||
.setInputData(inputData)
|
||||
.build()
|
||||
WorkManager.getInstance(context).enqueueUniqueWork(TAG_MANUAL, ExistingWorkPolicy.KEEP, request)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private const val TAG_AUTO = "BackupCreator"
|
||||
private const val TAG_MANUAL = "$TAG_AUTO:manual"
|
||||
|
||||
private const val IS_AUTO_BACKUP_KEY = "is_auto_backup" // Boolean
|
||||
private const val LOCATION_URI_KEY = "location_uri" // String
|
||||
private const val BACKUP_FLAGS_KEY = "backup_flags" // Int
|
||||
|
|
|
@ -5,16 +5,16 @@ import android.net.Uri
|
|||
import com.hippo.unifile.UniFile
|
||||
import eu.kanade.tachiyomi.R
|
||||
import eu.kanade.tachiyomi.data.backup.AbstractBackupManager
|
||||
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_CHAPTER
|
||||
import eu.kanade.tachiyomi.data.backup.BackupCreateService.Companion.BACKUP_CHAPTER_MASK
|
||||
import eu.kanade.tachiyomi.data.backup.BackupCreateService.Companion.BACKUP_CUSTOM_INFO
|
||||
import eu.kanade.tachiyomi.data.backup.BackupCreateService.Companion.BACKUP_CUSTOM_INFO_MASK
|
||||
import eu.kanade.tachiyomi.data.backup.BackupCreateService.Companion.BACKUP_HISTORY
|
||||
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_MASK
|
||||
import eu.kanade.tachiyomi.data.backup.BackupConst.BACKUP_CATEGORY
|
||||
import eu.kanade.tachiyomi.data.backup.BackupConst.BACKUP_CATEGORY_MASK
|
||||
import eu.kanade.tachiyomi.data.backup.BackupConst.BACKUP_CHAPTER
|
||||
import eu.kanade.tachiyomi.data.backup.BackupConst.BACKUP_CHAPTER_MASK
|
||||
import eu.kanade.tachiyomi.data.backup.BackupConst.BACKUP_CUSTOM_INFO
|
||||
import eu.kanade.tachiyomi.data.backup.BackupConst.BACKUP_CUSTOM_INFO_MASK
|
||||
import eu.kanade.tachiyomi.data.backup.BackupConst.BACKUP_HISTORY
|
||||
import eu.kanade.tachiyomi.data.backup.BackupConst.BACKUP_HISTORY_MASK
|
||||
import eu.kanade.tachiyomi.data.backup.BackupConst.BACKUP_TRACK
|
||||
import eu.kanade.tachiyomi.data.backup.BackupConst.BACKUP_TRACK_MASK
|
||||
import eu.kanade.tachiyomi.data.backup.full.models.Backup
|
||||
import eu.kanade.tachiyomi.data.backup.full.models.BackupCategory
|
||||
import eu.kanade.tachiyomi.data.backup.full.models.BackupChapter
|
||||
|
@ -34,6 +34,7 @@ import okio.buffer
|
|||
import okio.gzip
|
||||
import okio.sink
|
||||
import timber.log.Timber
|
||||
import java.io.FileOutputStream
|
||||
import kotlin.math.max
|
||||
|
||||
class FullBackupManager(context: Context) : AbstractBackupManager(context) {
|
||||
|
@ -44,9 +45,9 @@ class FullBackupManager(context: Context) : AbstractBackupManager(context) {
|
|||
* Create backup Json file from database
|
||||
*
|
||||
* @param uri path of Uri
|
||||
* @param isJob backup called from job
|
||||
* @param isAutoBackup backup called from scheduled backup job
|
||||
*/
|
||||
override fun createBackup(uri: Uri, flags: Int, isJob: Boolean): String? {
|
||||
override fun createBackup(uri: Uri, flags: Int, isAutoBackup: Boolean): String {
|
||||
// Create root object
|
||||
var backup: Backup? = null
|
||||
|
||||
|
@ -57,13 +58,14 @@ class FullBackupManager(context: Context) : AbstractBackupManager(context) {
|
|||
backupManga(databaseManga, flags),
|
||||
backupCategories(),
|
||||
emptyList(),
|
||||
backupExtensionInfo(databaseManga)
|
||||
backupExtensionInfo(databaseManga),
|
||||
)
|
||||
}
|
||||
|
||||
var file: UniFile? = null
|
||||
try {
|
||||
val file: UniFile = (
|
||||
if (isJob) {
|
||||
file = (
|
||||
if (isAutoBackup) {
|
||||
// Get dir of file and create
|
||||
var dir = UniFile.fromUri(context, uri)
|
||||
dir = dir.createDirectory("automatic")
|
||||
|
@ -85,15 +87,28 @@ class FullBackupManager(context: Context) : AbstractBackupManager(context) {
|
|||
)
|
||||
?: throw Exception("Couldn't create backup file")
|
||||
|
||||
if (!file.isFile) {
|
||||
throw IllegalStateException("Failed to get handle on file")
|
||||
}
|
||||
|
||||
val byteArray = parser.encodeToByteArray(BackupSerializer, backup!!)
|
||||
if (byteArray.isEmpty()) {
|
||||
throw IllegalStateException(context.getString(R.string.empty_backup_error))
|
||||
}
|
||||
|
||||
file.openOutputStream().sink().gzip().buffer().use { it.write(byteArray) }
|
||||
return file.uri.toString()
|
||||
file.openOutputStream().also {
|
||||
// Force overwrite old file
|
||||
(it as? FileOutputStream)?.channel?.truncate(0)
|
||||
}.sink().gzip().buffer().use { it.write(byteArray) }
|
||||
val fileUri = file.uri
|
||||
|
||||
// Make sure it's a valid backup file
|
||||
FullBackupRestoreValidator().validate(context, fileUri)
|
||||
|
||||
return fileUri.toString()
|
||||
} catch (e: Exception) {
|
||||
Timber.e(e)
|
||||
file?.delete()
|
||||
throw e
|
||||
}
|
||||
}
|
||||
|
@ -285,7 +300,7 @@ class FullBackupManager(context: Context) : AbstractBackupManager(context) {
|
|||
}
|
||||
}
|
||||
}
|
||||
databaseHelper.updateHistoryLastRead(historyToBeUpdated).executeAsBlocking()
|
||||
databaseHelper.upsertHistoryLastRead(historyToBeUpdated).executeAsBlocking()
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -6,34 +6,34 @@ import android.content.ActivityNotFoundException
|
|||
import android.content.Intent
|
||||
import android.net.Uri
|
||||
import android.os.Bundle
|
||||
import android.view.Menu
|
||||
import android.view.MenuInflater
|
||||
import android.view.MenuItem
|
||||
import android.view.View
|
||||
import android.widget.Toast
|
||||
import androidx.appcompat.app.AlertDialog
|
||||
import androidx.core.net.toUri
|
||||
import androidx.core.os.bundleOf
|
||||
import androidx.documentfile.provider.DocumentFile
|
||||
import androidx.preference.PreferenceScreen
|
||||
import com.hippo.unifile.UniFile
|
||||
import eu.kanade.tachiyomi.R
|
||||
import eu.kanade.tachiyomi.data.backup.BackupConst
|
||||
import eu.kanade.tachiyomi.data.backup.BackupCreateService
|
||||
import eu.kanade.tachiyomi.data.backup.BackupCreatorJob
|
||||
import eu.kanade.tachiyomi.data.backup.BackupRestoreService
|
||||
import eu.kanade.tachiyomi.data.backup.ValidatorParseException
|
||||
import eu.kanade.tachiyomi.data.backup.full.FullBackupRestoreValidator
|
||||
import eu.kanade.tachiyomi.data.backup.full.models.BackupFull
|
||||
import eu.kanade.tachiyomi.data.backup.legacy.LegacyBackupRestoreValidator
|
||||
import eu.kanade.tachiyomi.data.preference.asImmediateFlow
|
||||
import eu.kanade.tachiyomi.ui.base.controller.DialogController
|
||||
import eu.kanade.tachiyomi.ui.main.MainActivity
|
||||
import eu.kanade.tachiyomi.util.system.MiuiUtil
|
||||
import eu.kanade.tachiyomi.util.system.disableItems
|
||||
import eu.kanade.tachiyomi.util.system.getFilePicker
|
||||
import eu.kanade.tachiyomi.util.system.materialAlertDialog
|
||||
import eu.kanade.tachiyomi.util.system.openInBrowser
|
||||
import eu.kanade.tachiyomi.util.system.toast
|
||||
import eu.kanade.tachiyomi.util.view.requestFilePermissionsSafe
|
||||
import kotlinx.coroutines.flow.launchIn
|
||||
import kotlinx.coroutines.flow.onEach
|
||||
import eu.kanade.tachiyomi.data.preference.PreferenceKeys as Keys
|
||||
|
||||
class SettingsBackupController : SettingsController() {
|
||||
|
||||
|
@ -60,7 +60,7 @@ class SettingsBackupController : SettingsController() {
|
|||
context.toast(R.string.restore_miui_warning, Toast.LENGTH_LONG)
|
||||
}
|
||||
|
||||
if (!BackupCreateService.isRunning(context)) {
|
||||
if (!BackupCreatorJob.isManualJobRunning(context)) {
|
||||
val ctrl = CreateBackupDialog()
|
||||
ctrl.targetController = this@SettingsBackupController
|
||||
ctrl.showDialog(router)
|
||||
|
@ -83,7 +83,7 @@ class SettingsBackupController : SettingsController() {
|
|||
(activity as? MainActivity)?.getExtensionUpdates(true)
|
||||
val intent = Intent(Intent.ACTION_GET_CONTENT)
|
||||
intent.addCategory(Intent.CATEGORY_OPENABLE)
|
||||
intent.type = "application/*"
|
||||
intent.type = "*/*"
|
||||
val title = resources?.getString(R.string.select_backup_file)
|
||||
val chooser = Intent.createChooser(intent, title)
|
||||
startActivityForResult(chooser, CODE_BACKUP_RESTORE)
|
||||
|
@ -97,7 +97,7 @@ class SettingsBackupController : SettingsController() {
|
|||
titleRes = R.string.automatic_backups
|
||||
|
||||
intListPreference(activity) {
|
||||
key = Keys.backupInterval
|
||||
bindTo(preferences.backupInterval())
|
||||
titleRes = R.string.backup_frequency
|
||||
entriesRes = arrayOf(
|
||||
R.string.manual,
|
||||
|
@ -108,109 +108,88 @@ class SettingsBackupController : SettingsController() {
|
|||
R.string.weekly
|
||||
)
|
||||
entryValues = listOf(0, 6, 12, 24, 48, 168)
|
||||
defaultValue = 0
|
||||
|
||||
onChange { newValue ->
|
||||
// Always cancel the previous task, it seems that sometimes they are not updated
|
||||
|
||||
val interval = newValue as Int
|
||||
BackupCreatorJob.setupTask(context, interval)
|
||||
true
|
||||
}
|
||||
}
|
||||
preference {
|
||||
key = Keys.backupDirectory
|
||||
bindTo(preferences.backupsDirectory())
|
||||
titleRes = R.string.backup_location
|
||||
|
||||
onClick {
|
||||
val currentDir = preferences.backupsDirectory().get()
|
||||
try {
|
||||
val intent = Intent(Intent.ACTION_OPEN_DOCUMENT_TREE)
|
||||
startActivityForResult(intent, CODE_BACKUP_DIR)
|
||||
} catch (e: ActivityNotFoundException) {
|
||||
// Fall back to custom picker on error
|
||||
startActivityForResult(preferences.context.getFilePicker(currentDir), CODE_BACKUP_DIR)
|
||||
activity?.toast(R.string.file_picker_error)
|
||||
}
|
||||
}
|
||||
|
||||
visibleIf(preferences.backupInterval()) { it > 0 }
|
||||
|
||||
preferences.backupsDirectory().asFlow()
|
||||
.onEach { path ->
|
||||
val dir = UniFile.fromUri(context, path.toUri())
|
||||
summary = dir.filePath + "/automatic"
|
||||
}
|
||||
.launchIn(viewScope)
|
||||
preferences.backupInterval().asImmediateFlow { isVisible = it > 0 }
|
||||
.launchIn(viewScope)
|
||||
}
|
||||
intListPreference(activity) {
|
||||
key = Keys.numberOfBackups
|
||||
bindTo(preferences.numberOfBackups())
|
||||
titleRes = R.string.max_auto_backups
|
||||
entries = listOf("1", "2", "3", "4", "5")
|
||||
entries = (1..5).map(Int::toString)
|
||||
entryRange = 1..5
|
||||
defaultValue = 1
|
||||
|
||||
preferences.backupInterval().asImmediateFlow { isVisible = it > 0 }
|
||||
.launchIn(viewScope)
|
||||
visibleIf(preferences.backupInterval()) { it > 0 }
|
||||
}
|
||||
}
|
||||
|
||||
infoPreference(R.string.backup_info)
|
||||
}
|
||||
|
||||
override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) {
|
||||
inflater.inflate(R.menu.settings_backup, menu)
|
||||
}
|
||||
|
||||
override fun onOptionsItemSelected(item: MenuItem): Boolean {
|
||||
when (item.itemId) {
|
||||
R.id.action_backup_help -> activity?.openInBrowser(HELP_URL)
|
||||
}
|
||||
return super.onOptionsItemSelected(item)
|
||||
}
|
||||
|
||||
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
|
||||
if (data != null && resultCode == Activity.RESULT_OK) {
|
||||
val activity = activity ?: return
|
||||
val uri = data.data
|
||||
|
||||
if (uri == null) {
|
||||
activity.toast(R.string.backup_restore_invalid_uri)
|
||||
return
|
||||
}
|
||||
|
||||
when (requestCode) {
|
||||
CODE_BACKUP_DIR -> {
|
||||
// Get UriPermission so it's possible to write files
|
||||
val flags = Intent.FLAG_GRANT_READ_URI_PERMISSION or
|
||||
Intent.FLAG_GRANT_WRITE_URI_PERMISSION
|
||||
|
||||
if (uri != null) {
|
||||
activity.contentResolver.takePersistableUriPermission(uri, flags)
|
||||
}
|
||||
|
||||
// Set backup Uri
|
||||
activity.contentResolver.takePersistableUriPermission(uri, flags)
|
||||
preferences.backupsDirectory().set(uri.toString())
|
||||
}
|
||||
CODE_BACKUP_CREATE -> {
|
||||
val flags = Intent.FLAG_GRANT_READ_URI_PERMISSION or
|
||||
Intent.FLAG_GRANT_WRITE_URI_PERMISSION
|
||||
|
||||
if (uri != null) {
|
||||
activity.contentResolver.takePersistableUriPermission(uri, flags)
|
||||
}
|
||||
|
||||
val file = UniFile.fromUri(activity, uri)
|
||||
|
||||
activity.contentResolver.takePersistableUriPermission(uri, flags)
|
||||
activity.toast(R.string.creating_backup)
|
||||
|
||||
BackupCreateService.start(
|
||||
activity,
|
||||
file.uri,
|
||||
backupFlags,
|
||||
)
|
||||
BackupCreatorJob.startNow(activity, uri, backupFlags)
|
||||
}
|
||||
CODE_BACKUP_RESTORE -> {
|
||||
uri?.path?.let {
|
||||
val fileName = DocumentFile.fromSingleUri(activity, uri)?.name ?: uri.toString()
|
||||
when {
|
||||
fileName.endsWith(".proto.gz") -> {
|
||||
RestoreBackupDialog(
|
||||
uri,
|
||||
BackupConst.BACKUP_TYPE_FULL
|
||||
).showDialog(router)
|
||||
}
|
||||
fileName.endsWith(".json") -> {
|
||||
RestoreBackupDialog(
|
||||
uri,
|
||||
BackupConst.BACKUP_TYPE_LEGACY
|
||||
).showDialog(router)
|
||||
}
|
||||
else -> {
|
||||
activity.toast(activity.getString(R.string.invalid_backup_file_type, fileName))
|
||||
}
|
||||
}
|
||||
}
|
||||
RestoreBackupDialog(uri).showDialog(router)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -218,7 +197,6 @@ class SettingsBackupController : SettingsController() {
|
|||
|
||||
fun createBackup(flags: Int) {
|
||||
backupFlags = flags
|
||||
|
||||
try {
|
||||
// Use Android's built-in file creator
|
||||
val intent = Intent(Intent.ACTION_CREATE_DOCUMENT)
|
||||
|
@ -233,7 +211,6 @@ class SettingsBackupController : SettingsController() {
|
|||
}
|
||||
|
||||
class CreateBackupDialog(bundle: Bundle? = null) : DialogController(bundle) {
|
||||
|
||||
override fun onCreateDialog(savedViewState: Bundle?): Dialog {
|
||||
val activity = activity!!
|
||||
val options = arrayOf(
|
||||
|
@ -263,11 +240,11 @@ class SettingsBackupController : SettingsController() {
|
|||
for (i in 1 until listView.count) {
|
||||
if (listView.isItemChecked(i)) {
|
||||
when (i) {
|
||||
1 -> flags = flags or BackupCreateService.BACKUP_CATEGORY
|
||||
2 -> flags = flags or BackupCreateService.BACKUP_CHAPTER
|
||||
3 -> flags = flags or BackupCreateService.BACKUP_TRACK
|
||||
4 -> flags = flags or BackupCreateService.BACKUP_HISTORY
|
||||
5 -> flags = flags or BackupCreateService.BACKUP_CUSTOM_INFO
|
||||
1 -> flags = flags or BackupConst.BACKUP_CATEGORY
|
||||
2 -> flags = flags or BackupConst.BACKUP_CHAPTER
|
||||
3 -> flags = flags or BackupConst.BACKUP_TRACK
|
||||
4 -> flags = flags or BackupConst.BACKUP_HISTORY
|
||||
5 -> flags = flags or BackupConst.BACKUP_CUSTOM_INFO
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -281,32 +258,28 @@ class SettingsBackupController : SettingsController() {
|
|||
}
|
||||
|
||||
class RestoreBackupDialog(bundle: Bundle? = null) : DialogController(bundle) {
|
||||
constructor(uri: Uri, type: Int) : this(
|
||||
bundleOf(
|
||||
KEY_URI to uri,
|
||||
KEY_TYPE to type
|
||||
)
|
||||
constructor(uri: Uri) : this(
|
||||
bundleOf(KEY_URI to uri),
|
||||
)
|
||||
|
||||
override fun onCreateDialog(savedViewState: Bundle?): Dialog {
|
||||
val activity = activity!!
|
||||
val uri: Uri = args.getParcelable(KEY_URI)!!
|
||||
val type: Int = args.getInt(KEY_TYPE)
|
||||
|
||||
return try {
|
||||
var type = BackupConst.BACKUP_TYPE_FULL
|
||||
val results = try {
|
||||
FullBackupRestoreValidator().validate(activity, uri)
|
||||
} catch (_: ValidatorParseException) {
|
||||
type = BackupConst.BACKUP_TYPE_LEGACY
|
||||
LegacyBackupRestoreValidator().validate(activity, uri)
|
||||
}
|
||||
|
||||
var message = if (type == BackupConst.BACKUP_TYPE_FULL) {
|
||||
activity.getString(R.string.restore_content_full)
|
||||
} else {
|
||||
activity.getString(R.string.restore_content)
|
||||
}
|
||||
|
||||
val validator = if (type == BackupConst.BACKUP_TYPE_FULL) {
|
||||
FullBackupRestoreValidator()
|
||||
} else {
|
||||
LegacyBackupRestoreValidator()
|
||||
}
|
||||
|
||||
val results = validator.validate(activity, uri)
|
||||
if (results.missingSources.isNotEmpty()) {
|
||||
message += "\n\n${activity.getString(R.string.restore_missing_sources)}\n${results.missingSources.joinToString("\n") { "- $it" }}"
|
||||
}
|
||||
|
@ -332,16 +305,13 @@ class SettingsBackupController : SettingsController() {
|
|||
.create()
|
||||
}
|
||||
}
|
||||
|
||||
private companion object {
|
||||
const val KEY_URI = "RestoreBackupDialog.uri"
|
||||
const val KEY_TYPE = "RestoreBackupDialog.type"
|
||||
}
|
||||
}
|
||||
|
||||
private companion object {
|
||||
const val CODE_BACKUP_DIR = 503
|
||||
const val CODE_BACKUP_CREATE = 504
|
||||
const val CODE_BACKUP_RESTORE = 505
|
||||
}
|
||||
}
|
||||
|
||||
private const val KEY_URI = "RestoreBackupDialog.uri"
|
||||
|
||||
private const val CODE_BACKUP_DIR = 503
|
||||
private const val CODE_BACKUP_CREATE = 504
|
||||
private const val CODE_BACKUP_RESTORE = 505
|
||||
|
||||
private const val HELP_URL = "https://tachiyomi.org/help/guides/backups/"
|
||||
|
|
11
app/src/main/res/menu/settings_backup.xml
Normal file
11
app/src/main/res/menu/settings_backup.xml
Normal file
|
@ -0,0 +1,11 @@
|
|||
<menu xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto">
|
||||
|
||||
<item
|
||||
android:id="@+id/action_backup_help"
|
||||
android:icon="@drawable/ic_help_24dp"
|
||||
android:title="@string/help"
|
||||
app:iconTint="?attr/colorOnSurface"
|
||||
app:showAsAction="ifRoom" />
|
||||
|
||||
</menu>
|
|
@ -758,7 +758,9 @@
|
|||
<string name="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="restore_content_full">Data from the backup file will be restored.\n\nYou will need to install any missing extensions and log in to tracking services afterwards to use them.</string>
|
||||
<string name="restoring_backup_canceled">Canceled restore</string>
|
||||
<string name="backup_restore_invalid_uri">Error: empty URI</string>
|
||||
<string name="creating_backup">Creating backup</string>
|
||||
<string name="backup_info">Automatic backups are highly recommended. You should keep copies in other places as well.</string>
|
||||
<string name="what_should_backup">What do you want to backup?</string>
|
||||
<string name="restoring_backup">Restoring backup</string>
|
||||
<string name="restore_duration">%02d min, %02d sec</string>
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue