diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index 975efea38a..cf664d86ff 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -235,10 +235,6 @@
android:name=".data.updater.AppUpdateService"
android:exported="false" />
-
-
diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/backup/AbstractBackupManager.kt b/app/src/main/java/eu/kanade/tachiyomi/data/backup/AbstractBackupManager.kt
index 5ee002a949..e88e1ea1c7 100644
--- a/app/src/main/java/eu/kanade/tachiyomi/data/backup/AbstractBackupManager.kt
+++ b/app/src/main/java/eu/kanade/tachiyomi/data/backup/AbstractBackupManager.kt
@@ -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
diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/backup/BackupConst.kt b/app/src/main/java/eu/kanade/tachiyomi/data/backup/BackupConst.kt
index d168e2c3d9..15ab1399b8 100644
--- a/app/src/main/java/eu/kanade/tachiyomi/data/backup/BackupConst.kt
+++ b/app/src/main/java/eu/kanade/tachiyomi/data/backup/BackupConst.kt
@@ -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
}
diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/backup/BackupCreateService.kt b/app/src/main/java/eu/kanade/tachiyomi/data/backup/BackupCreateService.kt
deleted file mode 100644
index be4bbc919f..0000000000
--- a/app/src/main/java/eu/kanade/tachiyomi/data/backup/BackupCreateService.kt
+++ /dev/null
@@ -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(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
- }
-}
diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/backup/BackupCreatorJob.kt b/app/src/main/java/eu/kanade/tachiyomi/data/backup/BackupCreatorJob.kt
index 7953906d51..850e0a3697 100644
--- a/app/src/main/java/eu/kanade/tachiyomi/data/backup/BackupCreatorJob.kt
+++ b/app/src/main/java/eu/kanade/tachiyomi/data/backup/BackupCreatorJob.kt
@@ -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()
- 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()
val interval = prefInterval ?: preferences.backupInterval().get()
+ val workManager = WorkManager.getInstance(context)
if (interval > 0) {
val request = PeriodicWorkRequestBuilder(
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()
+ .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
diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/backup/full/FullBackupManager.kt b/app/src/main/java/eu/kanade/tachiyomi/data/backup/full/FullBackupManager.kt
index e065e932c1..e589e682ca 100644
--- a/app/src/main/java/eu/kanade/tachiyomi/data/backup/full/FullBackupManager.kt
+++ b/app/src/main/java/eu/kanade/tachiyomi/data/backup/full/FullBackupManager.kt
@@ -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()
}
/**
diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/setting/SettingsBackupController.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/setting/SettingsBackupController.kt
index 4b2daa2941..55d7da3d33 100644
--- a/app/src/main/java/eu/kanade/tachiyomi/ui/setting/SettingsBackupController.kt
+++ b/app/src/main/java/eu/kanade/tachiyomi/ui/setting/SettingsBackupController.kt
@@ -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/"
diff --git a/app/src/main/res/menu/settings_backup.xml b/app/src/main/res/menu/settings_backup.xml
new file mode 100644
index 0000000000..abe064f4c8
--- /dev/null
+++ b/app/src/main/res/menu/settings_backup.xml
@@ -0,0 +1,11 @@
+
diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml
index 814d437ee6..8d71fe8a3b 100644
--- a/app/src/main/res/values/strings.xml
+++ b/app/src/main/res/values/strings.xml
@@ -758,7 +758,9 @@
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.
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.
Canceled restore
+ Error: empty URI
Creating backup
+ Automatic backups are highly recommended. You should keep copies in other places as well.
What do you want to backup?
Restoring backup
%02d min, %02d sec