diff --git a/CHANGELOG.md b/CHANGELOG.md index 7ff33cdd1..49cf0459a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -20,6 +20,9 @@ The format is a modified version of [Keep a Changelog](https://keepachangelog.co - Change the term "Obsolete" to "Orphaned" for extensions ([@AntsyLich](https://github.com/AntsyLich)) ([#3383](https://github.com/mihonapp/mihon/pull/3383)) - Remove text limit of manga notes ([@AntsyLich](https://github.com/AntsyLich)) ([#3410](https://github.com/mihonapp/mihon/pull/3410)) +### Improved +- Batch database operations during backup restore for improved performance ([@Lolle2000la](https://github.com/Lolle2000la)) ([#3267](https://github.com/mihonapp/mihon/pull/3267)) + ### Fixed - Add missing `outlineVariant` color to Nord theme ([@CompileConnected](https://github.com/CompileConnected)) ([#3184](https://github.com/mihonapp/mihon/pull/3184)) - Continue reading button missing when unread filter is off ([@AntsyLich](https://github.com/AntsyLich)) ([#3382](https://github.com/mihonapp/mihon/pull/3382)) diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/backup/restore/BackupRestorer.kt b/app/src/main/java/eu/kanade/tachiyomi/data/backup/restore/BackupRestorer.kt index 76b1cc069..fe9c55b54 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/data/backup/restore/BackupRestorer.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/backup/restore/BackupRestorer.kt @@ -19,17 +19,26 @@ import kotlinx.coroutines.coroutineScope import kotlinx.coroutines.ensureActive import kotlinx.coroutines.launch import tachiyomi.core.common.i18n.stringResource +import tachiyomi.data.Database import tachiyomi.i18n.MR +import uy.kohesive.injekt.Injekt +import uy.kohesive.injekt.api.get import java.io.File import java.text.SimpleDateFormat import java.util.Date import java.util.Locale +import java.util.concurrent.CopyOnWriteArrayList +import kotlin.concurrent.atomics.AtomicInt +import kotlin.concurrent.atomics.ExperimentalAtomicApi +import kotlin.concurrent.atomics.incrementAndFetch +@OptIn(ExperimentalAtomicApi::class) class BackupRestorer( private val context: Context, private val notifier: BackupNotifier, private val isSync: Boolean, + private val database: Database = Injekt.get(), private val categoriesRestorer: CategoriesRestorer = CategoriesRestorer(), private val preferenceRestorer: PreferenceRestorer = PreferenceRestorer(context), private val extensionStoreRestorer: ExtensionStoreRestorer = ExtensionStoreRestorer(), @@ -37,8 +46,8 @@ class BackupRestorer( ) { private var restoreAmount = 0 - private var restoreProgress = 0 - private val errors = mutableListOf>() + private val restoreProgress = AtomicInt(0) + private val errors = CopyOnWriteArrayList>() /** * Mapping of source ID to source name from backup data @@ -111,10 +120,10 @@ class BackupRestorer( ensureActive() categoriesRestorer(backupCategories) - restoreProgress += 1 + val progress = restoreProgress.incrementAndFetch() notifier.showRestoreProgress( context.stringResource(MR.strings.categories), - restoreProgress, + progress, restoreAmount, isSync, ) @@ -125,18 +134,23 @@ class BackupRestorer( backupCategories: List, ) = launch { mangaRestorer.sortByNew(backupMangas) - .forEach { - ensureActive() + .chunked(100) + .forEach { chunk -> + database.transaction { + chunk.forEach { + ensureActive() - try { - mangaRestorer.restore(it, backupCategories) - } catch (e: Exception) { - val sourceName = sourceMapping[it.source] ?: it.source.toString() - errors.add(Date() to "${it.title} [$sourceName]: ${e.message}") + try { + mangaRestorer.restore(it, backupCategories) + } catch (e: Exception) { + val sourceName = sourceMapping[it.source] ?: it.source.toString() + errors.add(Date() to "${it.title} [$sourceName]: ${e.message}") + } + + restoreProgress.incrementAndFetch() + } } - - restoreProgress += 1 - notifier.showRestoreProgress(it.title, restoreProgress, restoreAmount, isSync) + notifier.showRestoreProgress(chunk.last().title, restoreProgress.load(), restoreAmount, isSync) } } @@ -150,10 +164,10 @@ class BackupRestorer( categories, ) - restoreProgress += 1 + val progress = restoreProgress.incrementAndFetch() notifier.showRestoreProgress( context.stringResource(MR.strings.app_settings), - restoreProgress, + progress, restoreAmount, isSync, ) @@ -163,10 +177,10 @@ class BackupRestorer( ensureActive() preferenceRestorer.restoreSource(preferences) - restoreProgress += 1 + val progress = restoreProgress.incrementAndFetch() notifier.showRestoreProgress( context.stringResource(MR.strings.source_settings), - restoreProgress, + progress, restoreAmount, isSync, ) @@ -176,19 +190,24 @@ class BackupRestorer( backupExtensionStores: List, ) = launch { backupExtensionStores - .forEach { - ensureActive() + .chunked(100) + .forEach { chunk -> + database.transaction { + chunk.forEach { + ensureActive() - try { - extensionStoreRestorer(it) - } catch (e: Exception) { - errors.add(Date() to "Error Adding Repo: ${it.name} : ${e.message}") + try { + extensionStoreRestorer(it) + } catch (e: Exception) { + errors.add(Date() to "Error Adding Repo: ${it.name} : ${e.message}") + } + + restoreProgress.incrementAndFetch() + } } - - restoreProgress += 1 notifier.showRestoreProgress( context.stringResource(MR.strings.extensionStores), - restoreProgress, + restoreProgress.load(), restoreAmount, isSync, ) @@ -208,7 +227,7 @@ class BackupRestorer( } return file } - } catch (e: Exception) { + } catch (_: Exception) { // Empty } return File("") diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/backup/restore/restorers/CategoriesRestorer.kt b/app/src/main/java/eu/kanade/tachiyomi/data/backup/restore/restorers/CategoriesRestorer.kt index a0e07c102..083590333 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/data/backup/restore/restorers/CategoriesRestorer.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/backup/restore/restorers/CategoriesRestorer.kt @@ -19,16 +19,18 @@ class CategoriesRestorer( val dbCategoriesByName = dbCategories.associateBy { it.name } var nextOrder = dbCategories.maxOfOrNull { it.order }?.plus(1) ?: 0 - val categories = backupCategories - .sortedBy { it.order } - .map { - val dbCategory = dbCategoriesByName[it.name] - if (dbCategory != null) return@map dbCategory - val order = nextOrder++ - database.categoriesQueries - .insert(it.name, order, it.flags) - .let { id -> it.toCategory(id).copy(order = order) } - } + val categories = database.transactionWithResult { + backupCategories + .sortedBy { it.order } + .map { + val dbCategory = dbCategoriesByName[it.name] + if (dbCategory != null) return@map dbCategory + val order = nextOrder++ + database.categoriesQueries + .insert(it.name, order, it.flags) + .let { id -> it.toCategory(id).copy(order = order) } + } + } libraryPreferences.categorizedDisplaySettings.set( (dbCategories + categories)