Update sqldelight-androidx-driver and remove transaction management (#3270)

`sqldelight-androidx-driver` has it's own transaction management
This commit is contained in:
AntsyLich
2026-04-27 18:24:30 +06:00
committed by GitHub
parent a2f3ec94aa
commit 8e0c911f93
28 changed files with 481 additions and 707 deletions
+2
View File
@@ -203,6 +203,8 @@ dependencies {
implementation(libs.bundles.kotlinx.coroutines)
implementation(libs.sqldelight.async)
// AndroidX libraries
implementation(libs.androidx.annotation)
implementation(libs.androidx.appCompat)
@@ -1,24 +1,26 @@
package eu.kanade.domain.manga.interactor
import app.cash.sqldelight.async.coroutines.awaitAsList
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.map
import tachiyomi.data.DatabaseHandler
import tachiyomi.data.Database
import tachiyomi.data.subscribeToList
class GetExcludedScanlators(
private val handler: DatabaseHandler,
private val database: Database,
) {
suspend fun await(mangaId: Long): Set<String> {
return handler.awaitList {
excluded_scanlatorsQueries.getExcludedScanlatorsByMangaId(mangaId)
}
return database.excluded_scanlatorsQueries
.getExcludedScanlatorsByMangaId(mangaId)
.awaitAsList()
.toSet()
}
fun subscribe(mangaId: Long): Flow<Set<String>> {
return handler.subscribeToList {
excluded_scanlatorsQueries.getExcludedScanlatorsByMangaId(mangaId)
}
return database.excluded_scanlatorsQueries
.getExcludedScanlatorsByMangaId(mangaId)
.subscribeToList()
.map { it.toSet() }
}
}
@@ -1,22 +1,24 @@
package eu.kanade.domain.manga.interactor
import tachiyomi.data.DatabaseHandler
import app.cash.sqldelight.async.coroutines.awaitAsList
import tachiyomi.data.Database
class SetExcludedScanlators(
private val handler: DatabaseHandler,
private val database: Database,
) {
suspend fun await(mangaId: Long, excludedScanlators: Set<String>) {
handler.await(inTransaction = true) {
val currentExcluded = handler.awaitList {
excluded_scanlatorsQueries.getExcludedScanlatorsByMangaId(mangaId)
}.toSet()
database.transaction {
val currentExcluded = database.excluded_scanlatorsQueries
.getExcludedScanlatorsByMangaId(mangaId)
.awaitAsList()
.toSet()
val toAdd = excludedScanlators.minus(currentExcluded)
for (scanlator in toAdd) {
excluded_scanlatorsQueries.insert(mangaId, scanlator)
database.excluded_scanlatorsQueries.insert(mangaId, scanlator)
}
val toRemove = currentExcluded.minus(excludedScanlators)
excluded_scanlatorsQueries.remove(mangaId, toRemove)
database.excluded_scanlatorsQueries.remove(mangaId, toRemove)
}
}
}
@@ -1,5 +1,7 @@
package eu.kanade.tachiyomi.data.backup.create.creators
import app.cash.sqldelight.async.coroutines.awaitAsList
import app.cash.sqldelight.async.coroutines.awaitAsOne
import eu.kanade.tachiyomi.data.backup.create.BackupOptions
import eu.kanade.tachiyomi.data.backup.models.BackupChapter
import eu.kanade.tachiyomi.data.backup.models.BackupHistory
@@ -7,7 +9,7 @@ import eu.kanade.tachiyomi.data.backup.models.BackupManga
import eu.kanade.tachiyomi.data.backup.models.backupChapterMapper
import eu.kanade.tachiyomi.data.backup.models.backupTrackMapper
import eu.kanade.tachiyomi.ui.reader.setting.ReadingMode
import tachiyomi.data.DatabaseHandler
import tachiyomi.data.Database
import tachiyomi.domain.category.interactor.GetCategories
import tachiyomi.domain.history.interactor.GetHistory
import tachiyomi.domain.manga.model.Manga
@@ -15,7 +17,7 @@ import uy.kohesive.injekt.Injekt
import uy.kohesive.injekt.api.get
class MangaBackupCreator(
private val handler: DatabaseHandler = Injekt.get(),
private val database: Database = Injekt.get(),
private val getCategories: GetCategories = Injekt.get(),
private val getHistory: GetHistory = Injekt.get(),
) {
@@ -30,19 +32,19 @@ class MangaBackupCreator(
// Entry for this manga
val mangaObject = manga.toBackupManga()
mangaObject.excludedScanlators = handler.awaitList {
excluded_scanlatorsQueries.getExcludedScanlatorsByMangaId(manga.id)
}
mangaObject.excludedScanlators = database.excluded_scanlatorsQueries
.getExcludedScanlatorsByMangaId(manga.id)
.awaitAsList()
if (options.chapters) {
// Backup all the chapters
handler.awaitList {
chaptersQueries.getChaptersByMangaId(
database.chaptersQueries
.getChaptersByMangaId(
mangaId = manga.id,
applyScanlatorFilter = 0, // false
mapper = backupChapterMapper,
)
}
.awaitAsList()
.takeUnless(List<BackupChapter>::isEmpty)
?.let { mangaObject.chapters = it }
}
@@ -56,7 +58,9 @@ class MangaBackupCreator(
}
if (options.tracking) {
val tracks = handler.awaitList { manga_syncQueries.getTracksByMangaId(manga.id, backupTrackMapper) }
val tracks = database.manga_syncQueries
.getTracksByMangaId(manga.id, backupTrackMapper)
.awaitAsList()
if (tracks.isNotEmpty()) {
mangaObject.tracking = tracks
}
@@ -66,7 +70,9 @@ class MangaBackupCreator(
val historyByMangaId = getHistory.await(manga.id)
if (historyByMangaId.isNotEmpty()) {
val history = historyByMangaId.map { history ->
val chapter = handler.awaitOne { chaptersQueries.getChapterById(history.chapterId) }
val chapter = database.chaptersQueries
.getChapterById(history.chapterId)
.awaitAsOne()
BackupHistory(chapter.url, history.readAt?.time ?: 0L, history.readDuration)
}
if (history.isNotEmpty()) {
@@ -1,14 +1,15 @@
package eu.kanade.tachiyomi.data.backup.restore.restorers
import app.cash.sqldelight.async.coroutines.awaitAsOne
import eu.kanade.tachiyomi.data.backup.models.BackupCategory
import tachiyomi.data.DatabaseHandler
import tachiyomi.data.Database
import tachiyomi.domain.category.interactor.GetCategories
import tachiyomi.domain.library.service.LibraryPreferences
import uy.kohesive.injekt.Injekt
import uy.kohesive.injekt.api.get
class CategoriesRestorer(
private val handler: DatabaseHandler = Injekt.get(),
private val database: Database = Injekt.get(),
private val getCategories: GetCategories = Injekt.get(),
private val libraryPreferences: LibraryPreferences = Injekt.get(),
) {
@@ -25,10 +26,9 @@ class CategoriesRestorer(
val dbCategory = dbCategoriesByName[it.name]
if (dbCategory != null) return@map dbCategory
val order = nextOrder++
handler.awaitOneExecutable {
categoriesQueries.insert(it.name, order, it.flags)
categoriesQueries.selectLastInsertedRowId()
}
database.categoriesQueries
.insert(it.name, order, it.flags)
.awaitAsOne()
.let { id -> it.toCategory(id).copy(order = order) }
}
@@ -2,12 +2,12 @@ package eu.kanade.tachiyomi.data.backup.restore.restorers
import eu.kanade.tachiyomi.data.backup.models.BackupExtensionRepos
import mihon.domain.extensionrepo.interactor.GetExtensionRepo
import tachiyomi.data.DatabaseHandler
import tachiyomi.data.Database
import uy.kohesive.injekt.Injekt
import uy.kohesive.injekt.api.get
class ExtensionRepoRestorer(
private val handler: DatabaseHandler = Injekt.get(),
private val database: Database = Injekt.get(),
private val getExtensionRepos: GetExtensionRepo = Injekt.get(),
) {
@@ -26,15 +26,13 @@ class ExtensionRepoRestorer(
} else if (shaExists != null) {
error("${shaExists.name} has the same signing key fingerprint")
} else {
handler.await {
extension_reposQueries.insert(
backupRepo.baseUrl,
backupRepo.name,
backupRepo.shortName,
backupRepo.website,
backupRepo.signingKeyFingerprint,
)
}
database.extension_reposQueries.insert(
backupRepo.baseUrl,
backupRepo.name,
backupRepo.shortName,
backupRepo.website,
backupRepo.signingKeyFingerprint,
)
}
}
}
@@ -1,12 +1,15 @@
package eu.kanade.tachiyomi.data.backup.restore.restorers
import app.cash.sqldelight.async.coroutines.awaitAsList
import app.cash.sqldelight.async.coroutines.awaitAsOne
import app.cash.sqldelight.async.coroutines.awaitAsOneOrNull
import eu.kanade.domain.manga.interactor.UpdateManga
import eu.kanade.tachiyomi.data.backup.models.BackupCategory
import eu.kanade.tachiyomi.data.backup.models.BackupChapter
import eu.kanade.tachiyomi.data.backup.models.BackupHistory
import eu.kanade.tachiyomi.data.backup.models.BackupManga
import eu.kanade.tachiyomi.data.backup.models.BackupTracking
import tachiyomi.data.DatabaseHandler
import tachiyomi.data.Database
import tachiyomi.data.UpdateStrategyColumnAdapter
import tachiyomi.domain.category.interactor.GetCategories
import tachiyomi.domain.chapter.interactor.GetChaptersByMangaId
@@ -24,7 +27,7 @@ import java.util.Date
import kotlin.math.max
class MangaRestorer(
private val handler: DatabaseHandler = Injekt.get(),
private val database: Database = Injekt.get(),
private val getCategories: GetCategories = Injekt.get(),
private val getMangaByUrlAndSourceId: GetMangaByUrlAndSourceId = Injekt.get(),
private val getChaptersByMangaId: GetChaptersByMangaId = Injekt.get(),
@@ -43,7 +46,9 @@ class MangaRestorer(
}
suspend fun sortByNew(backupMangas: List<BackupManga>): List<BackupManga> {
val urlsBySource = handler.awaitList { mangasQueries.getAllMangaSourceAndUrl() }
val urlsBySource = database.mangasQueries
.getAllMangaSourceAndUrl()
.awaitAsList()
.groupBy({ it.source }, { it.url })
return backupMangas
@@ -57,7 +62,7 @@ class MangaRestorer(
backupManga: BackupManga,
backupCategories: List<BackupCategory>,
) {
handler.await(inTransaction = true) {
database.transaction {
val dbManga = findExistingManga(backupManga)
val manga = backupManga.getMangaImpl()
val restoredManga = if (dbManga == null) {
@@ -105,33 +110,31 @@ class MangaRestorer(
}
private suspend fun updateManga(manga: Manga): Manga {
handler.await(true) {
mangasQueries.update(
source = manga.source,
url = manga.url,
artist = manga.artist,
author = manga.author,
description = manga.description,
genre = manga.genre?.joinToString(separator = ", "),
title = manga.title,
status = manga.status,
thumbnailUrl = manga.thumbnailUrl,
favorite = manga.favorite,
lastUpdate = manga.lastUpdate,
nextUpdate = null,
calculateInterval = null,
initialized = manga.initialized,
viewer = manga.viewerFlags,
chapterFlags = manga.chapterFlags,
coverLastModified = manga.coverLastModified,
dateAdded = manga.dateAdded,
mangaId = manga.id,
updateStrategy = manga.updateStrategy.let(UpdateStrategyColumnAdapter::encode),
version = manga.version,
isSyncing = 1,
notes = manga.notes,
)
}
database.mangasQueries.update(
source = manga.source,
url = manga.url,
artist = manga.artist,
author = manga.author,
description = manga.description,
genre = manga.genre?.joinToString(separator = ", "),
title = manga.title,
status = manga.status,
thumbnailUrl = manga.thumbnailUrl,
favorite = manga.favorite,
lastUpdate = manga.lastUpdate,
nextUpdate = null,
calculateInterval = null,
initialized = manga.initialized,
viewer = manga.viewerFlags,
chapterFlags = manga.chapterFlags,
coverLastModified = manga.coverLastModified,
dateAdded = manga.dateAdded,
mangaId = manga.id,
updateStrategy = manga.updateStrategy.let(UpdateStrategyColumnAdapter::encode),
version = manga.version,
isSyncing = 1,
notes = manga.notes,
)
return manga
}
@@ -189,9 +192,9 @@ class MangaRestorer(
this.copy(id = 0L, mangaId = 0L, dateFetch = 0L, dateUpload = 0L, lastModifiedAt = 0L, version = 0L)
private suspend fun insertNewChapters(chapters: List<Chapter>) {
handler.await(true) {
database.transaction {
chapters.forEach { chapter ->
chaptersQueries.insert(
database.chaptersQueries.insert(
chapter.mangaId,
chapter.url,
chapter.name,
@@ -210,9 +213,9 @@ class MangaRestorer(
}
private suspend fun updateExistingChapters(chapters: List<Chapter>) {
handler.await(true) {
database.transaction {
chapters.forEach { chapter ->
chaptersQueries.update(
database.chaptersQueries.update(
mangaId = null,
url = null,
name = null,
@@ -238,32 +241,30 @@ class MangaRestorer(
* @return id of [Manga], null if not found
*/
private suspend fun insertManga(manga: Manga): Long {
return handler.awaitOneExecutable(true) {
mangasQueries.insert(
source = manga.source,
url = manga.url,
artist = manga.artist,
author = manga.author,
description = manga.description,
genre = manga.genre,
title = manga.title,
status = manga.status,
thumbnailUrl = manga.thumbnailUrl,
favorite = manga.favorite,
lastUpdate = manga.lastUpdate,
nextUpdate = 0L,
calculateInterval = 0L,
initialized = manga.initialized,
viewerFlags = manga.viewerFlags,
chapterFlags = manga.chapterFlags,
coverLastModified = manga.coverLastModified,
dateAdded = manga.dateAdded,
updateStrategy = manga.updateStrategy,
version = manga.version,
notes = manga.notes,
)
mangasQueries.selectLastInsertedRowId()
}
return database.mangasQueries.insert(
source = manga.source,
url = manga.url,
artist = manga.artist,
author = manga.author,
description = manga.description,
genre = manga.genre,
title = manga.title,
status = manga.status,
thumbnailUrl = manga.thumbnailUrl,
favorite = manga.favorite,
lastUpdate = manga.lastUpdate,
nextUpdate = 0L,
calculateInterval = 0L,
initialized = manga.initialized,
viewerFlags = manga.viewerFlags,
chapterFlags = manga.chapterFlags,
coverLastModified = manga.coverLastModified,
dateAdded = manga.dateAdded,
updateStrategy = manga.updateStrategy,
version = manga.version,
notes = manga.notes,
)
.awaitAsOne()
}
private suspend fun restoreMangaDetails(
@@ -309,10 +310,10 @@ class MangaRestorer(
}
if (mangaCategoriesToUpdate.isNotEmpty()) {
handler.await(true) {
mangas_categoriesQueries.deleteMangaCategoryByMangaId(manga.id)
database.transaction {
database.mangas_categoriesQueries.deleteMangaCategoryByMangaId(manga.id)
mangaCategoriesToUpdate.forEach { (mangaId, categoryId) ->
mangas_categoriesQueries.insert(mangaId, categoryId)
database.mangas_categoriesQueries.insert(mangaId, categoryId)
}
}
}
@@ -320,11 +321,15 @@ class MangaRestorer(
private suspend fun restoreHistory(backupHistory: List<BackupHistory>) {
val toUpdate = backupHistory.mapNotNull { history ->
val dbHistory = handler.awaitOneOrNull { historyQueries.getHistoryByChapterUrl(history.url) }
val dbHistory = database.historyQueries
.getHistoryByChapterUrl(history.url)
.awaitAsOneOrNull()
val item = history.getHistoryImpl()
if (dbHistory == null) {
val chapter = handler.awaitOneOrNull { chaptersQueries.getChapterByUrl(history.url) }
val chapter = database.chaptersQueries
.getChapterByUrl(history.url)
.awaitAsOneOrNull()
return@mapNotNull if (chapter == null) {
// Chapter doesn't exist; skip
null
@@ -345,15 +350,14 @@ class MangaRestorer(
)
}
if (toUpdate.isNotEmpty()) {
handler.await(true) {
toUpdate.forEach {
historyQueries.upsert(
it.chapterId,
it.readAt,
it.readDuration,
)
}
if (toUpdate.isEmpty()) return
database.transaction {
toUpdate.forEach {
database.historyQueries.upsert(
it.chapterId,
it.readAt,
it.readDuration,
)
}
}
}
@@ -388,26 +392,26 @@ class MangaRestorer(
if (newTracks.isNotEmpty()) {
insertTrack.awaitAll(newTracks)
}
if (existingTracks.isNotEmpty()) {
handler.await(true) {
existingTracks.forEach { track ->
manga_syncQueries.update(
track.mangaId,
track.trackerId,
track.remoteId,
track.libraryId,
track.title,
track.lastChapterRead,
track.totalChapters,
track.status,
track.score,
track.remoteUrl,
track.startDate,
track.finishDate,
track.private,
track.id,
)
}
if (existingTracks.isEmpty()) return
database.transaction {
existingTracks.forEach { track ->
database.manga_syncQueries.update(
track.mangaId,
track.trackerId,
track.remoteId,
track.libraryId,
track.title,
track.lastChapterRead,
track.totalChapters,
track.status,
track.score,
track.remoteUrl,
track.startDate,
track.finishDate,
track.private,
track.id,
)
}
}
}
@@ -422,16 +426,11 @@ class MangaRestorer(
*/
private suspend fun restoreExcludedScanlators(manga: Manga, excludedScanlators: List<String>) {
if (excludedScanlators.isEmpty()) return
val existingExcludedScanlators = handler.awaitList {
excluded_scanlatorsQueries.getExcludedScanlatorsByMangaId(manga.id)
}
val existingExcludedScanlators = database.excluded_scanlatorsQueries
.getExcludedScanlatorsByMangaId(manga.id)
.awaitAsList()
val toInsert = excludedScanlators.filter { it !in existingExcludedScanlators }
if (toInsert.isNotEmpty()) {
handler.await {
toInsert.forEach {
excluded_scanlatorsQueries.insert(manga.id, it)
}
}
}
if (toInsert.isEmpty()) return
toInsert.forEach { database.excluded_scanlatorsQueries.insert(manga.id, it) }
}
}
@@ -26,9 +26,7 @@ import nl.adaptivity.xmlutil.XmlDeclMode
import nl.adaptivity.xmlutil.core.XmlVersion
import nl.adaptivity.xmlutil.serialization.XML
import tachiyomi.core.common.storage.AndroidStorageFolderProvider
import tachiyomi.data.AndroidDatabaseHandler
import tachiyomi.data.Database
import tachiyomi.data.DatabaseHandler
import tachiyomi.data.DateColumnAdapter
import tachiyomi.data.History
import tachiyomi.data.Mangas
@@ -81,7 +79,6 @@ class AppModule(val app: Application) : InjektModule {
),
)
}
addSingletonFactory<DatabaseHandler> { AndroidDatabaseHandler(get(), get()) }
addSingletonFactory {
Json {