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:
KaiserBh 2023-07-23 08:39:56 +10:00 committed by GitHub
parent 46e3b9e40d
commit 7b2764e8f7
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 37 additions and 21 deletions

View File

@ -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,

View File

@ -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()

View File

@ -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

View File

@ -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()
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) 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)
} }
} }

View File

@ -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>