Use SQLDelight for more SY specific things

This commit is contained in:
Jobobby04
2022-07-03 13:37:27 -04:00
parent e71c9e2775
commit d88c769d3b
83 changed files with 678 additions and 1504 deletions
+45 -86
View File
@@ -5,19 +5,18 @@ package exh
import android.content.Context
import androidx.core.content.edit
import androidx.preference.PreferenceManager
import com.pushtorefresh.storio.sqlite.queries.DeleteQuery
import com.pushtorefresh.storio.sqlite.queries.Query
import com.pushtorefresh.storio.sqlite.queries.RawQuery
import eu.kanade.data.DatabaseHandler
import eu.kanade.domain.manga.interactor.GetManga
import eu.kanade.domain.manga.interactor.GetMangaBySource
import eu.kanade.domain.manga.interactor.UpdateManga
import eu.kanade.domain.manga.model.MangaUpdate
import eu.kanade.tachiyomi.BuildConfig
import eu.kanade.tachiyomi.data.backup.BackupCreatorJob
import eu.kanade.tachiyomi.data.database.DatabaseHelper
import eu.kanade.tachiyomi.data.database.models.Chapter
import eu.kanade.tachiyomi.data.database.models.Manga
import eu.kanade.tachiyomi.data.database.resolvers.MangaUrlPutResolver
import eu.kanade.tachiyomi.data.database.tables.ChapterTable
import eu.kanade.tachiyomi.data.database.tables.MangaTable
import eu.kanade.tachiyomi.data.database.tables.TrackTable
import eu.kanade.tachiyomi.data.library.LibraryUpdateJob
import eu.kanade.tachiyomi.data.preference.MANGA_NON_COMPLETED
import eu.kanade.tachiyomi.data.preference.PreferenceKeys
@@ -29,7 +28,6 @@ import eu.kanade.tachiyomi.extension.ExtensionUpdateJob
import eu.kanade.tachiyomi.network.PREF_DOH_CLOUDFLARE
import eu.kanade.tachiyomi.source.Source
import eu.kanade.tachiyomi.source.SourceManager
import eu.kanade.tachiyomi.source.model.SManga
import eu.kanade.tachiyomi.source.online.all.Hitomi
import eu.kanade.tachiyomi.source.online.all.NHentai
import eu.kanade.tachiyomi.ui.library.LibrarySort
@@ -68,11 +66,15 @@ import uy.kohesive.injekt.injectLazy
import java.io.File
import java.net.URI
import java.net.URISyntaxException
import eu.kanade.domain.manga.model.Manga as DomainManga
object EXHMigrations {
private val db: DatabaseHelper by injectLazy()
private val database: DatabaseHandler by injectLazy()
private val handler: DatabaseHandler by injectLazy()
private val sourceManager: SourceManager by injectLazy()
private val getManga: GetManga by injectLazy()
private val getMangaBySource: GetMangaBySource by injectLazy()
private val updateManga: UpdateManga by injectLazy()
/**
* Performs a migration when the application is updated.
@@ -102,68 +104,41 @@ object EXHMigrations {
val prefs = PreferenceManager.getDefaultSharedPreferences(context)
if (oldVersion under 4) {
db.inTransaction {
updateSourceId(HBROWSE_SOURCE_ID, 6912)
// Migrate BHrowse URLs
val hBrowseManga = db.db.get()
.listOfObjects(Manga::class.java)
.withQuery(
Query.builder()
.table(MangaTable.TABLE)
.where("${MangaTable.COL_SOURCE} = $HBROWSE_SOURCE_ID")
.build(),
)
.prepare()
.executeAsBlocking()
hBrowseManga.forEach {
it.url = it.url + "/c00001/"
}
updateSourceId(HBROWSE_SOURCE_ID, 6912)
// Migrate BHrowse URLs
val hBrowseManga = runBlocking { getMangaBySource.await(HBROWSE_SOURCE_ID) }
val mangaUpdates = hBrowseManga.map {
MangaUpdate(it.id, url = it.url + "/c00001/")
}
db.db.put()
.objects(hBrowseManga)
// Extremely slow without the resolver :/
.withPutResolver(MangaUrlPutResolver())
.prepare()
.executeAsBlocking()
runBlocking {
updateManga.awaitAll(mangaUpdates)
}
}
if (oldVersion under 5) {
db.inTransaction {
// Migrate Hitomi source IDs
updateSourceId(Hitomi.otherId, 6910)
}
// Migrate Hitomi source IDs
updateSourceId(Hitomi.otherId, 6910)
}
if (oldVersion under 6) {
db.inTransaction {
updateSourceId(PERV_EDEN_EN_SOURCE_ID, 6905)
updateSourceId(PERV_EDEN_IT_SOURCE_ID, 6906)
updateSourceId(NHentai.otherId, 6907)
}
updateSourceId(PERV_EDEN_EN_SOURCE_ID, 6905)
updateSourceId(PERV_EDEN_IT_SOURCE_ID, 6906)
updateSourceId(NHentai.otherId, 6907)
}
if (oldVersion under 7) {
db.inTransaction {
val mergedMangas = db.db.get()
.listOfObjects(Manga::class.java)
.withQuery(
Query.builder()
.table(MangaTable.TABLE)
.where("${MangaTable.COL_SOURCE} = $MERGED_SOURCE_ID")
.build(),
)
.prepare()
.executeAsBlocking()
val mergedMangas = runBlocking { getMangaBySource.await(MERGED_SOURCE_ID) }
if (mergedMangas.isNotEmpty()) {
val mangaConfigs = mergedMangas.mapNotNull { mergedManga -> readMangaConfig(mergedManga)?.let { mergedManga to it } }
if (mangaConfigs.isNotEmpty()) {
val mangaToUpdate = mutableListOf<Manga>()
val mangaToUpdate = mutableListOf<MangaUpdate>()
val mergedMangaReferences = mutableListOf<MergedMangaReference>()
mangaConfigs.onEach { mergedManga ->
mergedManga.second.children.firstOrNull()?.url?.let {
if (db.getManga(it, MERGED_SOURCE_ID).executeAsBlocking() != null) return@onEach
mergedManga.first.url = it
}
mangaToUpdate += mergedManga.first
val newFirst = mergedManga.second.children.firstOrNull()?.url?.let {
if (runBlocking { getManga.await(it, MERGED_SOURCE_ID) } != null) return@onEach
mangaToUpdate += MangaUpdate(id = mergedManga.first.id, url = it)
mergedManga.first.copy(url = it)
} ?: mergedManga.first
mergedMangaReferences += MergedMangaReference(
id = null,
isInfoManga = false,
@@ -171,9 +146,9 @@ object EXHMigrations {
chapterSortMode = 0,
chapterPriority = 0,
downloadChapters = false,
mergeId = mergedManga.first.id!!,
mergeId = mergedManga.first.id,
mergeUrl = mergedManga.first.url,
mangaId = mergedManga.first.id!!,
mangaId = mergedManga.first.id,
mangaUrl = mergedManga.first.url,
mangaSourceId = MERGED_SOURCE_ID,
)
@@ -186,7 +161,7 @@ object EXHMigrations {
chapterSortMode = 0,
chapterPriority = 0,
downloadChapters = true,
mergeId = mergedManga.first.id!!,
mergeId = mergedManga.first.id,
mergeUrl = mergedManga.first.url,
mangaId = load.manga.id!!,
mangaUrl = load.manga.url,
@@ -194,12 +169,9 @@ object EXHMigrations {
)
}
}
db.db.put()
.objects(mangaToUpdate)
// Extremely slow without the resolver :/
.withPutResolver(MangaUrlPutResolver())
.prepare()
.executeAsBlocking()
runBlocking {
updateManga.awaitAll(mangaToUpdate)
}
db.insertMergedMangas(mergedMangaReferences).executeAsBlocking()
val loadedMangaList = mangaConfigs.map { it.second.children }.flatten().mapNotNull { it.load(db, sourceManager) }.distinct()
@@ -208,7 +180,7 @@ object EXHMigrations {
.withQuery(
Query.builder()
.table(ChapterTable.TABLE)
.where("${ChapterTable.COL_MANGA_ID} IN (${mergedMangas.filter { it.id != null }.joinToString { it.id.toString() }})")
.where("${ChapterTable.COL_MANGA_ID} IN (${mergedMangas.joinToString { it.id.toString() }})")
.build(),
)
.prepare()
@@ -289,13 +261,9 @@ object EXHMigrations {
}
// Delete old mangadex trackers
db.db.lowLevel().delete(
DeleteQuery.builder()
.table(TrackTable.TABLE)
.where("${TrackTable.COL_SYNC_ID} = ?")
.whereArgs(6)
.build(),
)
runBlocking {
handler.await { ehQueries.deleteBySyncId(6) }
}
}
if (oldVersion under 18) {
val readerTheme = preferences.readerTheme().get()
@@ -407,7 +375,7 @@ object EXHMigrations {
}
if (oldVersion under 31) {
runBlocking {
database.await(true) {
handler.await(true) {
prefs.getStringSet("eh_saved_searches", emptySet())?.forEach {
kotlin.runCatching {
val content = Json.decodeFromString<JsonObject>(it.substringAfter(':'))
@@ -421,7 +389,7 @@ object EXHMigrations {
}
}
}
database.await(true) {
handler.await(true) {
prefs.getStringSet("latest_tab_sources", emptySet())?.forEach {
feed_saved_searchQueries.insertFeedSavedSearch(
_id = null,
@@ -557,7 +525,7 @@ object EXHMigrations {
}
}
private fun readMangaConfig(manga: SManga): MangaConfig? {
private fun readMangaConfig(manga: DomainManga): MangaConfig? {
return MangaConfig.readFromUrl(manga.url)
}
@@ -586,17 +554,8 @@ object EXHMigrations {
private data class LoadedMangaSource(val source: Source, val manga: Manga)
private fun updateSourceId(newId: Long, oldId: Long) {
db.lowLevel().executeSQL(
RawQuery.builder()
.query(
"""
UPDATE ${MangaTable.TABLE}
SET ${MangaTable.COL_SOURCE} = $newId
WHERE ${MangaTable.COL_SOURCE} = $oldId
""".trimIndent(),
)
.affectsTables(MangaTable.TABLE)
.build(),
)
runBlocking {
handler.await { ehQueries.migrateSource(newId, oldId) }
}
}
}
+4 -6
View File
@@ -7,8 +7,7 @@ import eu.kanade.data.chapter.chapterMapper
import eu.kanade.data.manga.mangaMapper
import eu.kanade.domain.chapter.interactor.SyncChaptersWithSource
import eu.kanade.domain.chapter.model.Chapter
import eu.kanade.domain.manga.interactor.GetMangaById
import eu.kanade.domain.manga.interactor.GetMangaByUrlAndSource
import eu.kanade.domain.manga.interactor.GetManga
import eu.kanade.domain.manga.interactor.UpdateManga
import eu.kanade.domain.manga.model.Manga
import eu.kanade.domain.manga.model.toMangaInfo
@@ -25,8 +24,7 @@ import uy.kohesive.injekt.api.get
class GalleryAdder(
private val handler: DatabaseHandler = Injekt.get(),
private val getMangaByUrlAndSource: GetMangaByUrlAndSource = Injekt.get(),
private val getMangaById: GetMangaById = Injekt.get(),
private val getManga: GetManga = Injekt.get(),
private val updateManga: UpdateManga = Injekt.get(),
private val syncChaptersWithSource: SyncChaptersWithSource = Injekt.get(),
private val sourceManager: SourceManager = Injekt.get(),
@@ -120,7 +118,7 @@ class GalleryAdder(
} ?: return GalleryAddEvent.Fail.UnknownType(url, context)
// Use manga in DB if possible, otherwise, make a new manga
var manga = getMangaByUrlAndSource.await(cleanedMangaUrl, source.id)
var manga = getManga.await(cleanedMangaUrl, source.id)
?: handler.awaitOne(true) {
// Insert created manga if not in DB before fetching details
// This allows us to keep the metadata when fetching details
@@ -135,7 +133,7 @@ class GalleryAdder(
// Fetch and copy details
val newManga = source.getMangaDetails(manga.toMangaInfo())
updateManga.awaitUpdateFromSource(manga, newManga, false, Injekt.get())
manga = getMangaById.await(manga.id)!!
manga = getManga.await(manga.id)!!
if (fav) {
updateManga.awaitUpdateFavorite(manga.id, true)
+79 -106
View File
@@ -2,13 +2,16 @@ package exh.debug
import android.app.Application
import androidx.work.WorkManager
import com.pushtorefresh.storio.sqlite.queries.RawQuery
import eu.kanade.data.AndroidDatabaseHandler
import eu.kanade.data.DatabaseHandler
import eu.kanade.data.manga.mangaMapper
import eu.kanade.domain.manga.interactor.GetAllManga
import eu.kanade.domain.manga.interactor.GetExhFavoriteMangaWithMetadata
import eu.kanade.domain.manga.interactor.GetFavorites
import eu.kanade.domain.manga.interactor.GetFlatMetadataById
import eu.kanade.domain.manga.interactor.GetSearchMetadata
import eu.kanade.domain.manga.interactor.InsertFlatMetadata
import eu.kanade.domain.manga.interactor.UpdateManga
import eu.kanade.domain.manga.model.toMangaInfo
import eu.kanade.tachiyomi.data.database.DatabaseHelper
import eu.kanade.tachiyomi.data.database.tables.MangaTable
import eu.kanade.tachiyomi.data.preference.PreferencesHelper
import eu.kanade.tachiyomi.source.SourceManager
@@ -17,18 +20,10 @@ import exh.EXHMigrations
import exh.eh.EHentaiThrottleManager
import exh.eh.EHentaiUpdateWorker
import exh.metadata.metadata.EHentaiSearchMetadata
import exh.metadata.metadata.base.awaitFlatMetadataForManga
import exh.metadata.metadata.base.awaitInsertFlatMetadata
import exh.source.EH_SOURCE_ID
import exh.source.EXH_SOURCE_ID
import exh.source.isEhBasedManga
import exh.source.nHentaiSourceIds
import exh.util.cancellable
import exh.util.executeOnIO
import exh.util.jobScheduler
import kotlinx.coroutines.flow.asFlow
import kotlinx.coroutines.flow.mapNotNull
import kotlinx.coroutines.flow.toList
import kotlinx.coroutines.runBlocking
import uy.kohesive.injekt.injectLazy
import java.util.UUID
@@ -36,12 +31,16 @@ import java.util.UUID
@Suppress("unused")
object DebugFunctions {
val app: Application by injectLazy()
val db: DatabaseHelper by injectLazy()
val handler: DatabaseHandler by injectLazy()
val prefs: PreferencesHelper by injectLazy()
val sourceManager: SourceManager by injectLazy()
val updateManga: UpdateManga by injectLazy()
val getFavorites: GetFavorites by injectLazy()
val getFlatMetadataById: GetFlatMetadataById by injectLazy()
val insertFlatMetadata: InsertFlatMetadata by injectLazy()
val getExhFavoriteMangaWithMetadata: GetExhFavoriteMangaWithMetadata by injectLazy()
val getSearchMetadata: GetSearchMetadata by injectLazy()
val getAllManga: GetAllManga by injectLazy()
fun forceUpgradeMigration() {
prefs.ehLastVersionCode().set(1)
@@ -55,18 +54,11 @@ object DebugFunctions {
fun resetAgedFlagInEXHManga() {
runBlocking {
val metadataManga = db.getFavoriteMangaWithMetadata().executeOnIO()
val allManga = metadataManga.asFlow().cancellable().mapNotNull { manga ->
if (manga.isEhBasedManga()) manga
else null
}.toList()
allManga.forEach { manga ->
val meta = handler.awaitFlatMetadataForManga(manga.id!!)?.raise<EHentaiSearchMetadata>() ?: return@forEach
getExhFavoriteMangaWithMetadata.await().forEach { manga ->
val meta = getFlatMetadataById.await(manga.id)?.raise<EHentaiSearchMetadata>() ?: return@forEach
// remove age flag
meta.aged = false
handler.awaitInsertFlatMetadata(meta.flatten())
insertFlatMetadata.await(meta)
}
}
}
@@ -77,8 +69,7 @@ object DebugFunctions {
fun resetEHGalleriesForUpdater() {
throttleManager.resetThrottle()
runBlocking {
val allManga = handler
.awaitList { mangasQueries.getEhMangaWithMetadata(EH_SOURCE_ID, EXH_SOURCE_ID, mangaMapper) }
val allManga = getExhFavoriteMangaWithMetadata.await()
val eh = sourceManager.get(EH_SOURCE_ID)
val ex = sourceManager.get(EXH_SOURCE_ID)
@@ -99,11 +90,8 @@ object DebugFunctions {
fun getEHMangaListWithAgedFlagInfo(): String {
return runBlocking {
val allManga = handler
.awaitList { mangasQueries.getEhMangaWithMetadata(EH_SOURCE_ID, EXH_SOURCE_ID, mangaMapper) }
allManga.map { manga ->
val meta = handler.awaitFlatMetadataForManga(manga.id)?.raise<EHentaiSearchMetadata>() ?: return@map
getExhFavoriteMangaWithMetadata.await().map { manga ->
val meta = getFlatMetadataById.await(manga.id)?.raise<EHentaiSearchMetadata>() ?: return@map
"Aged: ${meta.aged}\t Title: ${manga.title}"
}
}.joinToString(",\n")
@@ -111,10 +99,9 @@ object DebugFunctions {
fun countAgedFlagInEXHManga(): Int {
return runBlocking {
handler
.awaitList { mangasQueries.getEhMangaWithMetadata(EH_SOURCE_ID, EXH_SOURCE_ID, mangaMapper) }
getExhFavoriteMangaWithMetadata.await()
.count { manga ->
val meta = handler.awaitFlatMetadataForManga(manga.id)
val meta = getFlatMetadataById.await(manga.id)
?.raise<EHentaiSearchMetadata>()
?: return@count false
meta.aged
@@ -123,31 +110,30 @@ object DebugFunctions {
}
fun addAllMangaInDatabaseToLibrary() {
db.inTransaction {
db.lowLevel().executeSQL(
RawQuery.builder()
.query(
"""
UPDATE ${MangaTable.TABLE}
SET ${MangaTable.COL_FAVORITE} = 1
""".trimIndent(),
)
.affectsTables(MangaTable.TABLE)
.build(),
(handler as AndroidDatabaseHandler).rawQuery {
it.execute(
null,
"""
UPDATE ${MangaTable.TABLE}
SET ${MangaTable.COL_FAVORITE} = 1
""".trimIndent(),
0,
)
}
}
fun countMangaInDatabaseInLibrary() = runBlocking { getFavorites.await().size }
fun countMangaInDatabaseNotInLibrary() = db.getMangas().executeAsBlocking().count { !it.favorite }
fun countMangaInDatabaseNotInLibrary() = runBlocking { getAllManga.await() }.count { !it.favorite }
fun countMangaInDatabase() = db.getMangas().executeAsBlocking().size
fun countMangaInDatabase() = runBlocking { getAllManga.await() }.size
fun countMetadataInDatabase() = db.getSearchMetadata().executeAsBlocking().size
fun countMetadataInDatabase() = runBlocking { getSearchMetadata.await().size }
fun countMangaInLibraryWithMissingMetadata() = db.getMangas().executeAsBlocking().count {
it.favorite && db.getSearchMetadataForManga(it.id!!).executeAsBlocking() == null
fun countMangaInLibraryWithMissingMetadata() = runBlocking {
runBlocking { getAllManga.await() }.count {
it.favorite && getSearchMetadata.await(it.id) == null
}
}
fun clearSavedSearches() = runBlocking { handler.await { saved_searchQueries.deleteAll() } }
@@ -214,18 +200,17 @@ object DebugFunctions {
fun cancelAllScheduledJobs() = app.jobScheduler.cancelAll()
private fun convertSources(from: Long, to: Long) {
db.lowLevel().executeSQL(
RawQuery.builder()
.query(
"""
UPDATE ${MangaTable.TABLE}
SET ${MangaTable.COL_SOURCE} = $to
WHERE ${MangaTable.COL_SOURCE} = $from
""".trimIndent(),
)
.affectsTables(MangaTable.TABLE)
.build(),
)
(handler as AndroidDatabaseHandler).rawQuery {
it.execute(
null,
"""
UPDATE ${MangaTable.TABLE}
SET ${MangaTable.COL_SOURCE} = $to
WHERE ${MangaTable.COL_SOURCE} = $from
""".trimIndent(),
0,
)
}
}
/*fun copyEHentaiSavedSearchesToExhentai() {
@@ -307,34 +292,28 @@ object DebugFunctions {
}*/
fun fixReaderViewerBackupBug() {
db.inTransaction {
db.lowLevel().executeSQL(
RawQuery.builder()
.query(
"""
UPDATE ${MangaTable.TABLE}
SET ${MangaTable.COL_VIEWER} = 0
WHERE ${MangaTable.COL_VIEWER} = -1
""".trimIndent(),
)
.affectsTables(MangaTable.TABLE)
.build(),
(handler as AndroidDatabaseHandler).rawQuery {
it.execute(
null,
"""
UPDATE ${MangaTable.TABLE}
SET ${MangaTable.COL_VIEWER} = 0
WHERE ${MangaTable.COL_VIEWER} = -1
""".trimIndent(),
0,
)
}
}
fun resetReaderViewerForAllManga() {
db.inTransaction {
db.lowLevel().executeSQL(
RawQuery.builder()
.query(
"""
UPDATE ${MangaTable.TABLE}
SET ${MangaTable.COL_VIEWER} = 0
""".trimIndent(),
)
.affectsTables(MangaTable.TABLE)
.build(),
(handler as AndroidDatabaseHandler).rawQuery {
it.execute(
null,
"""
UPDATE ${MangaTable.TABLE}
SET ${MangaTable.COL_VIEWER} = 0
""".trimIndent(),
0,
)
}
}
@@ -344,34 +323,28 @@ object DebugFunctions {
.also { it.remove(NHentai.otherId) }
.joinToString(separator = ",")
db.inTransaction {
db.lowLevel().executeSQL(
RawQuery.builder()
.query(
"""
UPDATE ${MangaTable.TABLE}
SET ${MangaTable.COL_SOURCE} = ${NHentai.otherId}
WHERE ${MangaTable.COL_FAVORITE} = 1 AND ${MangaTable.COL_SOURCE} in ($sources)
""".trimIndent(),
)
.affectsTables(MangaTable.TABLE)
.build(),
(handler as AndroidDatabaseHandler).rawQuery {
it.execute(
null,
"""
UPDATE ${MangaTable.TABLE}
SET ${MangaTable.COL_SOURCE} = ${NHentai.otherId}
WHERE ${MangaTable.COL_FAVORITE} = 1 AND ${MangaTable.COL_SOURCE} in ($sources)
""".trimIndent(),
0,
)
}
}
fun resetFilteredScanlatorsForAllManga() {
db.inTransaction {
db.lowLevel().executeSQL(
RawQuery.builder()
.query(
"""
UPDATE ${MangaTable.TABLE}
SET ${MangaTable.COL_FILTERED_SCANLATORS} = NULL
""".trimIndent(),
)
.affectsTables(MangaTable.TABLE)
.build(),
(handler as AndroidDatabaseHandler).rawQuery {
it.execute(
null,
"""
UPDATE ${MangaTable.TABLE}
SET ${MangaTable.COL_FILTERED_SCANLATORS} = NULL
""".trimIndent(),
0,
)
}
}
@@ -14,7 +14,7 @@ import eu.kanade.domain.history.interactor.RemoveHistoryById
import eu.kanade.domain.history.interactor.UpsertHistory
import eu.kanade.domain.history.model.History
import eu.kanade.domain.history.model.HistoryUpdate
import eu.kanade.domain.manga.interactor.GetMangaById
import eu.kanade.domain.manga.interactor.GetManga
import eu.kanade.domain.manga.interactor.UpdateManga
import eu.kanade.domain.manga.model.Manga
import eu.kanade.domain.manga.model.MangaUpdate
@@ -37,7 +37,7 @@ class EHentaiUpdateHelper(context: Context) {
)
private val handler: DatabaseHandler by injectLazy()
private val getChapterByMangaId: GetChapterByMangaId by injectLazy()
private val getMangaById: GetMangaById by injectLazy()
private val getManga: GetManga by injectLazy()
private val updateManga: UpdateManga by injectLazy()
private val setMangaCategories: SetMangaCategories by injectLazy()
private val getCategories: GetCategories by injectLazy()
@@ -64,7 +64,7 @@ class EHentaiUpdateHelper(context: Context) {
.mapNotNull { mangaId ->
coroutineScope {
val manga = async(Dispatchers.IO) {
getMangaById.await(mangaId)
getManga.await(mangaId)
}
val chapterList = async(Dispatchers.IO) {
getChapterByMangaId.await(mangaId)
@@ -21,7 +21,6 @@ import eu.kanade.domain.manga.interactor.UpdateManga
import eu.kanade.domain.manga.model.Manga
import eu.kanade.domain.manga.model.toDbManga
import eu.kanade.domain.manga.model.toMangaInfo
import eu.kanade.tachiyomi.data.database.DatabaseHelper
import eu.kanade.tachiyomi.data.library.LibraryUpdateNotifier
import eu.kanade.tachiyomi.data.preference.DEVICE_CHARGING
import eu.kanade.tachiyomi.data.preference.DEVICE_ONLY_ON_WIFI
@@ -35,7 +34,7 @@ import exh.eh.EHentaiUpdateWorkerConstants.UPDATES_PER_ITERATION
import exh.log.xLog
import exh.metadata.metadata.EHentaiSearchMetadata
import exh.metadata.metadata.base.awaitFlatMetadataForManga
import exh.metadata.metadata.base.insertFlatMetadataAsync
import exh.metadata.metadata.base.awaitInsertFlatMetadata
import exh.source.EH_SOURCE_ID
import exh.source.EXH_SOURCE_ID
import exh.source.isEhBasedManga
@@ -54,7 +53,6 @@ import kotlin.time.Duration.Companion.days
class EHentaiUpdateWorker(private val context: Context, workerParams: WorkerParameters) :
CoroutineWorker(context, workerParams) {
private val db: DatabaseHelper by injectLazy()
private val handler: DatabaseHandler by injectLazy()
private val prefs: PreferencesHelper by injectLazy()
private val sourceManager: SourceManager by injectLazy()
@@ -228,7 +226,7 @@ class EHentaiUpdateWorker(private val context: Context, workerParams: WorkerPara
// Age dead galleries
logger.d("Aged %s - notfound", manga.id)
meta.aged = true
db.insertFlatMetadataAsync(meta.flatten()).await()
handler.awaitInsertFlatMetadata(meta.flatten())
}
throw GalleryNotUpdatedException(false, t)
}
@@ -8,7 +8,7 @@ import eu.kanade.data.DatabaseHandler
import eu.kanade.domain.category.interactor.GetCategories
import eu.kanade.domain.category.interactor.SetMangaCategories
import eu.kanade.domain.category.model.Category
import eu.kanade.domain.manga.interactor.GetMangaByUrlAndSource
import eu.kanade.domain.manga.interactor.GetManga
import eu.kanade.domain.manga.interactor.UpdateManga
import eu.kanade.domain.manga.model.Manga
import eu.kanade.tachiyomi.R
@@ -50,7 +50,7 @@ import kotlin.time.Duration.Companion.seconds
class FavoritesSyncHelper(val context: Context) {
private val handler: DatabaseHandler by injectLazy()
private val getCategories: GetCategories by injectLazy()
private val getMangaByUrlAndSource: GetMangaByUrlAndSource by injectLazy()
private val getManga: GetManga by injectLazy()
private val updateManga: UpdateManga by injectLazy()
private val setMangaCategories: SetMangaCategories by injectLazy()
@@ -332,7 +332,7 @@ class FavoritesSyncHelper(val context: Context) {
EXH_SOURCE_ID,
EH_SOURCE_ID,
).forEach {
val manga = getMangaByUrlAndSource.await(url, it)
val manga = getManga.await(url, it)
if (manga?.favorite == true) {
updateManga.awaitUpdateFavorite(manga.id, false)
@@ -1,9 +1,10 @@
package exh.favorites
import eu.kanade.data.DatabaseHandler
import eu.kanade.data.exh.favoriteEntryMapper
import eu.kanade.domain.category.interactor.GetCategories
import eu.kanade.domain.manga.interactor.DeleteFavoriteEntries
import eu.kanade.domain.manga.interactor.GetFavoriteEntries
import eu.kanade.domain.manga.interactor.GetFavorites
import eu.kanade.domain.manga.interactor.InsertFavoriteEntries
import eu.kanade.domain.manga.model.Manga
import eu.kanade.tachiyomi.data.database.models.toDomainManga
import eu.kanade.tachiyomi.source.online.all.EHentai
@@ -19,9 +20,11 @@ import kotlinx.coroutines.flow.toList
import uy.kohesive.injekt.injectLazy
class LocalFavoritesStorage {
private val handler: DatabaseHandler by injectLazy()
private val getFavorites: GetFavorites by injectLazy()
private val getCategories: GetCategories by injectLazy()
private val deleteFavoriteEntries: DeleteFavoriteEntries by injectLazy()
private val getFavoriteEntries: GetFavoriteEntries by injectLazy()
private val insertFavoriteEntries: InsertFavoriteEntries by injectLazy()
suspend fun getChangedDbEntries() = getFavorites.await()
.asFlow()
@@ -48,30 +51,20 @@ class LocalFavoritesStorage {
.parseToFavoriteEntries()
// Delete old snapshot
handler.await { eh_favoritesQueries.deleteAll() }
deleteFavoriteEntries.await()
// Insert new snapshots
handler.await(true) {
dbMangas.toList().forEach {
eh_favoritesQueries.insertEhFavorites(
it.id,
it.title,
it.gid,
it.token,
it.category.toLong(),
)
}
}
insertFavoriteEntries.await(dbMangas.toList())
}
suspend fun clearSnapshots() {
handler.await { eh_favoritesQueries.deleteAll() }
deleteFavoriteEntries.await()
}
private suspend fun Flow<FavoriteEntry>.getChangedEntries(): ChangeSet {
val terminated = toList()
val databaseEntries = handler.awaitList { eh_favoritesQueries.selectAll(favoriteEntryMapper) }
val databaseEntries = getFavoriteEntries.await()
val added = terminated.filter {
queryListForEntry(databaseEntries, it) == null
@@ -1,65 +0,0 @@
package exh.favorites.sql.mappers
import android.database.Cursor
import androidx.core.content.contentValuesOf
import com.pushtorefresh.storio.sqlite.SQLiteTypeMapping
import com.pushtorefresh.storio.sqlite.operations.delete.DefaultDeleteResolver
import com.pushtorefresh.storio.sqlite.operations.get.DefaultGetResolver
import com.pushtorefresh.storio.sqlite.operations.put.DefaultPutResolver
import com.pushtorefresh.storio.sqlite.queries.DeleteQuery
import com.pushtorefresh.storio.sqlite.queries.InsertQuery
import com.pushtorefresh.storio.sqlite.queries.UpdateQuery
import exh.favorites.sql.models.FavoriteEntry
import exh.favorites.sql.tables.FavoriteEntryTable.COL_CATEGORY
import exh.favorites.sql.tables.FavoriteEntryTable.COL_GID
import exh.favorites.sql.tables.FavoriteEntryTable.COL_ID
import exh.favorites.sql.tables.FavoriteEntryTable.COL_TITLE
import exh.favorites.sql.tables.FavoriteEntryTable.COL_TOKEN
import exh.favorites.sql.tables.FavoriteEntryTable.TABLE
class FavoriteEntryTypeMapping : SQLiteTypeMapping<FavoriteEntry>(
FavoriteEntryPutResolver(),
FavoriteEntryGetResolver(),
FavoriteEntryDeleteResolver(),
)
class FavoriteEntryPutResolver : DefaultPutResolver<FavoriteEntry>() {
override fun mapToInsertQuery(obj: FavoriteEntry) = InsertQuery.builder()
.table(TABLE)
.build()
override fun mapToUpdateQuery(obj: FavoriteEntry) = UpdateQuery.builder()
.table(TABLE)
.where("$COL_ID = ?")
.whereArgs(obj.id)
.build()
override fun mapToContentValues(obj: FavoriteEntry) = contentValuesOf(
COL_ID to obj.id,
COL_TITLE to obj.title,
COL_GID to obj.gid,
COL_TOKEN to obj.token,
COL_CATEGORY to obj.category,
)
}
class FavoriteEntryGetResolver : DefaultGetResolver<FavoriteEntry>() {
override fun mapFromCursor(cursor: Cursor): FavoriteEntry = FavoriteEntry(
id = cursor.getLong(cursor.getColumnIndexOrThrow(COL_ID)),
title = cursor.getString(cursor.getColumnIndexOrThrow(COL_TITLE)),
gid = cursor.getString(cursor.getColumnIndexOrThrow(COL_GID)),
token = cursor.getString(cursor.getColumnIndexOrThrow(COL_TOKEN)),
category = cursor.getInt(cursor.getColumnIndexOrThrow(COL_CATEGORY)),
)
}
class FavoriteEntryDeleteResolver : DefaultDeleteResolver<FavoriteEntry>() {
override fun mapToDeleteQuery(obj: FavoriteEntry) = DeleteQuery.builder()
.table(TABLE)
.where("$COL_ID = ?")
.whereArgs(obj.id)
.build()
}
@@ -1,30 +0,0 @@
package exh.favorites.sql.queries
import com.pushtorefresh.storio.sqlite.queries.DeleteQuery
import com.pushtorefresh.storio.sqlite.queries.Query
import eu.kanade.tachiyomi.data.database.DbProvider
import exh.favorites.sql.models.FavoriteEntry
import exh.favorites.sql.tables.FavoriteEntryTable
interface FavoriteEntryQueries : DbProvider {
fun getFavoriteEntries() = db.get()
.listOfObjects(FavoriteEntry::class.java)
.withQuery(
Query.builder()
.table(FavoriteEntryTable.TABLE)
.build(),
)
.prepare()
fun insertFavoriteEntries(favoriteEntries: List<FavoriteEntry>) = db.put()
.objects(favoriteEntries)
.prepare()
fun deleteAllFavoriteEntries() = db.delete()
.byQuery(
DeleteQuery.builder()
.table(FavoriteEntryTable.TABLE)
.build(),
)
.prepare()
}
@@ -1,16 +0,0 @@
package exh.favorites.sql.tables
object FavoriteEntryTable {
const val TABLE = "eh_favorites"
const val COL_ID = "_id"
const val COL_TITLE = "title"
const val COL_GID = "gid"
const val COL_TOKEN = "token"
const val COL_CATEGORY = "category"
}
@@ -1,7 +1,8 @@
package exh.md.handlers
import eu.kanade.data.DatabaseHandler
import eu.kanade.domain.manga.interactor.GetMangaByUrlAndSource
import eu.kanade.domain.manga.interactor.GetFlatMetadataById
import eu.kanade.domain.manga.interactor.GetManga
import eu.kanade.domain.manga.interactor.InsertFlatMetadata
import eu.kanade.tachiyomi.source.model.SManga
import exh.log.xLogE
import exh.md.dto.ChapterDataDto
@@ -13,8 +14,6 @@ import exh.md.utils.MdUtil
import exh.md.utils.asMdMap
import exh.metadata.metadata.MangaDexSearchMetadata
import exh.metadata.metadata.base.RaisedTag
import exh.metadata.metadata.base.awaitFlatMetadataForManga
import exh.metadata.metadata.base.awaitInsertFlatMetadata
import exh.util.capitalize
import exh.util.floor
import exh.util.nullIfEmpty
@@ -26,8 +25,9 @@ import java.util.Locale
class ApiMangaParser(
private val lang: String,
) {
private val handler: DatabaseHandler by injectLazy()
private val getMangaByUrlAndSource: GetMangaByUrlAndSource by injectLazy()
private val getManga: GetManga by injectLazy()
private val insertFlatMetadata: InsertFlatMetadata by injectLazy()
private val getFlatMetadataById: GetFlatMetadataById by injectLazy()
val metaClass = MangaDexSearchMetadata::class
@@ -46,16 +46,16 @@ class ApiMangaParser(
simpleChapters: List<String>,
statistics: StatisticsMangaDto?,
): MangaInfo {
val mangaId = getMangaByUrlAndSource.await(manga.key, sourceId)?.id
val mangaId = getManga.await(manga.key, sourceId)?.id
val metadata = if (mangaId != null) {
val flatMetadata = handler.awaitFlatMetadataForManga(mangaId)
val flatMetadata = getFlatMetadataById.await(mangaId)
flatMetadata?.raise(metaClass) ?: newMetaInstance()
} else newMetaInstance()
parseIntoMetadata(metadata, input, simpleChapters, statistics)
if (mangaId != null) {
metadata.mangaId = mangaId
handler.awaitInsertFlatMetadata(metadata.flatten())
insertFlatMetadata.await(metadata.flatten())
}
return metadata.createMangaInfo(manga)
@@ -1,6 +1,6 @@
package exh.md.similar
import eu.kanade.domain.manga.interactor.GetMangaById
import eu.kanade.domain.manga.interactor.GetManga
import eu.kanade.domain.manga.model.Manga
import eu.kanade.tachiyomi.source.model.FilterList
import eu.kanade.tachiyomi.source.online.all.MangaDex
@@ -17,14 +17,14 @@ import uy.kohesive.injekt.api.get
class MangaDexSimilarPresenter(
val mangaId: Long,
sourceId: Long,
private val getMangaById: GetMangaById = Injekt.get(),
private val getManga: GetManga = Injekt.get(),
) : BrowseSourcePresenter(sourceId) {
var manga: Manga? = null
override fun createPager(query: String, filters: FilterList): Pager {
val sourceAsMangaDex = source.getMainSource() as MangaDex
this.manga = runBlocking { getMangaById.await(mangaId) }
this.manga = runBlocking { getManga.await(mangaId) }
return MangaDexSimilarPager(manga!!, sourceAsMangaDex)
}
}
@@ -4,15 +4,9 @@ import com.pushtorefresh.storio.sqlite.queries.DeleteQuery
import com.pushtorefresh.storio.sqlite.queries.Query
import com.pushtorefresh.storio.sqlite.queries.RawQuery
import eu.kanade.tachiyomi.data.database.DbProvider
import eu.kanade.tachiyomi.data.database.inTransaction
import eu.kanade.tachiyomi.data.database.models.Chapter
import eu.kanade.tachiyomi.data.database.models.Manga
import eu.kanade.tachiyomi.data.database.queries.getAllMergedMangaQuery
import eu.kanade.tachiyomi.data.database.queries.getMergedChaptersQuery
import eu.kanade.tachiyomi.data.database.queries.getMergedMangaForDownloadingQuery
import eu.kanade.tachiyomi.data.database.queries.getMergedMangaFromUrlQuery
import eu.kanade.tachiyomi.data.database.queries.getMergedMangaQuery
import eu.kanade.tachiyomi.data.database.tables.ChapterTable
import exh.merged.sql.models.MergedMangaReference
import exh.merged.sql.resolvers.MergeMangaSettingsPutResolver
import exh.merged.sql.resolvers.MergedMangaIdPutResolver
@@ -31,17 +25,6 @@ interface MergedQueries : DbProvider {
)
.prepare()
fun getMergedMangaReferences(mergedMangaUrl: String) = db.get()
.listOfObjects(MergedMangaReference::class.java)
.withQuery(
Query.builder()
.table(MergedTable.TABLE)
.where("${MergedTable.COL_MERGE_URL} = ?")
.whereArgs(mergedMangaUrl)
.build(),
)
.prepare()
fun deleteMangaForMergedManga(mergedMangaId: Long) = db.delete()
.byQuery(
DeleteQuery.builder()
@@ -72,56 +55,6 @@ interface MergedQueries : DbProvider {
)
.prepare()
fun getMergedMangas(mergedMangaUrl: String) = db.get()
.listOfObjects(Manga::class.java)
.withQuery(
RawQuery.builder()
.query(getMergedMangaFromUrlQuery())
.args(mergedMangaUrl)
.build(),
)
.prepare()
fun getMergedMangas() = db.get()
.listOfObjects(Manga::class.java)
.withQuery(
RawQuery.builder()
.query(getAllMergedMangaQuery())
.build(),
)
.prepare()
fun deleteMangaForMergedManga(mergedMangaUrl: String) = db.delete()
.byQuery(
DeleteQuery.builder()
.table(MergedTable.TABLE)
.where("${MergedTable.COL_MERGE_URL} = ?")
.whereArgs(mergedMangaUrl)
.build(),
)
.prepare()
fun getMergedMangaReferences() = db.get()
.listOfObjects(MergedMangaReference::class.java)
.withQuery(
Query.builder()
.table(MergedTable.TABLE)
.orderBy(MergedTable.COL_ID)
.build(),
)
.prepare()
fun getChaptersByMergedMangaId(mergedMangaId: Long) = db.get()
.listOfObjects(Chapter::class.java)
.withQuery(
RawQuery.builder()
.query(getMergedChaptersQuery())
.args(mergedMangaId)
.observesTables(ChapterTable.TABLE, MergedTable.TABLE)
.build(),
)
.prepare()
fun insertMergedManga(mergedManga: MergedMangaReference) = db.put().`object`(mergedManga).prepare()
fun insertNewMergedMangaId(mergedManga: MergedMangaReference) = db.put().`object`(mergedManga).withPutResolver(MergedMangaIdPutResolver()).prepare()
@@ -133,20 +66,4 @@ interface MergedQueries : DbProvider {
fun updateMergeMangaSettings(mergeManga: MergedMangaReference) = db.put().`object`(mergeManga).withPutResolver(MergeMangaSettingsPutResolver()).prepare()
fun deleteMergedManga(mergedManga: MergedMangaReference) = db.delete().`object`(mergedManga).prepare()
fun deleteAllMergedManga() = db.delete().byQuery(
DeleteQuery.builder()
.table(MergedTable.TABLE)
.build(),
)
.prepare()
fun setMangasForMergedManga(mergedMangaId: Long, mergedMangas: List<MergedMangaReference>) {
db.inTransaction {
deleteMangaForMergedManga(mergedMangaId).executeAsBlocking()
mergedMangas.chunked(100) { chunk ->
insertMergedMangas(chunk).executeAsBlocking()
}
}
}
}
@@ -1,23 +1,15 @@
package exh.metadata.metadata.base
import com.pushtorefresh.storio.operations.PreparedOperation
import eu.kanade.data.AndroidDatabaseHandler
import eu.kanade.data.DatabaseHandler
import eu.kanade.data.exh.searchMetadataMapper
import eu.kanade.data.exh.searchTagMapper
import eu.kanade.data.exh.searchTitleMapper
import eu.kanade.tachiyomi.data.database.DatabaseHelper
import exh.metadata.sql.models.SearchMetadata
import exh.metadata.sql.models.SearchTag
import exh.metadata.sql.models.SearchTitle
import kotlinx.coroutines.Deferred
import kotlinx.coroutines.async
import kotlinx.coroutines.coroutineScope
import kotlinx.serialization.InternalSerializationApi
import kotlinx.serialization.Serializable
import kotlinx.serialization.serializer
import rx.Completable
import rx.Single
import kotlin.reflect.KClass
@Serializable
@@ -36,17 +28,7 @@ data class FlatMetadata(
}
}
fun DatabaseHandler.getFlatMetadataForManga(mangaId: Long): FlatMetadata? {
this as AndroidDatabaseHandler
val meta = db.search_metadataQueries.selectByMangaId(mangaId, searchMetadataMapper).executeAsOneOrNull()
return if (meta != null) {
val tags = db.search_tagsQueries.selectByMangaId(mangaId, searchTagMapper).executeAsList()
val titles = db.search_titlesQueries.selectByMangaId(mangaId, searchTitleMapper).executeAsList()
FlatMetadata(meta, tags, titles)
} else null
}
@Deprecated("Replace with GetFlatMetadataById")
suspend fun DatabaseHandler.awaitFlatMetadataForManga(mangaId: Long): FlatMetadata? {
return await {
val meta = search_metadataQueries.selectByMangaId(mangaId, searchMetadataMapper).executeAsOneOrNull()
@@ -59,86 +41,7 @@ suspend fun DatabaseHandler.awaitFlatMetadataForManga(mangaId: Long): FlatMetada
}
}
fun DatabaseHelper.getFlatMetadataForManga(mangaId: Long): PreparedOperation<FlatMetadata?> {
// We have to use fromCallable because StorIO messes up the thread scheduling if we use their rx functions
val single = Single.fromCallable {
val meta = getSearchMetadataForManga(mangaId).executeAsBlocking()
if (meta != null) {
val tags = getSearchTagsForManga(mangaId).executeAsBlocking()
val titles = getSearchTitlesForManga(mangaId).executeAsBlocking()
FlatMetadata(meta, tags, titles)
} else null
}
return preparedOperationFromSingle(single)
}
private fun <T> preparedOperationFromSingle(single: Single<T>): PreparedOperation<T> {
return object : PreparedOperation<T> {
/**
* Creates [rx.Observable] that emits result of Operation.
*
*
* Observable may be "Hot" or "Cold", please read documentation of the concrete implementation.
*
* @return observable result of operation with only one [rx.Observer.onNext] call.
*/
override fun createObservable() = single.toObservable()
/**
* Executes operation synchronously in current thread.
*
*
* Notice: Blocking I/O operation should not be executed on the Main Thread,
* it can cause ANR (Activity Not Responding dialog), block the UI and drop animations frames.
* So please, execute blocking I/O operation only from background thread.
* See [androidx.annotation.WorkerThread].
*
* @return nullable result of operation.
*/
override fun executeAsBlocking() = single.toBlocking().value()
/**
* Creates [rx.Observable] that emits result of Operation.
*
*
* Observable may be "Hot" (usually "Warm") or "Cold", please read documentation of the concrete implementation.
*
* @return observable result of operation with only one [rx.Observer.onNext] call.
*/
override fun asRxObservable() = single.toObservable()
/**
* Creates [rx.Single] that emits result of Operation lazily when somebody subscribes to it.
*
*
*
* @return single result of operation.
*/
override fun asRxSingle() = single
}
}
fun DatabaseHandler.insertFlatMetadata(flatMetadata: FlatMetadata) {
require(flatMetadata.metadata.mangaId != -1L)
this as AndroidDatabaseHandler // todo remove when legacy backup is dead
db.transaction {
flatMetadata.metadata.let {
db.search_metadataQueries.upsert(it.mangaId, it.uploader, it.extra, it.indexedExtra, it.extraVersion)
}
db.search_tagsQueries.deleteByManga(flatMetadata.metadata.mangaId)
flatMetadata.tags.forEach {
db.search_tagsQueries.insert(it.mangaId, it.namespace, it.name, it.type)
}
db.search_titlesQueries.deleteByManga(flatMetadata.metadata.mangaId)
flatMetadata.titles.forEach {
db.search_titlesQueries.insert(it.mangaId, it.title, it.type)
}
}
}
@Deprecated("Replace with InsertFlatMetadata")
suspend fun DatabaseHandler.awaitInsertFlatMetadata(flatMetadata: FlatMetadata) {
require(flatMetadata.metadata.mangaId != -1L)
@@ -156,29 +59,3 @@ suspend fun DatabaseHandler.awaitInsertFlatMetadata(flatMetadata: FlatMetadata)
}
}
}
fun DatabaseHelper.insertFlatMetadata(flatMetadata: FlatMetadata) {
require(flatMetadata.metadata.mangaId != -1L)
inTransaction {
insertSearchMetadata(flatMetadata.metadata).executeAsBlocking()
setSearchTagsForManga(flatMetadata.metadata.mangaId, flatMetadata.tags)
setSearchTitlesForManga(flatMetadata.metadata.mangaId, flatMetadata.titles)
}
}
fun DatabaseHelper.insertFlatMetadataCompletable(flatMetadata: FlatMetadata): Completable = Completable.fromCallable {
insertFlatMetadata(flatMetadata)
}
suspend fun DatabaseHelper.insertFlatMetadataAsync(flatMetadata: FlatMetadata): Deferred<Unit> = coroutineScope {
async {
require(flatMetadata.metadata.mangaId != -1L)
inTransaction {
insertSearchMetadata(flatMetadata.metadata).executeAsBlocking()
setSearchTagsForManga(flatMetadata.metadata.mangaId, flatMetadata.tags)
setSearchTitlesForManga(flatMetadata.metadata.mangaId, flatMetadata.titles)
}
}
}
@@ -1,66 +0,0 @@
package exh.metadata.sql.mappers
import android.database.Cursor
import androidx.core.content.contentValuesOf
import com.pushtorefresh.storio.sqlite.SQLiteTypeMapping
import com.pushtorefresh.storio.sqlite.operations.delete.DefaultDeleteResolver
import com.pushtorefresh.storio.sqlite.operations.get.DefaultGetResolver
import com.pushtorefresh.storio.sqlite.operations.put.DefaultPutResolver
import com.pushtorefresh.storio.sqlite.queries.DeleteQuery
import com.pushtorefresh.storio.sqlite.queries.InsertQuery
import com.pushtorefresh.storio.sqlite.queries.UpdateQuery
import exh.metadata.sql.models.SearchMetadata
import exh.metadata.sql.tables.SearchMetadataTable.COL_EXTRA
import exh.metadata.sql.tables.SearchMetadataTable.COL_EXTRA_VERSION
import exh.metadata.sql.tables.SearchMetadataTable.COL_INDEXED_EXTRA
import exh.metadata.sql.tables.SearchMetadataTable.COL_MANGA_ID
import exh.metadata.sql.tables.SearchMetadataTable.COL_UPLOADER
import exh.metadata.sql.tables.SearchMetadataTable.TABLE
class SearchMetadataTypeMapping : SQLiteTypeMapping<SearchMetadata>(
SearchMetadataPutResolver(),
SearchMetadataGetResolver(),
SearchMetadataDeleteResolver(),
)
class SearchMetadataPutResolver : DefaultPutResolver<SearchMetadata>() {
override fun mapToInsertQuery(obj: SearchMetadata) = InsertQuery.builder()
.table(TABLE)
.build()
override fun mapToUpdateQuery(obj: SearchMetadata) = UpdateQuery.builder()
.table(TABLE)
.where("$COL_MANGA_ID = ?")
.whereArgs(obj.mangaId)
.build()
override fun mapToContentValues(obj: SearchMetadata) = contentValuesOf(
COL_MANGA_ID to obj.mangaId,
COL_UPLOADER to obj.uploader,
COL_EXTRA to obj.extra,
COL_INDEXED_EXTRA to obj.indexedExtra,
COL_EXTRA_VERSION to obj.extraVersion,
)
}
class SearchMetadataGetResolver : DefaultGetResolver<SearchMetadata>() {
override fun mapFromCursor(cursor: Cursor): SearchMetadata =
SearchMetadata(
mangaId = cursor.getLong(cursor.getColumnIndexOrThrow(COL_MANGA_ID)),
uploader = cursor.getString(cursor.getColumnIndexOrThrow(COL_UPLOADER)),
extra = cursor.getString(cursor.getColumnIndexOrThrow(COL_EXTRA)),
indexedExtra = cursor.getString(cursor.getColumnIndexOrThrow(COL_INDEXED_EXTRA)),
extraVersion = cursor.getInt(cursor.getColumnIndexOrThrow(COL_EXTRA_VERSION)),
)
}
class SearchMetadataDeleteResolver : DefaultDeleteResolver<SearchMetadata>() {
override fun mapToDeleteQuery(obj: SearchMetadata) = DeleteQuery.builder()
.table(TABLE)
.where("$COL_MANGA_ID = ?")
.whereArgs(obj.mangaId)
.build()
}
@@ -1,65 +0,0 @@
package exh.metadata.sql.mappers
import android.database.Cursor
import androidx.core.content.contentValuesOf
import com.pushtorefresh.storio.sqlite.SQLiteTypeMapping
import com.pushtorefresh.storio.sqlite.operations.delete.DefaultDeleteResolver
import com.pushtorefresh.storio.sqlite.operations.get.DefaultGetResolver
import com.pushtorefresh.storio.sqlite.operations.put.DefaultPutResolver
import com.pushtorefresh.storio.sqlite.queries.DeleteQuery
import com.pushtorefresh.storio.sqlite.queries.InsertQuery
import com.pushtorefresh.storio.sqlite.queries.UpdateQuery
import exh.metadata.sql.models.SearchTag
import exh.metadata.sql.tables.SearchTagTable.COL_ID
import exh.metadata.sql.tables.SearchTagTable.COL_MANGA_ID
import exh.metadata.sql.tables.SearchTagTable.COL_NAME
import exh.metadata.sql.tables.SearchTagTable.COL_NAMESPACE
import exh.metadata.sql.tables.SearchTagTable.COL_TYPE
import exh.metadata.sql.tables.SearchTagTable.TABLE
class SearchTagTypeMapping : SQLiteTypeMapping<SearchTag>(
SearchTagPutResolver(),
SearchTagGetResolver(),
SearchTagDeleteResolver(),
)
class SearchTagPutResolver : DefaultPutResolver<SearchTag>() {
override fun mapToInsertQuery(obj: SearchTag) = InsertQuery.builder()
.table(TABLE)
.build()
override fun mapToUpdateQuery(obj: SearchTag) = UpdateQuery.builder()
.table(TABLE)
.where("$COL_ID = ?")
.whereArgs(obj.id)
.build()
override fun mapToContentValues(obj: SearchTag) = contentValuesOf(
COL_ID to obj.id,
COL_MANGA_ID to obj.mangaId,
COL_NAMESPACE to obj.namespace,
COL_NAME to obj.name,
COL_TYPE to obj.type,
)
}
class SearchTagGetResolver : DefaultGetResolver<SearchTag>() {
override fun mapFromCursor(cursor: Cursor): SearchTag = SearchTag(
id = cursor.getLong(cursor.getColumnIndexOrThrow(COL_ID)),
mangaId = cursor.getLong(cursor.getColumnIndexOrThrow(COL_MANGA_ID)),
namespace = cursor.getString(cursor.getColumnIndexOrThrow(COL_NAMESPACE)),
name = cursor.getString(cursor.getColumnIndexOrThrow(COL_NAME)),
type = cursor.getInt(cursor.getColumnIndexOrThrow(COL_TYPE)),
)
}
class SearchTagDeleteResolver : DefaultDeleteResolver<SearchTag>() {
override fun mapToDeleteQuery(obj: SearchTag) = DeleteQuery.builder()
.table(TABLE)
.where("$COL_ID = ?")
.whereArgs(obj.id)
.build()
}
@@ -1,62 +0,0 @@
package exh.metadata.sql.mappers
import android.database.Cursor
import androidx.core.content.contentValuesOf
import com.pushtorefresh.storio.sqlite.SQLiteTypeMapping
import com.pushtorefresh.storio.sqlite.operations.delete.DefaultDeleteResolver
import com.pushtorefresh.storio.sqlite.operations.get.DefaultGetResolver
import com.pushtorefresh.storio.sqlite.operations.put.DefaultPutResolver
import com.pushtorefresh.storio.sqlite.queries.DeleteQuery
import com.pushtorefresh.storio.sqlite.queries.InsertQuery
import com.pushtorefresh.storio.sqlite.queries.UpdateQuery
import exh.metadata.sql.models.SearchTitle
import exh.metadata.sql.tables.SearchTitleTable.COL_ID
import exh.metadata.sql.tables.SearchTitleTable.COL_MANGA_ID
import exh.metadata.sql.tables.SearchTitleTable.COL_TITLE
import exh.metadata.sql.tables.SearchTitleTable.COL_TYPE
import exh.metadata.sql.tables.SearchTitleTable.TABLE
class SearchTitleTypeMapping : SQLiteTypeMapping<SearchTitle>(
SearchTitlePutResolver(),
SearchTitleGetResolver(),
SearchTitleDeleteResolver(),
)
class SearchTitlePutResolver : DefaultPutResolver<SearchTitle>() {
override fun mapToInsertQuery(obj: SearchTitle) = InsertQuery.builder()
.table(TABLE)
.build()
override fun mapToUpdateQuery(obj: SearchTitle) = UpdateQuery.builder()
.table(TABLE)
.where("$COL_ID = ?")
.whereArgs(obj.id)
.build()
override fun mapToContentValues(obj: SearchTitle) = contentValuesOf(
COL_ID to obj.id,
COL_MANGA_ID to obj.mangaId,
COL_TITLE to obj.title,
COL_TYPE to obj.type,
)
}
class SearchTitleGetResolver : DefaultGetResolver<SearchTitle>() {
override fun mapFromCursor(cursor: Cursor): SearchTitle = SearchTitle(
id = cursor.getLong(cursor.getColumnIndexOrThrow(COL_ID)),
mangaId = cursor.getLong(cursor.getColumnIndexOrThrow(COL_MANGA_ID)),
title = cursor.getString(cursor.getColumnIndexOrThrow(COL_TITLE)),
type = cursor.getInt(cursor.getColumnIndexOrThrow(COL_TYPE)),
)
}
class SearchTitleDeleteResolver : DefaultDeleteResolver<SearchTitle>() {
override fun mapToDeleteQuery(obj: SearchTitle) = DeleteQuery.builder()
.table(TABLE)
.where("$COL_ID = ?")
.whereArgs(obj.id)
.build()
}
@@ -1,52 +0,0 @@
package exh.metadata.sql.queries
import com.pushtorefresh.storio.sqlite.queries.DeleteQuery
import com.pushtorefresh.storio.sqlite.queries.Query
import eu.kanade.tachiyomi.data.database.DbProvider
import exh.metadata.sql.models.SearchMetadata
import exh.metadata.sql.tables.SearchMetadataTable
interface SearchMetadataQueries : DbProvider {
fun getSearchMetadataForManga(mangaId: Long) = db.get()
.`object`(SearchMetadata::class.java)
.withQuery(
Query.builder()
.table(SearchMetadataTable.TABLE)
.where("${SearchMetadataTable.COL_MANGA_ID} = ?")
.whereArgs(mangaId)
.build(),
)
.prepare()
fun getSearchMetadata() = db.get()
.listOfObjects(SearchMetadata::class.java)
.withQuery(
Query.builder()
.table(SearchMetadataTable.TABLE)
.build(),
)
.prepare()
fun getSearchMetadataByIndexedExtra(extra: String) = db.get()
.listOfObjects(SearchMetadata::class.java)
.withQuery(
Query.builder()
.table(SearchMetadataTable.TABLE)
.where("${SearchMetadataTable.COL_INDEXED_EXTRA} = ?")
.whereArgs(extra)
.build(),
)
.prepare()
fun insertSearchMetadata(metadata: SearchMetadata) = db.put().`object`(metadata).prepare()
fun deleteSearchMetadata(metadata: SearchMetadata) = db.delete().`object`(metadata).prepare()
fun deleteAllSearchMetadata() = db.delete().byQuery(
DeleteQuery.builder()
.table(SearchMetadataTable.TABLE)
.build(),
)
.prepare()
}
@@ -1,53 +0,0 @@
package exh.metadata.sql.queries
import com.pushtorefresh.storio.sqlite.queries.DeleteQuery
import com.pushtorefresh.storio.sqlite.queries.Query
import eu.kanade.tachiyomi.data.database.DbProvider
import eu.kanade.tachiyomi.data.database.inTransaction
import exh.metadata.sql.models.SearchTag
import exh.metadata.sql.tables.SearchTagTable
interface SearchTagQueries : DbProvider {
fun getSearchTagsForManga(mangaId: Long) = db.get()
.listOfObjects(SearchTag::class.java)
.withQuery(
Query.builder()
.table(SearchTagTable.TABLE)
.where("${SearchTagTable.COL_MANGA_ID} = ?")
.whereArgs(mangaId)
.build(),
)
.prepare()
fun deleteSearchTagsForManga(mangaId: Long) = db.delete()
.byQuery(
DeleteQuery.builder()
.table(SearchTagTable.TABLE)
.where("${SearchTagTable.COL_MANGA_ID} = ?")
.whereArgs(mangaId)
.build(),
)
.prepare()
fun insertSearchTag(searchTag: SearchTag) = db.put().`object`(searchTag).prepare()
fun insertSearchTags(searchTags: List<SearchTag>) = db.put().objects(searchTags).prepare()
fun deleteSearchTag(searchTag: SearchTag) = db.delete().`object`(searchTag).prepare()
fun deleteAllSearchTags() = db.delete().byQuery(
DeleteQuery.builder()
.table(SearchTagTable.TABLE)
.build(),
)
.prepare()
fun setSearchTagsForManga(mangaId: Long, tags: List<SearchTag>) {
db.inTransaction {
deleteSearchTagsForManga(mangaId).executeAsBlocking()
tags.chunked(100) { chunk ->
insertSearchTags(chunk).executeAsBlocking()
}
}
}
}
@@ -1,53 +0,0 @@
package exh.metadata.sql.queries
import com.pushtorefresh.storio.sqlite.queries.DeleteQuery
import com.pushtorefresh.storio.sqlite.queries.Query
import eu.kanade.tachiyomi.data.database.DbProvider
import eu.kanade.tachiyomi.data.database.inTransaction
import exh.metadata.sql.models.SearchTitle
import exh.metadata.sql.tables.SearchTitleTable
interface SearchTitleQueries : DbProvider {
fun getSearchTitlesForManga(mangaId: Long) = db.get()
.listOfObjects(SearchTitle::class.java)
.withQuery(
Query.builder()
.table(SearchTitleTable.TABLE)
.where("${SearchTitleTable.COL_MANGA_ID} = ?")
.whereArgs(mangaId)
.build(),
)
.prepare()
fun deleteSearchTitlesForManga(mangaId: Long) = db.delete()
.byQuery(
DeleteQuery.builder()
.table(SearchTitleTable.TABLE)
.where("${SearchTitleTable.COL_MANGA_ID} = ?")
.whereArgs(mangaId)
.build(),
)
.prepare()
fun insertSearchTitle(searchTitle: SearchTitle) = db.put().`object`(searchTitle).prepare()
fun insertSearchTitles(searchTitles: List<SearchTitle>) = db.put().objects(searchTitles).prepare()
fun deleteSearchTitle(searchTitle: SearchTitle) = db.delete().`object`(searchTitle).prepare()
fun deleteAllSearchTitle() = db.delete().byQuery(
DeleteQuery.builder()
.table(SearchTitleTable.TABLE)
.build(),
)
.prepare()
fun setSearchTitlesForManga(mangaId: Long, titles: List<SearchTitle>) {
db.inTransaction {
deleteSearchTitlesForManga(mangaId).executeAsBlocking()
titles.chunked(100) { chunk ->
insertSearchTitles(chunk).executeAsBlocking()
}
}
}
}
@@ -1,15 +0,0 @@
package exh.metadata.sql.tables
object SearchMetadataTable {
const val TABLE = "search_metadata"
const val COL_MANGA_ID = "manga_id"
const val COL_UPLOADER = "uploader"
const val COL_EXTRA = "extra"
const val COL_INDEXED_EXTRA = "indexed_extra"
const val COL_EXTRA_VERSION = "extra_version"
}
@@ -1,15 +0,0 @@
package exh.metadata.sql.tables
object SearchTagTable {
const val TABLE = "search_tags"
const val COL_ID = "_id"
const val COL_MANGA_ID = "manga_id"
const val COL_NAMESPACE = "namespace"
const val COL_NAME = "name"
const val COL_TYPE = "type"
}
@@ -1,13 +0,0 @@
package exh.metadata.sql.tables
object SearchTitleTable {
const val TABLE = "search_titles"
const val COL_ID = "_id"
const val COL_MANGA_ID = "manga_id"
const val COL_TITLE = "title"
const val COL_TYPE = "type"
}
@@ -1,6 +1,6 @@
package exh.recs
import eu.kanade.domain.manga.interactor.GetMangaById
import eu.kanade.domain.manga.interactor.GetManga
import eu.kanade.domain.manga.model.Manga
import eu.kanade.tachiyomi.source.model.FilterList
import eu.kanade.tachiyomi.ui.browse.source.browse.BrowseSourcePresenter
@@ -15,13 +15,13 @@ import uy.kohesive.injekt.api.get
class RecommendsPresenter(
val mangaId: Long,
sourceId: Long,
private val getMangaById: GetMangaById = Injekt.get(),
private val getManga: GetManga = Injekt.get(),
) : BrowseSourcePresenter(sourceId) {
var manga: Manga? = null
override fun createPager(query: String, filters: FilterList): Pager {
this.manga = runBlocking { getMangaById.await(mangaId) }
this.manga = runBlocking { getManga.await(mangaId) }
return RecommendsPager(manga!!)
}
}
+13 -16
View File
@@ -1,8 +1,5 @@
package exh.search
import exh.metadata.sql.tables.SearchMetadataTable
import exh.metadata.sql.tables.SearchTagTable
import exh.metadata.sql.tables.SearchTitleTable
import java.util.Locale
class SearchEngine {
@@ -23,16 +20,16 @@ class SearchEngine {
val params = mutableListOf<String>()
it.joinToString(separator = " OR ", prefix = "(", postfix = ")") { q ->
params += q
"${SearchTagTable.TABLE}.${SearchTagTable.COL_NAME} LIKE ?"
"search_tags.name LIKE ?"
} to params
}
return when {
namespace != null -> {
var query =
"""
(SELECT ${SearchTagTable.COL_MANGA_ID} AS $COL_MANGA_ID FROM ${SearchTagTable.TABLE}
WHERE ${SearchTagTable.COL_NAMESPACE} IS NOT NULL
AND ${SearchTagTable.COL_NAMESPACE} LIKE ?
(SELECT ${"manga_id"} AS $COL_MANGA_ID FROM ${"search_tags"}
WHERE ${"namespace"} IS NOT NULL
AND ${"namespace"} LIKE ?
""".trimIndent()
val params = mutableListOf(escapeLike(namespace))
if (componentTagQuery != null) {
@@ -46,14 +43,14 @@ class SearchEngine {
// Match title + tags
val tagQuery =
"""
SELECT ${SearchTagTable.COL_MANGA_ID} AS $COL_MANGA_ID FROM ${SearchTagTable.TABLE}
SELECT ${"manga_id"} AS $COL_MANGA_ID FROM ${"search_tags"}
WHERE ${componentTagQuery!!.first}
""".trimIndent() to componentTagQuery.second
val titleQuery =
"""
SELECT ${SearchTitleTable.COL_MANGA_ID} AS $COL_MANGA_ID FROM ${SearchTitleTable.TABLE}
WHERE ${SearchTitleTable.COL_TITLE} LIKE ?
SELECT ${"manga_id"} AS $COL_MANGA_ID FROM ${"search_titles"}
WHERE ${"title"} LIKE ?
""".trimIndent() to listOf(component.asLenientTitleQuery())
"(${tagQuery.first} UNION ${titleQuery.first})".trimIndent() to
@@ -75,7 +72,7 @@ class SearchEngine {
textToSubQueries(null, component)
} else if (component is Namespace) {
if (component.namespace == "uploader") {
wheres += "meta.${SearchMetadataTable.COL_UPLOADER} LIKE ?"
wheres += "meta.uploader LIKE ?"
whereParams += component.tag!!.rawTextEscapedForLike()
null
} else {
@@ -97,15 +94,15 @@ class SearchEngine {
val completeParams = mutableListOf<String>()
var baseQuery =
"""
SELECT ${SearchMetadataTable.COL_MANGA_ID}
FROM ${SearchMetadataTable.TABLE} meta
SELECT ${"manga_id"}
FROM ${"search_metadata"} meta
""".trimIndent()
include.forEachIndexed { index, pair ->
baseQuery += "\n" + (
"""
INNER JOIN ${pair.first} i$index
ON i$index.$COL_MANGA_ID = meta.${SearchMetadataTable.COL_MANGA_ID}
ON i$index.$COL_MANGA_ID = meta.${"manga_id"}
""".trimIndent()
)
completeParams += pair.second
@@ -113,7 +110,7 @@ class SearchEngine {
exclude.forEach {
wheres += """
(meta.${SearchMetadataTable.COL_MANGA_ID} NOT IN ${it.first})
(meta.${"manga_id"} NOT IN ${it.first})
""".trimIndent()
whereParams += it.second
}
@@ -122,7 +119,7 @@ class SearchEngine {
baseQuery += "\nWHERE\n"
baseQuery += wheres.joinToString("\nAND\n")
}
baseQuery += "\nORDER BY ${SearchMetadataTable.COL_MANGA_ID}"
baseQuery += "\nORDER BY manga_id"
return baseQuery to completeParams
}
@@ -1,5 +1,6 @@
package exh.smartsearch
import eu.kanade.data.DatabaseHandler
import eu.kanade.tachiyomi.data.database.DatabaseHelper
import eu.kanade.tachiyomi.data.database.models.Manga
import eu.kanade.tachiyomi.source.CatalogueSource
@@ -18,6 +19,7 @@ class SmartSearchEngine(
private val extraSearchParams: String? = null,
) {
private val db: DatabaseHelper by injectLazy()
private val handler: DatabaseHandler by injectLazy()
private val normalizedLevenshtein = NormalizedLevenshtein()
@@ -6,7 +6,7 @@ import android.view.View
import androidx.core.os.bundleOf
import androidx.recyclerview.widget.LinearLayoutManager
import dev.chrisbanes.insetter.applyInsetter
import eu.kanade.domain.manga.interactor.GetMangaById
import eu.kanade.domain.manga.interactor.GetManga
import eu.kanade.domain.manga.model.Manga
import eu.kanade.tachiyomi.databinding.MetadataViewControllerBinding
import eu.kanade.tachiyomi.source.Source
@@ -29,7 +29,7 @@ class MetadataViewController : NucleusController<MetadataViewControllerBinding,
}
constructor(mangaId: Long) : this(
runBlocking { Injekt.get<GetMangaById>().await(mangaId)!! },
runBlocking { Injekt.get<GetManga>().await(mangaId)!! },
)
@Suppress("unused")