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