From 8e0c911f93e60db35dcbe2a9103ac6ea0d803e29 Mon Sep 17 00:00:00 2001 From: AntsyLich <59261191+AntsyLich@users.noreply.github.com> Date: Mon, 27 Apr 2026 18:24:30 +0600 Subject: [PATCH] Update sqldelight-androidx-driver and remove transaction management (#3270) `sqldelight-androidx-driver` has it's own transaction management --- app/build.gradle.kts | 2 + .../manga/interactor/GetExcludedScanlators.kt | 18 +- .../manga/interactor/SetExcludedScanlators.kt | 18 +- .../create/creators/MangaBackupCreator.kt | 26 ++- .../restore/restorers/CategoriesRestorer.kt | 12 +- .../restorers/ExtensionRepoRestorer.kt | 20 +- .../backup/restore/restorers/MangaRestorer.kt | 209 +++++++++--------- .../java/eu/kanade/tachiyomi/di/AppModule.kt | 3 - data/build.gradle.kts | 1 + .../repository/ExtensionRepoRepositoryImpl.kt | 65 ++++-- .../tachiyomi/data/AndroidDatabaseHandler.kt | 104 --------- .../java/tachiyomi/data/DatabaseHandler.kt | 47 ---- .../java/tachiyomi/data/QueryExtension.kt | 36 +++ .../java/tachiyomi/data/QueryPagingSource.kt | 17 +- .../java/tachiyomi/data/TransactionContext.kt | 165 -------------- .../data/category/CategoryRepositoryImpl.kt | 74 +++---- .../data/chapter/ChapterRepositoryImpl.kt | 70 +++--- .../data/history/HistoryRepositoryImpl.kt | 46 ++-- .../java/tachiyomi/data/manga/MangaMapper.kt | 2 +- .../data/manga/MangaRepositoryImpl.kt | 90 ++++---- .../data/source/SourceRepositoryImpl.kt | 36 +-- .../data/source/StubSourceRepositoryImpl.kt | 16 +- .../data/track/TrackRepositoryImpl.kt | 43 ++-- .../data/updates/UpdatesRepositoryImpl.kt | 26 ++- .../sqldelight/tachiyomi/data/categories.sq | 12 +- .../sqldelight/tachiyomi/data/chapters.sq | 11 +- .../main/sqldelight/tachiyomi/data/mangas.sq | 12 +- gradle/libs.versions.toml | 7 +- 28 files changed, 481 insertions(+), 707 deletions(-) delete mode 100644 data/src/main/java/tachiyomi/data/AndroidDatabaseHandler.kt delete mode 100644 data/src/main/java/tachiyomi/data/DatabaseHandler.kt create mode 100644 data/src/main/java/tachiyomi/data/QueryExtension.kt delete mode 100644 data/src/main/java/tachiyomi/data/TransactionContext.kt diff --git a/app/build.gradle.kts b/app/build.gradle.kts index 2d37cf4fc..cdaa6f960 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -203,6 +203,8 @@ dependencies { implementation(libs.bundles.kotlinx.coroutines) + implementation(libs.sqldelight.async) + // AndroidX libraries implementation(libs.androidx.annotation) implementation(libs.androidx.appCompat) diff --git a/app/src/main/java/eu/kanade/domain/manga/interactor/GetExcludedScanlators.kt b/app/src/main/java/eu/kanade/domain/manga/interactor/GetExcludedScanlators.kt index dc326f209..464421292 100644 --- a/app/src/main/java/eu/kanade/domain/manga/interactor/GetExcludedScanlators.kt +++ b/app/src/main/java/eu/kanade/domain/manga/interactor/GetExcludedScanlators.kt @@ -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 { - return handler.awaitList { - excluded_scanlatorsQueries.getExcludedScanlatorsByMangaId(mangaId) - } + return database.excluded_scanlatorsQueries + .getExcludedScanlatorsByMangaId(mangaId) + .awaitAsList() .toSet() } fun subscribe(mangaId: Long): Flow> { - return handler.subscribeToList { - excluded_scanlatorsQueries.getExcludedScanlatorsByMangaId(mangaId) - } + return database.excluded_scanlatorsQueries + .getExcludedScanlatorsByMangaId(mangaId) + .subscribeToList() .map { it.toSet() } } } diff --git a/app/src/main/java/eu/kanade/domain/manga/interactor/SetExcludedScanlators.kt b/app/src/main/java/eu/kanade/domain/manga/interactor/SetExcludedScanlators.kt index a52fb9afd..3783e6c7a 100644 --- a/app/src/main/java/eu/kanade/domain/manga/interactor/SetExcludedScanlators.kt +++ b/app/src/main/java/eu/kanade/domain/manga/interactor/SetExcludedScanlators.kt @@ -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) { - 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) } } } diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/backup/create/creators/MangaBackupCreator.kt b/app/src/main/java/eu/kanade/tachiyomi/data/backup/create/creators/MangaBackupCreator.kt index 1607682ca..618c0c8f0 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/data/backup/create/creators/MangaBackupCreator.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/backup/create/creators/MangaBackupCreator.kt @@ -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::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()) { 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 3d73cfefc..fe49d7e33 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 @@ -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) } } diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/backup/restore/restorers/ExtensionRepoRestorer.kt b/app/src/main/java/eu/kanade/tachiyomi/data/backup/restore/restorers/ExtensionRepoRestorer.kt index 006e48df1..7dbc0631f 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/data/backup/restore/restorers/ExtensionRepoRestorer.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/backup/restore/restorers/ExtensionRepoRestorer.kt @@ -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, + ) } } } diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/backup/restore/restorers/MangaRestorer.kt b/app/src/main/java/eu/kanade/tachiyomi/data/backup/restore/restorers/MangaRestorer.kt index 1de2a831a..d37b1fd77 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/data/backup/restore/restorers/MangaRestorer.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/backup/restore/restorers/MangaRestorer.kt @@ -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): List { - 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, ) { - 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) { - 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) { - 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) { 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) { 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) } } } diff --git a/app/src/main/java/eu/kanade/tachiyomi/di/AppModule.kt b/app/src/main/java/eu/kanade/tachiyomi/di/AppModule.kt index 59f1a2c25..9828155df 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/di/AppModule.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/di/AppModule.kt @@ -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 { AndroidDatabaseHandler(get(), get()) } addSingletonFactory { Json { diff --git a/data/build.gradle.kts b/data/build.gradle.kts index 38386f7c3..002c1a0b3 100644 --- a/data/build.gradle.kts +++ b/data/build.gradle.kts @@ -15,6 +15,7 @@ android { packageName.set("tachiyomi.data") dialect(libs.sqldelight.sqliteDialect338) schemaOutputDirectory.set(project.file("./src/main/sqldelight")) + generateAsync.set(true) } } } diff --git a/data/src/main/java/mihon/data/repository/ExtensionRepoRepositoryImpl.kt b/data/src/main/java/mihon/data/repository/ExtensionRepoRepositoryImpl.kt index 61c279630..e60f0815e 100644 --- a/data/src/main/java/mihon/data/repository/ExtensionRepoRepositoryImpl.kt +++ b/data/src/main/java/mihon/data/repository/ExtensionRepoRepositoryImpl.kt @@ -1,36 +1,49 @@ package mihon.data.repository import android.database.SQLException +import app.cash.sqldelight.async.coroutines.awaitAsList +import app.cash.sqldelight.async.coroutines.awaitAsOneOrNull import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.map import mihon.domain.extensionrepo.exception.SaveExtensionRepoException import mihon.domain.extensionrepo.model.ExtensionRepo import mihon.domain.extensionrepo.repository.ExtensionRepoRepository -import tachiyomi.data.DatabaseHandler +import tachiyomi.data.Database +import tachiyomi.data.subscribeToList +import tachiyomi.data.subscribeToOne class ExtensionRepoRepositoryImpl( - private val handler: DatabaseHandler, + private val database: Database, ) : ExtensionRepoRepository { override fun subscribeAll(): Flow> { - return handler.subscribeToList { extension_reposQueries.findAll(::mapExtensionRepo) } + return database.extension_reposQueries + .findAll(::mapExtensionRepo) + .subscribeToList() } override suspend fun getAll(): List { - return handler.awaitList { extension_reposQueries.findAll(::mapExtensionRepo) } + return database.extension_reposQueries + .findAll(::mapExtensionRepo) + .awaitAsList() } override suspend fun getRepo(baseUrl: String): ExtensionRepo? { - return handler.awaitOneOrNull { extension_reposQueries.findOne(baseUrl, ::mapExtensionRepo) } + return database.extension_reposQueries + .findOne(baseUrl, ::mapExtensionRepo) + .awaitAsOneOrNull() } override suspend fun getRepoBySigningKeyFingerprint(fingerprint: String): ExtensionRepo? { - return handler.awaitOneOrNull { - extension_reposQueries.findOneBySigningKeyFingerprint(fingerprint, ::mapExtensionRepo) - } + return database.extension_reposQueries + .findOneBySigningKeyFingerprint(fingerprint, ::mapExtensionRepo) + .awaitAsOneOrNull() } override fun getCount(): Flow { - return handler.subscribeToOne { extension_reposQueries.count() }.map { it.toInt() } + return database.extension_reposQueries + .count() + .subscribeToOne() + .map { it.toInt() } } override suspend fun insertRepo( @@ -41,7 +54,13 @@ class ExtensionRepoRepositoryImpl( signingKeyFingerprint: String, ) { try { - handler.await { extension_reposQueries.insert(baseUrl, name, shortName, website, signingKeyFingerprint) } + database.extension_reposQueries.insert( + baseUrl, + name, + shortName, + website, + signingKeyFingerprint, + ) } catch (ex: SQLException) { throw SaveExtensionRepoException(ex) } @@ -55,26 +74,30 @@ class ExtensionRepoRepositoryImpl( signingKeyFingerprint: String, ) { try { - handler.await { extension_reposQueries.upsert(baseUrl, name, shortName, website, signingKeyFingerprint) } + database.extension_reposQueries.upsert( + baseUrl, + name, + shortName, + website, + signingKeyFingerprint, + ) } catch (ex: SQLException) { throw SaveExtensionRepoException(ex) } } override suspend fun replaceRepo(newRepo: ExtensionRepo) { - handler.await { - extension_reposQueries.replace( - newRepo.baseUrl, - newRepo.name, - newRepo.shortName, - newRepo.website, - newRepo.signingKeyFingerprint, - ) - } + database.extension_reposQueries.replace( + newRepo.baseUrl, + newRepo.name, + newRepo.shortName, + newRepo.website, + newRepo.signingKeyFingerprint, + ) } override suspend fun deleteRepo(baseUrl: String) { - return handler.await { extension_reposQueries.delete(baseUrl) } + database.extension_reposQueries.delete(baseUrl) } private fun mapExtensionRepo( diff --git a/data/src/main/java/tachiyomi/data/AndroidDatabaseHandler.kt b/data/src/main/java/tachiyomi/data/AndroidDatabaseHandler.kt deleted file mode 100644 index be74a1102..000000000 --- a/data/src/main/java/tachiyomi/data/AndroidDatabaseHandler.kt +++ /dev/null @@ -1,104 +0,0 @@ -package tachiyomi.data - -import androidx.paging.PagingSource -import app.cash.sqldelight.ExecutableQuery -import app.cash.sqldelight.Query -import app.cash.sqldelight.coroutines.asFlow -import app.cash.sqldelight.coroutines.mapToList -import app.cash.sqldelight.coroutines.mapToOne -import app.cash.sqldelight.coroutines.mapToOneOrNull -import app.cash.sqldelight.db.SqlDriver -import kotlinx.coroutines.CoroutineDispatcher -import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.flow.Flow -import kotlinx.coroutines.withContext - -class AndroidDatabaseHandler( - val db: Database, - private val driver: SqlDriver, - val queryDispatcher: CoroutineDispatcher = Dispatchers.IO, - val transactionDispatcher: CoroutineDispatcher = queryDispatcher, -) : DatabaseHandler { - - val suspendingTransactionId = ThreadLocal() - - override suspend fun await(inTransaction: Boolean, block: suspend Database.() -> T): T { - return dispatch(inTransaction, block) - } - - override suspend fun awaitList( - inTransaction: Boolean, - block: suspend Database.() -> Query, - ): List { - return dispatch(inTransaction) { block(db).executeAsList() } - } - - override suspend fun awaitOne( - inTransaction: Boolean, - block: suspend Database.() -> Query, - ): T { - return dispatch(inTransaction) { block(db).executeAsOne() } - } - - override suspend fun awaitOneExecutable( - inTransaction: Boolean, - block: suspend Database.() -> ExecutableQuery, - ): T { - return dispatch(inTransaction) { block(db).executeAsOne() } - } - - override suspend fun awaitOneOrNull( - inTransaction: Boolean, - block: suspend Database.() -> Query, - ): T? { - return dispatch(inTransaction) { block(db).executeAsOneOrNull() } - } - - override suspend fun awaitOneOrNullExecutable( - inTransaction: Boolean, - block: suspend Database.() -> ExecutableQuery, - ): T? { - return dispatch(inTransaction) { block(db).executeAsOneOrNull() } - } - - override fun subscribeToList(block: Database.() -> Query): Flow> { - return block(db).asFlow().mapToList(queryDispatcher) - } - - override fun subscribeToOne(block: Database.() -> Query): Flow { - return block(db).asFlow().mapToOne(queryDispatcher) - } - - override fun subscribeToOneOrNull(block: Database.() -> Query): Flow { - return block(db).asFlow().mapToOneOrNull(queryDispatcher) - } - - override fun subscribeToPagingSource( - countQuery: Database.() -> Query, - queryProvider: Database.(Long, Long) -> Query, - ): PagingSource { - return QueryPagingSource( - handler = this, - countQuery = countQuery, - queryProvider = { limit, offset -> - queryProvider.invoke(db, limit, offset) - }, - ) - } - - private suspend fun dispatch(inTransaction: Boolean, block: suspend Database.() -> T): T { - // Create a transaction if needed and run the calling block inside it. - if (inTransaction) { - return withTransaction { block(db) } - } - - // If we're currently in the transaction thread, there's no need to dispatch our query. - if (driver.currentTransaction() != null) { - return block(db) - } - - // Get the current database context and run the calling block. - val context = getCurrentDatabaseContext() - return withContext(context) { block(db) } - } -} diff --git a/data/src/main/java/tachiyomi/data/DatabaseHandler.kt b/data/src/main/java/tachiyomi/data/DatabaseHandler.kt deleted file mode 100644 index 57f58f790..000000000 --- a/data/src/main/java/tachiyomi/data/DatabaseHandler.kt +++ /dev/null @@ -1,47 +0,0 @@ -package tachiyomi.data - -import androidx.paging.PagingSource -import app.cash.sqldelight.ExecutableQuery -import app.cash.sqldelight.Query -import kotlinx.coroutines.flow.Flow - -interface DatabaseHandler { - - suspend fun await(inTransaction: Boolean = false, block: suspend Database.() -> T): T - - suspend fun awaitList( - inTransaction: Boolean = false, - block: suspend Database.() -> Query, - ): List - - suspend fun awaitOne( - inTransaction: Boolean = false, - block: suspend Database.() -> Query, - ): T - - suspend fun awaitOneExecutable( - inTransaction: Boolean = false, - block: suspend Database.() -> ExecutableQuery, - ): T - - suspend fun awaitOneOrNull( - inTransaction: Boolean = false, - block: suspend Database.() -> Query, - ): T? - - suspend fun awaitOneOrNullExecutable( - inTransaction: Boolean = false, - block: suspend Database.() -> ExecutableQuery, - ): T? - - fun subscribeToList(block: Database.() -> Query): Flow> - - fun subscribeToOne(block: Database.() -> Query): Flow - - fun subscribeToOneOrNull(block: Database.() -> Query): Flow - - fun subscribeToPagingSource( - countQuery: Database.() -> Query, - queryProvider: Database.(Long, Long) -> Query, - ): PagingSource -} diff --git a/data/src/main/java/tachiyomi/data/QueryExtension.kt b/data/src/main/java/tachiyomi/data/QueryExtension.kt new file mode 100644 index 000000000..45c90b3e7 --- /dev/null +++ b/data/src/main/java/tachiyomi/data/QueryExtension.kt @@ -0,0 +1,36 @@ +package tachiyomi.data + +import app.cash.sqldelight.Query +import app.cash.sqldelight.async.coroutines.awaitAsList +import app.cash.sqldelight.async.coroutines.awaitAsOne +import app.cash.sqldelight.async.coroutines.awaitAsOneOrNull +import app.cash.sqldelight.coroutines.asFlow +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.map +import kotlinx.coroutines.withContext +import kotlin.coroutines.CoroutineContext +import kotlin.coroutines.EmptyCoroutineContext + +fun Query.subscribeToList( + context: CoroutineContext = EmptyCoroutineContext, +): Flow> = asFlow().map { + withContext(context) { + it.awaitAsList() + } +} + +fun Query.subscribeToOne( + context: CoroutineContext = EmptyCoroutineContext, +): Flow = asFlow().map { + withContext(context) { + it.awaitAsOne() + } +} + +fun Query.subscribeToOneOrNull( + context: CoroutineContext = EmptyCoroutineContext, +): Flow = asFlow().map { + withContext(context) { + it.awaitAsOneOrNull() + } +} diff --git a/data/src/main/java/tachiyomi/data/QueryPagingSource.kt b/data/src/main/java/tachiyomi/data/QueryPagingSource.kt index 16974e368..5b963ff6b 100644 --- a/data/src/main/java/tachiyomi/data/QueryPagingSource.kt +++ b/data/src/main/java/tachiyomi/data/QueryPagingSource.kt @@ -3,12 +3,14 @@ package tachiyomi.data import androidx.paging.PagingSource import androidx.paging.PagingState import app.cash.sqldelight.Query +import app.cash.sqldelight.async.coroutines.awaitAsList +import app.cash.sqldelight.async.coroutines.awaitAsOne import kotlin.properties.Delegates +@Suppress("unused") class QueryPagingSource( - val handler: DatabaseHandler, - val countQuery: Database.() -> Query, - val queryProvider: Database.(Long, Long) -> Query, + val countQuery: () -> Query, + val queryProvider: (Long, Long) -> Query, ) : PagingSource(), Query.Listener { override val jumpingSupported: Boolean = true @@ -29,17 +31,16 @@ class QueryPagingSource( try { val key = params.key ?: 0L val loadSize = params.loadSize - val count = handler.awaitOne { countQuery() } + val count = countQuery().awaitAsOne() val (offset, limit) = when (params) { is LoadParams.Prepend -> key - loadSize to loadSize.toLong() else -> key to loadSize.toLong() } - val data = handler.awaitList { - queryProvider(limit, offset) - .also { currentQuery = it } - } + val data = queryProvider(limit, offset) + .also { currentQuery = it } + .awaitAsList() val (prevKey, nextKey) = when (params) { is LoadParams.Append -> (offset - loadSize to offset + loadSize) diff --git a/data/src/main/java/tachiyomi/data/TransactionContext.kt b/data/src/main/java/tachiyomi/data/TransactionContext.kt deleted file mode 100644 index f804a0b8e..000000000 --- a/data/src/main/java/tachiyomi/data/TransactionContext.kt +++ /dev/null @@ -1,165 +0,0 @@ -package tachiyomi.data - -import kotlinx.coroutines.CoroutineDispatcher -import kotlinx.coroutines.Job -import kotlinx.coroutines.asContextElement -import kotlinx.coroutines.runBlocking -import kotlinx.coroutines.suspendCancellableCoroutine -import kotlinx.coroutines.withContext -import java.util.concurrent.RejectedExecutionException -import kotlin.concurrent.atomics.AtomicInt -import kotlin.concurrent.atomics.ExperimentalAtomicApi -import kotlin.concurrent.atomics.decrementAndFetch -import kotlin.concurrent.atomics.incrementAndFetch -import kotlin.coroutines.ContinuationInterceptor -import kotlin.coroutines.CoroutineContext -import kotlin.coroutines.EmptyCoroutineContext -import kotlin.coroutines.coroutineContext -import kotlin.coroutines.resume - -/** - * Returns the transaction dispatcher if we are on a transaction, or the database dispatchers. - */ -internal suspend fun AndroidDatabaseHandler.getCurrentDatabaseContext(): CoroutineContext { - return coroutineContext[TransactionElement]?.transactionDispatcher ?: queryDispatcher -} - -/** - * Calls the specified suspending [block] in a database transaction. The transaction will be - * marked as successful unless an exception is thrown in the suspending [block] or the coroutine - * is cancelled. - * - * SQLDelight will only perform at most one transaction at a time, additional transactions are queued - * and executed on a first come, first serve order. - * - * Performing blocking database operations is not permitted in a coroutine scope other than the - * one received by the suspending block. It is recommended that all [Dao] function invoked within - * the [block] be suspending functions. - * - * The dispatcher used to execute the given [block] will utilize threads from SQLDelight's query executor. - */ -internal suspend fun AndroidDatabaseHandler.withTransaction(block: suspend () -> T): T { - // Use inherited transaction context if available, this allows nested suspending transactions. - val transactionContext = - coroutineContext[TransactionElement]?.transactionDispatcher ?: createTransactionContext() - return withContext(transactionContext) { - val transactionElement = coroutineContext[TransactionElement]!! - transactionElement.acquire() - try { - db.transactionWithResult { - runBlocking(transactionContext) { - block() - } - } - } finally { - transactionElement.release() - } - } -} - -/** - * Creates a [CoroutineContext] for performing database operations within a coroutine transaction. - * - * The context is a combination of a dispatcher, a [TransactionElement] and a thread local element. - * - * * The dispatcher will dispatch coroutines to a single thread that is taken over from the SQLDelight - * query executor. If the coroutine context is switched, suspending DAO functions will be able to - * dispatch to the transaction thread. - * - * * The [TransactionElement] serves as an indicator for inherited context, meaning, if there is a - * switch of context, suspending DAO methods will be able to use the indicator to dispatch the - * database operation to the transaction thread. - * - * * The thread local element serves as a second indicator and marks threads that are used to - * execute coroutines within the coroutine transaction, more specifically it allows us to identify - * if a blocking DAO method is invoked within the transaction coroutine. Never assign meaning to - * this value, for now all we care is if its present or not. - */ -private suspend fun AndroidDatabaseHandler.createTransactionContext(): CoroutineContext { - val controlJob = Job() - // make sure to tie the control job to this context to avoid blocking the transaction if - // context get cancelled before we can even start using this job. Otherwise, the acquired - // transaction thread will forever wait for the controlJob to be cancelled. - // see b/148181325 - coroutineContext[Job]?.invokeOnCompletion { - controlJob.cancel() - } - - val dispatcher = transactionDispatcher.acquireTransactionThread(controlJob) - val transactionElement = TransactionElement(controlJob, dispatcher) - val threadLocalElement = - suspendingTransactionId.asContextElement(System.identityHashCode(controlJob)) - return dispatcher + transactionElement + threadLocalElement -} - -/** - * Acquires a thread from the executor and returns a [ContinuationInterceptor] to dispatch - * coroutines to the acquired thread. The [controlJob] is used to control the release of the - * thread by cancelling the job. - */ -private suspend fun CoroutineDispatcher.acquireTransactionThread( - controlJob: Job, -): ContinuationInterceptor { - return suspendCancellableCoroutine { continuation -> - continuation.invokeOnCancellation { - // We got cancelled while waiting to acquire a thread, we can't stop our attempt to - // acquire a thread, but we can cancel the controlling job so once it gets acquired it - // is quickly released. - controlJob.cancel() - } - try { - dispatch(EmptyCoroutineContext) { - runBlocking { - // Thread acquired, resume coroutine - continuation.resume(coroutineContext[ContinuationInterceptor]!!) - controlJob.join() - } - } - } catch (ex: RejectedExecutionException) { - // Couldn't acquire a thread, cancel coroutine - continuation.cancel( - IllegalStateException( - "Unable to acquire a thread to perform the database transaction", - ex, - ), - ) - } - } -} - -/** - * A [CoroutineContext.Element] that indicates there is an on-going database transaction. - */ -@OptIn(ExperimentalAtomicApi::class) -private class TransactionElement( - private val transactionThreadControlJob: Job, - val transactionDispatcher: ContinuationInterceptor, -) : CoroutineContext.Element { - - companion object Key : CoroutineContext.Key - - override val key: CoroutineContext.Key - get() = TransactionElement - - /** - * Number of transactions (including nested ones) started with this element. - * Call [acquire] to increase the count and [release] to decrease it. If the count reaches zero - * when [release] is invoked then the transaction job is cancelled and the transaction thread - * is released. - */ - private val referenceCount = AtomicInt(0) - - fun acquire() { - referenceCount.incrementAndFetch() - } - - fun release() { - val count = referenceCount.decrementAndFetch() - if (count < 0) { - throw IllegalStateException("Transaction was never started or was already released") - } else if (count == 0) { - // Cancel the job that controls the transaction thread, causing it to be released. - transactionThreadControlJob.cancel() - } - } -} diff --git a/data/src/main/java/tachiyomi/data/category/CategoryRepositoryImpl.kt b/data/src/main/java/tachiyomi/data/category/CategoryRepositoryImpl.kt index bf379978e..aa4b0e9ce 100644 --- a/data/src/main/java/tachiyomi/data/category/CategoryRepositoryImpl.kt +++ b/data/src/main/java/tachiyomi/data/category/CategoryRepositoryImpl.kt @@ -1,66 +1,58 @@ package tachiyomi.data.category +import app.cash.sqldelight.async.coroutines.awaitAsList +import app.cash.sqldelight.async.coroutines.awaitAsOneOrNull import kotlinx.coroutines.flow.Flow import tachiyomi.data.Database -import tachiyomi.data.DatabaseHandler +import tachiyomi.data.subscribeToList import tachiyomi.domain.category.model.Category import tachiyomi.domain.category.model.CategoryUpdate import tachiyomi.domain.category.repository.CategoryRepository class CategoryRepositoryImpl( - private val handler: DatabaseHandler, + private val database: Database, ) : CategoryRepository { override suspend fun get(id: Long): Category? { - return handler.awaitOneOrNull { categoriesQueries.getCategory(id, ::mapCategory) } + return database.categoriesQueries + .getCategory(id, ::mapCategory) + .awaitAsOneOrNull() } override suspend fun getAll(): List { - return handler.awaitList { categoriesQueries.getCategories(::mapCategory) } + return database.categoriesQueries + .getCategories(::mapCategory) + .awaitAsList() } override fun getAllAsFlow(): Flow> { - return handler.subscribeToList { categoriesQueries.getCategories(::mapCategory) } + return database.categoriesQueries + .getCategories(::mapCategory) + .subscribeToList() } override suspend fun getCategoriesByMangaId(mangaId: Long): List { - return handler.awaitList { - categoriesQueries.getCategoriesByMangaId(mangaId, ::mapCategory) - } + return database.categoriesQueries + .getCategoriesByMangaId(mangaId, ::mapCategory) + .awaitAsList() } override fun getCategoriesByMangaIdAsFlow(mangaId: Long): Flow> { - return handler.subscribeToList { - categoriesQueries.getCategoriesByMangaId(mangaId, ::mapCategory) - } + return database.categoriesQueries + .getCategoriesByMangaId(mangaId, ::mapCategory) + .subscribeToList() } override suspend fun insert(category: Category) { - handler.await { - categoriesQueries.insert( - name = category.name, - order = category.order, - flags = category.flags, - ) - } + database.categoriesQueries.insert( + name = category.name, + order = category.order, + flags = category.flags, + ) } override suspend fun updatePartial(update: CategoryUpdate) { - handler.await { - updatePartialBlocking(update) - } - } - - override suspend fun updatePartial(updates: List) { - handler.await(inTransaction = true) { - for (update in updates) { - updatePartialBlocking(update) - } - } - } - - private fun Database.updatePartialBlocking(update: CategoryUpdate) { - categoriesQueries.update( + database.categoriesQueries.update( name = update.name, order = update.order, flags = update.flags, @@ -68,18 +60,18 @@ class CategoryRepositoryImpl( ) } - override suspend fun updateAllFlags(flags: Long?) { - handler.await { - categoriesQueries.updateAllFlags(flags) + override suspend fun updatePartial(updates: List) { + database.transaction { + updates.forEach { updatePartial(it) } } } + override suspend fun updateAllFlags(flags: Long?) { + database.categoriesQueries.updateAllFlags(flags) + } + override suspend fun delete(categoryId: Long) { - handler.await { - categoriesQueries.delete( - categoryId = categoryId, - ) - } + database.categoriesQueries.delete(categoryId = categoryId) } private fun mapCategory( diff --git a/data/src/main/java/tachiyomi/data/chapter/ChapterRepositoryImpl.kt b/data/src/main/java/tachiyomi/data/chapter/ChapterRepositoryImpl.kt index ffc4e5446..4bcc75363 100644 --- a/data/src/main/java/tachiyomi/data/chapter/ChapterRepositoryImpl.kt +++ b/data/src/main/java/tachiyomi/data/chapter/ChapterRepositoryImpl.kt @@ -1,23 +1,27 @@ package tachiyomi.data.chapter +import app.cash.sqldelight.async.coroutines.awaitAsList +import app.cash.sqldelight.async.coroutines.awaitAsOne +import app.cash.sqldelight.async.coroutines.awaitAsOneOrNull import kotlinx.coroutines.flow.Flow import logcat.LogPriority import tachiyomi.core.common.util.lang.toLong import tachiyomi.core.common.util.system.logcat -import tachiyomi.data.DatabaseHandler +import tachiyomi.data.Database +import tachiyomi.data.subscribeToList import tachiyomi.domain.chapter.model.Chapter import tachiyomi.domain.chapter.model.ChapterUpdate import tachiyomi.domain.chapter.repository.ChapterRepository class ChapterRepositoryImpl( - private val handler: DatabaseHandler, + private val database: Database, ) : ChapterRepository { override suspend fun addAll(chapters: List): List { return try { - handler.await(inTransaction = true) { + database.transactionWithResult { chapters.map { chapter -> - chaptersQueries.insert( + val lastInsertId = database.chaptersQueries.insert( chapter.mangaId, chapter.url, chapter.name, @@ -30,8 +34,7 @@ class ChapterRepositoryImpl( chapter.dateFetch, chapter.dateUpload, chapter.version, - ) - val lastInsertId = chaptersQueries.selectLastInsertedRowId().executeAsOne() + ).awaitAsOne() chapter.copy(id = lastInsertId) } } @@ -50,9 +53,9 @@ class ChapterRepositoryImpl( } private suspend fun partialUpdate(vararg chapterUpdates: ChapterUpdate) { - handler.await(inTransaction = true) { + database.transaction { chapterUpdates.forEach { chapterUpdate -> - chaptersQueries.update( + database.chaptersQueries.update( mangaId = chapterUpdate.mangaId, url = chapterUpdate.url, name = chapterUpdate.name, @@ -74,59 +77,55 @@ class ChapterRepositoryImpl( override suspend fun removeChaptersWithIds(chapterIds: List) { try { - handler.await { chaptersQueries.removeChaptersWithIds(chapterIds) } + database.chaptersQueries.removeChaptersWithIds(chapterIds) } catch (e: Exception) { logcat(LogPriority.ERROR, e) } } override suspend fun getChapterByMangaId(mangaId: Long, applyScanlatorFilter: Boolean): List { - return handler.awaitList { - chaptersQueries.getChaptersByMangaId(mangaId, applyScanlatorFilter.toLong(), ::mapChapter) - } + return database.chaptersQueries + .getChaptersByMangaId(mangaId, applyScanlatorFilter.toLong(), ::mapChapter) + .awaitAsList() } override suspend fun getScanlatorsByMangaId(mangaId: Long): List { - return handler.awaitList { - chaptersQueries.getScanlatorsByMangaId(mangaId) { it.orEmpty() } - } + return database.chaptersQueries + .getScanlatorsByMangaId(mangaId) { it.orEmpty() } + .awaitAsList() } override fun getScanlatorsByMangaIdAsFlow(mangaId: Long): Flow> { - return handler.subscribeToList { - chaptersQueries.getScanlatorsByMangaId(mangaId) { it.orEmpty() } - } + return database.chaptersQueries + .getScanlatorsByMangaId(mangaId) { it.orEmpty() } + .subscribeToList() } override suspend fun getBookmarkedChaptersByMangaId(mangaId: Long): List { - return handler.awaitList { - chaptersQueries.getBookmarkedChaptersByMangaId( - mangaId, - ::mapChapter, - ) - } + return database.chaptersQueries + .getBookmarkedChaptersByMangaId(mangaId, ::mapChapter) + .awaitAsList() } override suspend fun getChapterById(id: Long): Chapter? { - return handler.awaitOneOrNull { chaptersQueries.getChapterById(id, ::mapChapter) } + return database.chaptersQueries + .getChapterById(id, ::mapChapter) + .awaitAsOneOrNull() } override suspend fun getChapterByMangaIdAsFlow(mangaId: Long, applyScanlatorFilter: Boolean): Flow> { - return handler.subscribeToList { - chaptersQueries.getChaptersByMangaId(mangaId, applyScanlatorFilter.toLong(), ::mapChapter) - } + return database.chaptersQueries + .getChaptersByMangaId(mangaId, applyScanlatorFilter.toLong(), ::mapChapter) + .subscribeToList() } override suspend fun getChapterByUrlAndMangaId(url: String, mangaId: Long): Chapter? { - return handler.awaitOneOrNull { - chaptersQueries.getChapterByUrlAndMangaId( - url, - mangaId, - ::mapChapter, - ) - } + return database.chaptersQueries + .getChapterByUrlAndMangaId(url, mangaId, ::mapChapter) + .awaitAsOneOrNull() } + @Suppress("UNUSED_PARAMETER") private fun mapChapter( id: Long, mangaId: Long, @@ -142,7 +141,6 @@ class ChapterRepositoryImpl( dateUpload: Long, lastModifiedAt: Long, version: Long, - @Suppress("UNUSED_PARAMETER") isSyncing: Long, ): Chapter = Chapter( id = id, diff --git a/data/src/main/java/tachiyomi/data/history/HistoryRepositoryImpl.kt b/data/src/main/java/tachiyomi/data/history/HistoryRepositoryImpl.kt index e9157025c..1dda3da41 100644 --- a/data/src/main/java/tachiyomi/data/history/HistoryRepositoryImpl.kt +++ b/data/src/main/java/tachiyomi/data/history/HistoryRepositoryImpl.kt @@ -1,41 +1,49 @@ package tachiyomi.data.history +import app.cash.sqldelight.async.coroutines.awaitAsList +import app.cash.sqldelight.async.coroutines.awaitAsOne +import app.cash.sqldelight.async.coroutines.awaitAsOneOrNull import kotlinx.coroutines.flow.Flow import logcat.LogPriority import tachiyomi.core.common.util.system.logcat -import tachiyomi.data.DatabaseHandler +import tachiyomi.data.Database +import tachiyomi.data.subscribeToList import tachiyomi.domain.history.model.History import tachiyomi.domain.history.model.HistoryUpdate import tachiyomi.domain.history.model.HistoryWithRelations import tachiyomi.domain.history.repository.HistoryRepository class HistoryRepositoryImpl( - private val handler: DatabaseHandler, + private val database: Database, ) : HistoryRepository { override fun getHistory(query: String): Flow> { - return handler.subscribeToList { - historyViewQueries.history(query, HistoryMapper::mapHistoryWithRelations) - } + return database.historyViewQueries + .history(query, HistoryMapper::mapHistoryWithRelations) + .subscribeToList() } override suspend fun getLastHistory(): HistoryWithRelations? { - return handler.awaitOneOrNull { - historyViewQueries.getLatestHistory(HistoryMapper::mapHistoryWithRelations) - } + return database.historyViewQueries + .getLatestHistory(HistoryMapper::mapHistoryWithRelations) + .awaitAsOneOrNull() } override suspend fun getTotalReadDuration(): Long { - return handler.awaitOne { historyQueries.getReadDuration() } + return database.historyQueries + .getReadDuration() + .awaitAsOne() } override suspend fun getHistoryByMangaId(mangaId: Long): List { - return handler.awaitList { historyQueries.getHistoryByMangaId(mangaId, HistoryMapper::mapHistory) } + return database.historyQueries + .getHistoryByMangaId(mangaId, HistoryMapper::mapHistory) + .awaitAsList() } override suspend fun resetHistory(historyId: Long) { try { - handler.await { historyQueries.resetHistoryById(historyId) } + database.historyQueries.resetHistoryById(historyId) } catch (e: Exception) { logcat(LogPriority.ERROR, throwable = e) } @@ -43,7 +51,7 @@ class HistoryRepositoryImpl( override suspend fun resetHistoryByMangaId(mangaId: Long) { try { - handler.await { historyQueries.resetHistoryByMangaId(mangaId) } + database.historyQueries.resetHistoryByMangaId(mangaId) } catch (e: Exception) { logcat(LogPriority.ERROR, throwable = e) } @@ -51,7 +59,7 @@ class HistoryRepositoryImpl( override suspend fun deleteAllHistory(): Boolean { return try { - handler.await { historyQueries.removeAllHistory() } + database.historyQueries.removeAllHistory() true } catch (e: Exception) { logcat(LogPriority.ERROR, throwable = e) @@ -61,13 +69,11 @@ class HistoryRepositoryImpl( override suspend fun upsertHistory(historyUpdate: HistoryUpdate) { try { - handler.await { - historyQueries.upsert( - historyUpdate.chapterId, - historyUpdate.readAt, - historyUpdate.sessionReadDuration, - ) - } + database.historyQueries.upsert( + historyUpdate.chapterId, + historyUpdate.readAt, + historyUpdate.sessionReadDuration, + ) } catch (e: Exception) { logcat(LogPriority.ERROR, throwable = e) } diff --git a/data/src/main/java/tachiyomi/data/manga/MangaMapper.kt b/data/src/main/java/tachiyomi/data/manga/MangaMapper.kt index 767f3bfc3..7087a0b95 100644 --- a/data/src/main/java/tachiyomi/data/manga/MangaMapper.kt +++ b/data/src/main/java/tachiyomi/data/manga/MangaMapper.kt @@ -6,6 +6,7 @@ import tachiyomi.domain.manga.model.Manga import tachiyomi.domain.manga.model.MangaWithChapterCount object MangaMapper { + @Suppress("UNUSED_PARAMETER") fun mapManga( id: Long, source: Long, @@ -30,7 +31,6 @@ object MangaMapper { lastModifiedAt: Long, favoriteModifiedAt: Long?, version: Long, - @Suppress("UNUSED_PARAMETER") isSyncing: Long, notes: String, ): Manga = Manga( diff --git a/data/src/main/java/tachiyomi/data/manga/MangaRepositoryImpl.kt b/data/src/main/java/tachiyomi/data/manga/MangaRepositoryImpl.kt index f0e81a2ba..c230ad7d9 100644 --- a/data/src/main/java/tachiyomi/data/manga/MangaRepositoryImpl.kt +++ b/data/src/main/java/tachiyomi/data/manga/MangaRepositoryImpl.kt @@ -1,11 +1,17 @@ package tachiyomi.data.manga +import app.cash.sqldelight.async.coroutines.awaitAsList +import app.cash.sqldelight.async.coroutines.awaitAsOne +import app.cash.sqldelight.async.coroutines.awaitAsOneOrNull import kotlinx.coroutines.flow.Flow import logcat.LogPriority import tachiyomi.core.common.util.system.logcat -import tachiyomi.data.DatabaseHandler +import tachiyomi.data.Database import tachiyomi.data.StringListColumnAdapter import tachiyomi.data.UpdateStrategyColumnAdapter +import tachiyomi.data.subscribeToList +import tachiyomi.data.subscribeToOne +import tachiyomi.data.subscribeToOneOrNull import tachiyomi.domain.library.model.LibraryManga import tachiyomi.domain.manga.model.Manga import tachiyomi.domain.manga.model.MangaUpdate @@ -15,73 +21,79 @@ import java.time.LocalDate import java.time.ZoneId class MangaRepositoryImpl( - private val handler: DatabaseHandler, + private val database: Database, ) : MangaRepository { override suspend fun getMangaById(id: Long): Manga { - return handler.awaitOne { mangasQueries.getMangaById(id, MangaMapper::mapManga) } + return database.mangasQueries + .getMangaById(id, MangaMapper::mapManga) + .awaitAsOne() } override suspend fun getMangaByIdAsFlow(id: Long): Flow { - return handler.subscribeToOne { mangasQueries.getMangaById(id, MangaMapper::mapManga) } + return database.mangasQueries + .getMangaById(id, MangaMapper::mapManga) + .subscribeToOne() } override suspend fun getMangaByUrlAndSourceId(url: String, sourceId: Long): Manga? { - return handler.awaitOneOrNull { - mangasQueries.getMangaByUrlAndSource( - url, - sourceId, - MangaMapper::mapManga, - ) - } + return database.mangasQueries + .getMangaByUrlAndSource(url, sourceId, MangaMapper::mapManga) + .awaitAsOneOrNull() } override fun getMangaByUrlAndSourceIdAsFlow(url: String, sourceId: Long): Flow { - return handler.subscribeToOneOrNull { - mangasQueries.getMangaByUrlAndSource( - url, - sourceId, - MangaMapper::mapManga, - ) - } + return database.mangasQueries + .getMangaByUrlAndSource(url, sourceId, MangaMapper::mapManga) + .subscribeToOneOrNull() } override suspend fun getFavorites(): List { - return handler.awaitList { mangasQueries.getFavorites(MangaMapper::mapManga) } + return database.mangasQueries + .getFavorites(MangaMapper::mapManga) + .awaitAsList() } override suspend fun getReadMangaNotInLibrary(): List { - return handler.awaitList { mangasQueries.getReadMangaNotInLibrary(MangaMapper::mapManga) } + return database.mangasQueries + .getReadMangaNotInLibrary(MangaMapper::mapManga) + .awaitAsList() } override suspend fun getLibraryManga(): List { - return handler.awaitList { libraryViewQueries.library(MangaMapper::mapLibraryManga) } + return database.libraryViewQueries + .library(MangaMapper::mapLibraryManga) + .awaitAsList() } override fun getLibraryMangaAsFlow(): Flow> { - return handler.subscribeToList { libraryViewQueries.library(MangaMapper::mapLibraryManga) } + return database.libraryViewQueries + .library(MangaMapper::mapLibraryManga) + .subscribeToList() } override fun getFavoritesBySourceId(sourceId: Long): Flow> { - return handler.subscribeToList { mangasQueries.getFavoriteBySourceId(sourceId, MangaMapper::mapManga) } + return database.mangasQueries + .getFavoriteBySourceId(sourceId, MangaMapper::mapManga) + .subscribeToList() } override suspend fun getDuplicateLibraryManga(id: Long, title: String): List { - return handler.awaitList { - mangasQueries.getDuplicateLibraryManga(id, title, MangaMapper::mapMangaWithChapterCount) - } + return database.mangasQueries + .getDuplicateLibraryManga(id, title, MangaMapper::mapMangaWithChapterCount) + .awaitAsList() } override suspend fun getUpcomingManga(statuses: Set): Flow> { val epochMillis = LocalDate.now().atStartOfDay(ZoneId.systemDefault()).toEpochSecond() * 1000 - return handler.subscribeToList { - mangasQueries.getUpcomingManga(epochMillis, statuses, MangaMapper::mapManga) - } + return database.mangasQueries + .getUpcomingManga(epochMillis, statuses, MangaMapper::mapManga) + .subscribeToList() } override suspend fun resetViewerFlags(): Boolean { return try { - handler.await { mangasQueries.resetViewerFlags() } + database.mangasQueries.resetViewerFlags() true } catch (e: Exception) { logcat(LogPriority.ERROR, e) @@ -90,10 +102,10 @@ class MangaRepositoryImpl( } override suspend fun setMangaCategories(mangaId: Long, categoryIds: List) { - handler.await(inTransaction = true) { - mangas_categoriesQueries.deleteMangaCategoryByMangaId(mangaId) - categoryIds.map { categoryId -> - mangas_categoriesQueries.insert(mangaId, categoryId) + database.transaction { + database.mangas_categoriesQueries.deleteMangaCategoryByMangaId(mangaId) + categoryIds.forEach { categoryId -> + database.mangas_categoriesQueries.insert(mangaId, categoryId) } } } @@ -119,9 +131,9 @@ class MangaRepositoryImpl( } override suspend fun insertNetworkManga(manga: List): List { - return handler.await(inTransaction = true) { + return database.transactionWithResult { manga.map { - mangasQueries.insertNetworkManga( + database.mangasQueries.insertNetworkManga( source = it.source, url = it.url, artist = it.artist, @@ -147,15 +159,15 @@ class MangaRepositoryImpl( updateDetails = it.initialized, mapper = MangaMapper::mapManga, ) - .executeAsOne() + .awaitAsOne() } } } private suspend fun partialUpdate(vararg mangaUpdates: MangaUpdate) { - handler.await(inTransaction = true) { + database.transaction { mangaUpdates.forEach { value -> - mangasQueries.update( + database.mangasQueries.update( source = value.source, url = value.url, artist = value.artist, diff --git a/data/src/main/java/tachiyomi/data/source/SourceRepositoryImpl.kt b/data/src/main/java/tachiyomi/data/source/SourceRepositoryImpl.kt index d4d40d8ba..3a677ae28 100644 --- a/data/src/main/java/tachiyomi/data/source/SourceRepositoryImpl.kt +++ b/data/src/main/java/tachiyomi/data/source/SourceRepositoryImpl.kt @@ -7,7 +7,8 @@ import eu.kanade.tachiyomi.source.online.HttpSource import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.combine import kotlinx.coroutines.flow.map -import tachiyomi.data.DatabaseHandler +import tachiyomi.data.Database +import tachiyomi.data.subscribeToList import tachiyomi.domain.source.model.SourceWithCount import tachiyomi.domain.source.model.StubSource import tachiyomi.domain.source.repository.SourcePagingSource @@ -17,7 +18,7 @@ import tachiyomi.domain.source.model.Source as DomainSource class SourceRepositoryImpl( private val sourceManager: SourceManager, - private val handler: DatabaseHandler, + private val database: Database, ) : SourceRepository { override fun getSources(): Flow> { @@ -39,10 +40,12 @@ class SourceRepositoryImpl( } override fun getSourcesWithFavoriteCount(): Flow>> { - return combine( - handler.subscribeToList { mangasQueries.getSourceIdWithFavoriteCount() }, - sourceManager.catalogueSources, - ) { sourceIdWithFavoriteCount, _ -> sourceIdWithFavoriteCount } + val sourceIdWithFavoriteCountFlow = database.mangasQueries + .getSourceIdWithFavoriteCount() + .subscribeToList() + return combine(sourceIdWithFavoriteCountFlow, sourceManager.catalogueSources) { sourceIdWithFavoriteCount, _ -> + sourceIdWithFavoriteCount + } .map { it.map { (sourceId, count) -> val source = sourceManager.getOrStub(sourceId) @@ -55,17 +58,18 @@ class SourceRepositoryImpl( } override fun getSourcesWithNonLibraryManga(): Flow> { - val sourceIdWithNonLibraryManga = - handler.subscribeToList { mangasQueries.getSourceIdsWithNonLibraryManga() } - return sourceIdWithNonLibraryManga.map { sourceId -> - sourceId.map { (sourceId, count) -> - val source = sourceManager.getOrStub(sourceId) - val domainSource = mapSourceToDomainSource(source).copy( - isStub = source is StubSource, - ) - SourceWithCount(domainSource, count) + return database.mangasQueries + .getSourceIdsWithNonLibraryManga() + .subscribeToList() + .map { sourceId -> + sourceId.map { (sourceId, count) -> + val source = sourceManager.getOrStub(sourceId) + val domainSource = mapSourceToDomainSource(source).copy( + isStub = source is StubSource, + ) + SourceWithCount(domainSource, count) + } } - } } override fun search( diff --git a/data/src/main/java/tachiyomi/data/source/StubSourceRepositoryImpl.kt b/data/src/main/java/tachiyomi/data/source/StubSourceRepositoryImpl.kt index 157e1eb2f..4801bfcdd 100644 --- a/data/src/main/java/tachiyomi/data/source/StubSourceRepositoryImpl.kt +++ b/data/src/main/java/tachiyomi/data/source/StubSourceRepositoryImpl.kt @@ -1,24 +1,30 @@ package tachiyomi.data.source +import app.cash.sqldelight.async.coroutines.awaitAsOneOrNull import kotlinx.coroutines.flow.Flow -import tachiyomi.data.DatabaseHandler +import tachiyomi.data.Database +import tachiyomi.data.subscribeToList import tachiyomi.domain.source.model.StubSource import tachiyomi.domain.source.repository.StubSourceRepository class StubSourceRepositoryImpl( - private val handler: DatabaseHandler, + private val database: Database, ) : StubSourceRepository { override fun subscribeAll(): Flow> { - return handler.subscribeToList { sourcesQueries.findAll(::mapStubSource) } + return database.sourcesQueries + .findAll(::mapStubSource) + .subscribeToList() } override suspend fun getStubSource(id: Long): StubSource? { - return handler.awaitOneOrNull { sourcesQueries.findOne(id, ::mapStubSource) } + return database.sourcesQueries + .findOne(id, ::mapStubSource) + .awaitAsOneOrNull() } override suspend fun upsertStubSource(id: Long, lang: String, name: String) { - handler.await { sourcesQueries.upsert(id, lang, name) } + database.sourcesQueries.upsert(id, lang, name) } private fun mapStubSource( diff --git a/data/src/main/java/tachiyomi/data/track/TrackRepositoryImpl.kt b/data/src/main/java/tachiyomi/data/track/TrackRepositoryImpl.kt index 70ab78c37..bc4ef79b6 100644 --- a/data/src/main/java/tachiyomi/data/track/TrackRepositoryImpl.kt +++ b/data/src/main/java/tachiyomi/data/track/TrackRepositoryImpl.kt @@ -1,43 +1,46 @@ package tachiyomi.data.track +import app.cash.sqldelight.async.coroutines.awaitAsList +import app.cash.sqldelight.async.coroutines.awaitAsOneOrNull import kotlinx.coroutines.flow.Flow -import tachiyomi.data.DatabaseHandler +import tachiyomi.data.Database +import tachiyomi.data.subscribeToList import tachiyomi.domain.track.model.Track import tachiyomi.domain.track.repository.TrackRepository class TrackRepositoryImpl( - private val handler: DatabaseHandler, + private val database: Database, ) : TrackRepository { override suspend fun getTrackById(id: Long): Track? { - return handler.awaitOneOrNull { manga_syncQueries.getTrackById(id, TrackMapper::mapTrack) } + return database.manga_syncQueries + .getTrackById(id, TrackMapper::mapTrack) + .awaitAsOneOrNull() } override suspend fun getTracksByMangaId(mangaId: Long): List { - return handler.awaitList { - manga_syncQueries.getTracksByMangaId(mangaId, TrackMapper::mapTrack) - } + return database.manga_syncQueries + .getTracksByMangaId(mangaId, TrackMapper::mapTrack) + .awaitAsList() } override fun getTracksAsFlow(): Flow> { - return handler.subscribeToList { - manga_syncQueries.getTracks(TrackMapper::mapTrack) - } + return database.manga_syncQueries + .getTracks(TrackMapper::mapTrack) + .subscribeToList() } override fun getTracksByMangaIdAsFlow(mangaId: Long): Flow> { - return handler.subscribeToList { - manga_syncQueries.getTracksByMangaId(mangaId, TrackMapper::mapTrack) - } + return database.manga_syncQueries + .getTracksByMangaId(mangaId, TrackMapper::mapTrack) + .subscribeToList() } override suspend fun delete(mangaId: Long, trackerId: Long) { - handler.await { - manga_syncQueries.delete( - mangaId = mangaId, - syncId = trackerId, - ) - } + database.manga_syncQueries.delete( + mangaId = mangaId, + syncId = trackerId, + ) } override suspend fun insert(track: Track) { @@ -49,9 +52,9 @@ class TrackRepositoryImpl( } private suspend fun insertValues(vararg tracks: Track) { - handler.await(inTransaction = true) { + database.transaction { tracks.forEach { mangaTrack -> - manga_syncQueries.insert( + database.manga_syncQueries.insert( mangaId = mangaTrack.mangaId, syncId = mangaTrack.trackerId, remoteId = mangaTrack.remoteId, diff --git a/data/src/main/java/tachiyomi/data/updates/UpdatesRepositoryImpl.kt b/data/src/main/java/tachiyomi/data/updates/UpdatesRepositoryImpl.kt index dc6453919..8dc663381 100644 --- a/data/src/main/java/tachiyomi/data/updates/UpdatesRepositoryImpl.kt +++ b/data/src/main/java/tachiyomi/data/updates/UpdatesRepositoryImpl.kt @@ -1,14 +1,16 @@ package tachiyomi.data.updates +import app.cash.sqldelight.async.coroutines.awaitAsList import kotlinx.coroutines.flow.Flow import tachiyomi.core.common.util.lang.toLong -import tachiyomi.data.DatabaseHandler +import tachiyomi.data.Database +import tachiyomi.data.subscribeToList import tachiyomi.domain.manga.model.MangaCover import tachiyomi.domain.updates.model.UpdatesWithRelations import tachiyomi.domain.updates.repository.UpdatesRepository class UpdatesRepositoryImpl( - private val databaseHandler: DatabaseHandler, + private val database: Database, ) : UpdatesRepository { override suspend fun awaitWithRead( @@ -16,14 +18,14 @@ class UpdatesRepositoryImpl( after: Long, limit: Long, ): List { - return databaseHandler.awaitList { - updatesViewQueries.getUpdatesByReadStatus( + return database.updatesViewQueries + .getUpdatesByReadStatus( read = read, after = after, limit = limit, mapper = ::mapUpdatesWithRelations, ) - } + .awaitAsList() } override fun subscribeAll( @@ -34,18 +36,17 @@ class UpdatesRepositoryImpl( bookmarked: Boolean?, hideExcludedScanlators: Boolean, ): Flow> { - return databaseHandler.subscribeToList { - updatesViewQueries.getRecentUpdatesWithFilters( + return database.updatesViewQueries + .getRecentUpdatesWithFilters( after = after, limit = limit, - // invert because unread in Kotlin -> read column in SQL read = unread?.let { !it }, started = started?.toLong(), bookmarked = bookmarked, hideExcludedScanlators = hideExcludedScanlators.toLong(), mapper = ::mapUpdatesWithRelations, ) - } + .subscribeToList() } override fun subscribeWithRead( @@ -53,16 +54,17 @@ class UpdatesRepositoryImpl( after: Long, limit: Long, ): Flow> { - return databaseHandler.subscribeToList { - updatesViewQueries.getUpdatesByReadStatus( + return database.updatesViewQueries + .getUpdatesByReadStatus( read = read, after = after, limit = limit, mapper = ::mapUpdatesWithRelations, ) - } + .subscribeToList() } + @Suppress("UNUSED_PARAMETER") private fun mapUpdatesWithRelations( mangaId: Long, mangaTitle: String, diff --git a/data/src/main/sqldelight/tachiyomi/data/categories.sq b/data/src/main/sqldelight/tachiyomi/data/categories.sq index a25e6ca3b..8fbf9af40 100644 --- a/data/src/main/sqldelight/tachiyomi/data/categories.sq +++ b/data/src/main/sqldelight/tachiyomi/data/categories.sq @@ -42,9 +42,12 @@ JOIN mangas_categories MC ON C._id = MC.category_id WHERE MC.manga_id = :mangaId; -insert: -INSERT INTO categories(name, sort, flags) -VALUES (:name, :order, :flags); +insert { + INSERT INTO categories(name, sort, flags) + VALUES (:name, :order, :flags); + + SELECT last_insert_rowid(); +} delete: DELETE FROM categories @@ -60,6 +63,3 @@ WHERE _id = :categoryId; updateAllFlags: UPDATE categories SET flags = coalesce(?, flags); - -selectLastInsertedRowId: -SELECT last_insert_rowid(); \ No newline at end of file diff --git a/data/src/main/sqldelight/tachiyomi/data/chapters.sq b/data/src/main/sqldelight/tachiyomi/data/chapters.sq index f4946391d..668007dc1 100644 --- a/data/src/main/sqldelight/tachiyomi/data/chapters.sq +++ b/data/src/main/sqldelight/tachiyomi/data/chapters.sq @@ -97,10 +97,12 @@ UPDATE chapters SET is_syncing = 0 WHERE is_syncing = 1; -insert: -INSERT INTO chapters(manga_id, url, name, scanlator, read, bookmark, last_page_read, chapter_number, source_order, date_fetch, date_upload, last_modified_at, version, is_syncing) -VALUES (:mangaId, :url, :name, :scanlator, :read, :bookmark, :lastPageRead, :chapterNumber, :sourceOrder, :dateFetch, :dateUpload, 0, :version, 0); +insert { + INSERT INTO chapters(manga_id, url, name, scanlator, read, bookmark, last_page_read, chapter_number, source_order, date_fetch, date_upload, last_modified_at, version, is_syncing) + VALUES (:mangaId, :url, :name, :scanlator, :read, :bookmark, :lastPageRead, :chapterNumber, :sourceOrder, :dateFetch, :dateUpload, 0, :version, 0); + SELECT last_insert_rowid(); +} update: UPDATE chapters SET manga_id = coalesce(:mangaId, manga_id), @@ -117,6 +119,3 @@ SET manga_id = coalesce(:mangaId, manga_id), version = coalesce(:version, version), is_syncing = coalesce(:isSyncing, is_syncing) WHERE _id = :chapterId; - -selectLastInsertedRowId: -SELECT last_insert_rowid(); \ No newline at end of file diff --git a/data/src/main/sqldelight/tachiyomi/data/mangas.sq b/data/src/main/sqldelight/tachiyomi/data/mangas.sq index d98835180..33032424b 100644 --- a/data/src/main/sqldelight/tachiyomi/data/mangas.sq +++ b/data/src/main/sqldelight/tachiyomi/data/mangas.sq @@ -193,9 +193,12 @@ AND ( ) ); -insert: -INSERT INTO mangas(source, url, artist, author, description, genre, title, status, thumbnail_url, favorite, last_update, next_update, initialized, viewer, chapter_flags, cover_last_modified, date_added, update_strategy, calculate_interval, last_modified_at, version, notes) -VALUES (:source, :url, :artist, :author, :description, :genre, :title, :status, :thumbnailUrl, :favorite, :lastUpdate, :nextUpdate, :initialized, :viewerFlags, :chapterFlags, :coverLastModified, :dateAdded, :updateStrategy, :calculateInterval, 0, :version, :notes); +insert { + INSERT INTO mangas(source, url, artist, author, description, genre, title, status, thumbnail_url, favorite, last_update, next_update, initialized, viewer, chapter_flags, cover_last_modified, date_added, update_strategy, calculate_interval, last_modified_at, version, notes) + VALUES (:source, :url, :artist, :author, :description, :genre, :title, :status, :thumbnailUrl, :favorite, :lastUpdate, :nextUpdate, :initialized, :viewerFlags, :chapterFlags, :coverLastModified, :dateAdded, :updateStrategy, :calculateInterval, 0, :version, :notes); + + SELECT last_insert_rowid(); +} update: UPDATE mangas SET @@ -223,9 +226,6 @@ UPDATE mangas SET notes = coalesce(:notes, notes) WHERE _id = :mangaId; -selectLastInsertedRowId: -SELECT last_insert_rowid(); - insertNetworkManga { -- Insert the manga if it doesn't exist already INSERT INTO mangas( diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 3aa44b483..88ed06f50 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -63,7 +63,7 @@ rxJava = "1.3.8" shizuku = "13.1.5" spotless = "8.4.0" sqldelight = "2.3.2" -sqldelight-androidx-driver = "0.0.17" +sqldelight-androidx-driver = "0.1.1" stringSimilarity = "0.1.0" subsamplingScaleImageView = "66e0db195d" swipe = "1.3.0" @@ -169,7 +169,8 @@ shizuku-provider = { module = "dev.rikka.shizuku:provider", version.ref = "shizu spotless-gradle = { module = "com.diffplug.spotless:spotless-plugin-gradle", version.ref = "spotless" } sqldelight-androidxDriver = { module = "com.eygraber:sqldelight-androidx-driver", version.ref = "sqldelight-androidx-driver" } sqldelight-androidxPaging = { module = "app.cash.sqldelight:androidx-paging3-extensions", version.ref = "sqldelight" } -sqldelight-coroutines = { module = "app.cash.sqldelight:coroutines-extensions-jvm", version.ref = "sqldelight" } +sqldelight-async = { module = "app.cash.sqldelight:async-extensions", version.ref = "sqldelight" } +sqldelight-coroutines = { module = "app.cash.sqldelight:coroutines-extensions", version.ref = "sqldelight" } sqldelight-sqliteDialect338 = { module = "app.cash.sqldelight:sqlite-3-38-dialect", version.ref = "sqldelight" } stringSimilarity = { module = "com.aallam.similarity:string-similarity-kotlin", version.ref = "stringSimilarity" } subsamplingScaleImageView = { module = "com.github.tachiyomiorg:subsampling-scale-image-view", version.ref = "subsamplingScaleImageView" } @@ -208,6 +209,6 @@ markdown = ["markdown-core", "markdown-coil"] okhttp = ["okhttp-core", "okhttp-logging", "okhttp-brotli", "okhttp-dnsOverHttps"] serialization = ["kotlinx-serialization-json", "kotlinx-serialization-jsonOkio", "kotlinx-serialization-protobuf", "xmlutil-core", "xmlutil-serialization"] shizuku = ["shizuku-api", "shizuku-provider"] -sqldelight = ["sqldelight-androidxDriver", "sqldelight-coroutines", "sqldelight-androidxPaging"] +sqldelight = ["sqldelight-androidxDriver", "sqldelight-async", "sqldelight-coroutines", "sqldelight-androidxPaging"] test = ["junit-jupiter", "kotest-assertions", "mockk"] voyager = ["voyager-navigator", "voyager-screenModel", "voyager-tabNavigator", "voyager-transitions"]