Refactor backup and restore to support cross device sync. (#9699)
* refactor: backup and restore to support cross device sync. * chore: Updated string resources * refactor: change function name. * refactor: Use URI SyncHolder.kt not needed anymore.
This commit is contained in:
parent
46e3b9e40d
commit
7b2764e8f7
5 changed files with 37 additions and 21 deletions
|
@ -81,7 +81,7 @@ class BackupManager(
|
||||||
backupMangas(databaseManga, flags),
|
backupMangas(databaseManga, flags),
|
||||||
backupCategories(flags),
|
backupCategories(flags),
|
||||||
emptyList(),
|
emptyList(),
|
||||||
backupExtensionInfo(databaseManga),
|
prepExtensionInfoForSync(databaseManga),
|
||||||
)
|
)
|
||||||
|
|
||||||
var file: UniFile? = null
|
var file: UniFile? = null
|
||||||
|
@ -135,7 +135,7 @@ class BackupManager(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun backupExtensionInfo(mangas: List<Manga>): List<BackupSource> {
|
fun prepExtensionInfoForSync(mangas: List<Manga>): List<BackupSource> {
|
||||||
return mangas
|
return mangas
|
||||||
.asSequence()
|
.asSequence()
|
||||||
.map(Manga::source)
|
.map(Manga::source)
|
||||||
|
@ -150,7 +150,7 @@ class BackupManager(
|
||||||
*
|
*
|
||||||
* @return list of [BackupCategory] to be backed up
|
* @return list of [BackupCategory] to be backed up
|
||||||
*/
|
*/
|
||||||
private suspend fun backupCategories(options: Int): List<BackupCategory> {
|
suspend fun backupCategories(options: Int): List<BackupCategory> {
|
||||||
// Check if user wants category information in backup
|
// Check if user wants category information in backup
|
||||||
return if (options and BACKUP_CATEGORY_MASK == BACKUP_CATEGORY) {
|
return if (options and BACKUP_CATEGORY_MASK == BACKUP_CATEGORY) {
|
||||||
getCategories.await()
|
getCategories.await()
|
||||||
|
@ -161,7 +161,7 @@ class BackupManager(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private suspend fun backupMangas(mangas: List<Manga>, flags: Int): List<BackupManga> {
|
suspend fun backupMangas(mangas: List<Manga>, flags: Int): List<BackupManga> {
|
||||||
return mangas.map {
|
return mangas.map {
|
||||||
backupManga(it, flags)
|
backupManga(it, flags)
|
||||||
}
|
}
|
||||||
|
@ -514,7 +514,7 @@ class BackupManager(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private suspend fun updateManga(manga: Manga): Long {
|
suspend fun updateManga(manga: Manga): Long {
|
||||||
handler.await(true) {
|
handler.await(true) {
|
||||||
mangasQueries.update(
|
mangasQueries.update(
|
||||||
source = manga.source,
|
source = manga.source,
|
||||||
|
|
|
@ -79,9 +79,9 @@ class BackupNotifier(private val context: Context) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun showRestoreProgress(content: String = "", progress: Int = 0, maxAmount: Int = 100): NotificationCompat.Builder {
|
fun showRestoreProgress(content: String = "", contentTitle: String = context.getString(R.string.restoring_backup), progress: Int = 0, maxAmount: Int = 100): NotificationCompat.Builder {
|
||||||
val builder = with(progressNotificationBuilder) {
|
val builder = with(progressNotificationBuilder) {
|
||||||
setContentTitle(context.getString(R.string.restoring_backup))
|
setContentTitle(contentTitle)
|
||||||
|
|
||||||
if (!preferences.hideNotificationContent().get()) {
|
if (!preferences.hideNotificationContent().get()) {
|
||||||
setContentText(content)
|
setContentText(content)
|
||||||
|
@ -114,7 +114,7 @@ class BackupNotifier(private val context: Context) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun showRestoreComplete(time: Long, errorCount: Int, path: String?, file: String?) {
|
fun showRestoreComplete(time: Long, errorCount: Int, path: String?, file: String?, contentTitle: String = context.getString(R.string.restore_completed)) {
|
||||||
context.cancelNotification(Notifications.ID_RESTORE_PROGRESS)
|
context.cancelNotification(Notifications.ID_RESTORE_PROGRESS)
|
||||||
|
|
||||||
val timeString = context.getString(
|
val timeString = context.getString(
|
||||||
|
@ -126,7 +126,7 @@ class BackupNotifier(private val context: Context) {
|
||||||
)
|
)
|
||||||
|
|
||||||
with(completeNotificationBuilder) {
|
with(completeNotificationBuilder) {
|
||||||
setContentTitle(context.getString(R.string.restore_completed))
|
setContentTitle(contentTitle)
|
||||||
setContentText(context.resources.getQuantityString(R.plurals.restore_completed_message, errorCount, timeString, errorCount))
|
setContentText(context.resources.getQuantityString(R.plurals.restore_completed_message, errorCount, timeString, errorCount))
|
||||||
|
|
||||||
clearActions()
|
clearActions()
|
||||||
|
|
|
@ -26,6 +26,7 @@ class BackupRestoreJob(private val context: Context, workerParams: WorkerParamet
|
||||||
override suspend fun doWork(): Result {
|
override suspend fun doWork(): Result {
|
||||||
val uri = inputData.getString(LOCATION_URI_KEY)?.toUri()
|
val uri = inputData.getString(LOCATION_URI_KEY)?.toUri()
|
||||||
?: return Result.failure()
|
?: return Result.failure()
|
||||||
|
val sync = inputData.getBoolean(SYNC, false)
|
||||||
|
|
||||||
try {
|
try {
|
||||||
setForeground(getForegroundInfo())
|
setForeground(getForegroundInfo())
|
||||||
|
@ -35,7 +36,7 @@ class BackupRestoreJob(private val context: Context, workerParams: WorkerParamet
|
||||||
|
|
||||||
return try {
|
return try {
|
||||||
val restorer = BackupRestorer(context, notifier)
|
val restorer = BackupRestorer(context, notifier)
|
||||||
restorer.restoreBackup(uri)
|
restorer.syncFromBackup(uri, sync)
|
||||||
Result.success()
|
Result.success()
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
if (e is CancellationException) {
|
if (e is CancellationException) {
|
||||||
|
@ -63,9 +64,10 @@ class BackupRestoreJob(private val context: Context, workerParams: WorkerParamet
|
||||||
return context.workManager.isRunning(TAG)
|
return context.workManager.isRunning(TAG)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun start(context: Context, uri: Uri) {
|
fun start(context: Context, uri: Uri, sync: Boolean = false) {
|
||||||
val inputData = workDataOf(
|
val inputData = workDataOf(
|
||||||
LOCATION_URI_KEY to uri.toString(),
|
LOCATION_URI_KEY to uri.toString(),
|
||||||
|
SYNC to sync,
|
||||||
)
|
)
|
||||||
val request = OneTimeWorkRequestBuilder<BackupRestoreJob>()
|
val request = OneTimeWorkRequestBuilder<BackupRestoreJob>()
|
||||||
.addTag(TAG)
|
.addTag(TAG)
|
||||||
|
@ -83,3 +85,5 @@ class BackupRestoreJob(private val context: Context, workerParams: WorkerParamet
|
||||||
private const val TAG = "BackupRestore"
|
private const val TAG = "BackupRestore"
|
||||||
|
|
||||||
private const val LOCATION_URI_KEY = "location_uri" // String
|
private const val LOCATION_URI_KEY = "location_uri" // String
|
||||||
|
|
||||||
|
private const val SYNC = "sync" // Boolean
|
||||||
|
|
|
@ -36,12 +36,12 @@ class BackupRestorer(
|
||||||
|
|
||||||
private val errors = mutableListOf<Pair<Date, String>>()
|
private val errors = mutableListOf<Pair<Date, String>>()
|
||||||
|
|
||||||
suspend fun restoreBackup(uri: Uri): Boolean {
|
suspend fun syncFromBackup(uri: Uri, sync: Boolean): Boolean {
|
||||||
val startTime = System.currentTimeMillis()
|
val startTime = System.currentTimeMillis()
|
||||||
restoreProgress = 0
|
restoreProgress = 0
|
||||||
errors.clear()
|
errors.clear()
|
||||||
|
|
||||||
if (!performRestore(uri)) {
|
if (!performRestore(uri, sync)) {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -50,7 +50,11 @@ class BackupRestorer(
|
||||||
|
|
||||||
val logFile = writeErrorLog()
|
val logFile = writeErrorLog()
|
||||||
|
|
||||||
notifier.showRestoreComplete(time, errors.size, logFile.parent, logFile.name)
|
if (sync) {
|
||||||
|
notifier.showRestoreComplete(time, errors.size, logFile.parent, logFile.name, contentTitle = context.getString(R.string.library_sync_complete))
|
||||||
|
} else {
|
||||||
|
notifier.showRestoreComplete(time, errors.size, logFile.parent, logFile.name)
|
||||||
|
}
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -73,7 +77,7 @@ class BackupRestorer(
|
||||||
return File("")
|
return File("")
|
||||||
}
|
}
|
||||||
|
|
||||||
private suspend fun performRestore(uri: Uri): Boolean {
|
private suspend fun performRestore(uri: Uri, sync: Boolean): Boolean {
|
||||||
val backup = BackupUtil.decodeBackup(context, uri)
|
val backup = BackupUtil.decodeBackup(context, uri)
|
||||||
|
|
||||||
restoreAmount = backup.backupManga.size + 1 // +1 for categories
|
restoreAmount = backup.backupManga.size + 1 // +1 for categories
|
||||||
|
@ -94,7 +98,7 @@ class BackupRestorer(
|
||||||
return@coroutineScope false
|
return@coroutineScope false
|
||||||
}
|
}
|
||||||
|
|
||||||
restoreManga(it, backup.backupCategories)
|
restoreManga(it, backup.backupCategories, sync)
|
||||||
}
|
}
|
||||||
// TODO: optionally trigger online library + tracker update
|
// TODO: optionally trigger online library + tracker update
|
||||||
true
|
true
|
||||||
|
@ -105,10 +109,10 @@ class BackupRestorer(
|
||||||
backupManager.restoreCategories(backupCategories)
|
backupManager.restoreCategories(backupCategories)
|
||||||
|
|
||||||
restoreProgress += 1
|
restoreProgress += 1
|
||||||
showRestoreProgress(restoreProgress, restoreAmount, context.getString(R.string.categories))
|
showRestoreProgress(restoreProgress, restoreAmount, context.getString(R.string.categories), context.getString(R.string.restoring_backup))
|
||||||
}
|
}
|
||||||
|
|
||||||
private suspend fun restoreManga(backupManga: BackupManga, backupCategories: List<BackupCategory>) {
|
private suspend fun restoreManga(backupManga: BackupManga, backupCategories: List<BackupCategory>, sync: Boolean) {
|
||||||
val manga = backupManga.getMangaImpl()
|
val manga = backupManga.getMangaImpl()
|
||||||
val chapters = backupManga.getChaptersImpl()
|
val chapters = backupManga.getChaptersImpl()
|
||||||
val categories = backupManga.categories.map { it.toInt() }
|
val categories = backupManga.categories.map { it.toInt() }
|
||||||
|
@ -134,7 +138,11 @@ class BackupRestorer(
|
||||||
}
|
}
|
||||||
|
|
||||||
restoreProgress += 1
|
restoreProgress += 1
|
||||||
showRestoreProgress(restoreProgress, restoreAmount, manga.title)
|
if (sync) {
|
||||||
|
showRestoreProgress(restoreProgress, restoreAmount, manga.title, context.getString(R.string.syncing_library))
|
||||||
|
} else {
|
||||||
|
showRestoreProgress(restoreProgress, restoreAmount, manga.title, context.getString(R.string.restoring_backup))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -182,7 +190,7 @@ class BackupRestorer(
|
||||||
* @param amount total restoreAmount of manga
|
* @param amount total restoreAmount of manga
|
||||||
* @param title title of restored manga
|
* @param title title of restored manga
|
||||||
*/
|
*/
|
||||||
private fun showRestoreProgress(progress: Int, amount: Int, title: String) {
|
private fun showRestoreProgress(progress: Int, amount: Int, title: String, contentTitle: String) {
|
||||||
notifier.showRestoreProgress(title, progress, amount)
|
notifier.showRestoreProgress(title, contentTitle, progress, amount)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -516,6 +516,10 @@
|
||||||
<string name="restoring_backup_canceled">Canceled restore</string>
|
<string name="restoring_backup_canceled">Canceled restore</string>
|
||||||
<string name="backup_info">You should keep copies of backups in other places as well.</string>
|
<string name="backup_info">You should keep copies of backups in other places as well.</string>
|
||||||
|
|
||||||
|
<!-- Sync section -->
|
||||||
|
<string name="syncing_library">Syncing library</string>
|
||||||
|
<string name="library_sync_complete">Library sync complete</string>
|
||||||
|
|
||||||
<!-- Advanced section -->
|
<!-- Advanced section -->
|
||||||
<string name="label_network">Network</string>
|
<string name="label_network">Network</string>
|
||||||
<string name="pref_clear_cookies">Clear cookies</string>
|
<string name="pref_clear_cookies">Clear cookies</string>
|
||||||
|
|
Reference in a new issue