Use SQLDelight for all Manga related queries (#7447)

(cherry picked from commit 17951cfd68)

# Conflicts:
#	app/src/main/java/eu/kanade/domain/DomainModule.kt
#	app/src/main/java/eu/kanade/domain/manga/repository/MangaRepository.kt
#	app/src/main/java/eu/kanade/tachiyomi/data/database/DatabaseHelper.kt
#	app/src/main/java/eu/kanade/tachiyomi/data/database/DbExtensions.kt
#	app/src/main/java/eu/kanade/tachiyomi/data/database/DbProvider.kt
#	app/src/main/java/eu/kanade/tachiyomi/data/database/mappers/ChapterTypeMapping.kt
#	app/src/main/java/eu/kanade/tachiyomi/data/database/mappers/MangaCategoryTypeMapping.kt
#	app/src/main/java/eu/kanade/tachiyomi/data/database/mappers/MangaTypeMapping.kt
#	app/src/main/java/eu/kanade/tachiyomi/data/database/queries/MangaQueries.kt
#	app/src/main/java/eu/kanade/tachiyomi/data/database/queries/RawQueries.kt
#	app/src/main/java/eu/kanade/tachiyomi/data/database/resolvers/ChapterProgressPutResolver.kt
#	app/src/main/java/eu/kanade/tachiyomi/data/database/resolvers/LibraryMangaGetResolver.kt
#	app/src/main/java/eu/kanade/tachiyomi/data/database/resolvers/MangaFlagsPutResolver.kt
#	app/src/main/java/eu/kanade/tachiyomi/data/database/tables/CategoryTable.kt
#	app/src/main/java/eu/kanade/tachiyomi/data/database/tables/ChapterTable.kt
#	app/src/main/java/eu/kanade/tachiyomi/data/database/tables/MangaCategoryTable.kt
#	app/src/main/java/eu/kanade/tachiyomi/data/database/tables/MangaTable.kt
#	app/src/main/java/eu/kanade/tachiyomi/data/library/LibraryUpdateService.kt
#	app/src/main/java/eu/kanade/tachiyomi/ui/browse/migration/search/SearchController.kt
#	app/src/main/java/eu/kanade/tachiyomi/ui/browse/source/browse/BrowseSourcePresenter.kt
#	app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryPresenter.kt
#	app/src/main/java/eu/kanade/tachiyomi/ui/reader/ReaderPresenter.kt
This commit is contained in:
Andreas
2022-07-03 16:17:41 +02:00
committed by Jobobby04
parent 0c89c4cd64
commit ea38ffc53e
90 changed files with 878 additions and 2368 deletions
+68 -95
View File
@@ -2,21 +2,21 @@
package exh
import android.content.Context
import androidx.core.content.edit
import androidx.preference.PreferenceManager
import com.pushtorefresh.storio.sqlite.queries.Query
import eu.kanade.data.DatabaseHandler
import eu.kanade.data.chapter.chapterMapper
import eu.kanade.domain.chapter.interactor.DeleteChapters
import eu.kanade.domain.chapter.interactor.UpdateChapter
import eu.kanade.domain.chapter.model.ChapterUpdate
import eu.kanade.domain.manga.interactor.GetManga
import eu.kanade.domain.manga.interactor.GetMangaBySource
import eu.kanade.domain.manga.interactor.InsertMergedReference
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.tables.ChapterTable
import eu.kanade.tachiyomi.data.library.LibraryUpdateJob
import eu.kanade.tachiyomi.data.preference.MANGA_NON_COMPLETED
import eu.kanade.tachiyomi.data.preference.PreferenceKeys
@@ -39,7 +39,6 @@ import eu.kanade.tachiyomi.util.preference.minusAssign
import eu.kanade.tachiyomi.util.system.DeviceUtil
import exh.eh.EHentaiUpdateWorker
import exh.log.xLogE
import exh.log.xLogW
import exh.merged.sql.models.MergedMangaReference
import exh.source.BlacklistedSources
import exh.source.EH_SOURCE_ID
@@ -69,12 +68,14 @@ import java.net.URISyntaxException
import eu.kanade.domain.manga.model.Manga as DomainManga
object EXHMigrations {
private val db: DatabaseHelper 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()
private val updateChapter: UpdateChapter by injectLazy()
private val deleteChapters: DeleteChapters by injectLazy()
private val insertMergedReference: InsertMergedReference by injectLazy()
/**
* Performs a migration when the application is updated.
@@ -125,89 +126,73 @@ object EXHMigrations {
updateSourceId(NHentai.otherId, 6907)
}
if (oldVersion under 7) {
db.inTransaction {
val mergedMangas = runBlocking { getMangaBySource.await(MERGED_SOURCE_ID) }
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<MangaUpdate>()
val mergedMangaReferences = mutableListOf<MergedMangaReference>()
mangaConfigs.onEach { mergedManga ->
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
if (mergedMangas.isNotEmpty()) {
val mangaConfigs = mergedMangas.mapNotNull { mergedManga -> readMangaConfig(mergedManga)?.let { mergedManga to it } }
if (mangaConfigs.isNotEmpty()) {
val mangaToUpdate = mutableListOf<MangaUpdate>()
val mergedMangaReferences = mutableListOf<MergedMangaReference>()
mangaConfigs.onEach { mergedManga ->
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,
getChapterUpdates = false,
chapterSortMode = 0,
chapterPriority = 0,
downloadChapters = false,
mergeId = newFirst.id,
mergeUrl = newFirst.url,
mangaId = newFirst.id,
mangaUrl = newFirst.url,
mangaSourceId = MERGED_SOURCE_ID,
)
mergedManga.second.children.distinct().forEachIndexed { index, mangaSource ->
val load = mangaSource.load() ?: return@forEachIndexed
mergedMangaReferences += MergedMangaReference(
id = null,
isInfoManga = false,
getChapterUpdates = false,
isInfoManga = index == 0,
getChapterUpdates = true,
chapterSortMode = 0,
chapterPriority = 0,
downloadChapters = false,
mergeId = mergedManga.first.id,
mergeUrl = mergedManga.first.url,
mangaId = mergedManga.first.id,
mangaUrl = mergedManga.first.url,
mangaSourceId = MERGED_SOURCE_ID,
downloadChapters = true,
mergeId = newFirst.id,
mergeUrl = newFirst.url,
mangaId = load.manga.id,
mangaUrl = load.manga.url,
mangaSourceId = load.source.id,
)
mergedManga.second.children.distinct().forEachIndexed { index, mangaSource ->
val load = mangaSource.load(db, sourceManager) ?: return@forEachIndexed
mergedMangaReferences += MergedMangaReference(
id = null,
isInfoManga = index == 0,
getChapterUpdates = true,
chapterSortMode = 0,
chapterPriority = 0,
downloadChapters = true,
mergeId = mergedManga.first.id,
mergeUrl = mergedManga.first.url,
mangaId = load.manga.id!!,
mangaUrl = load.manga.url,
mangaSourceId = load.source.id,
)
}
}
runBlocking {
updateManga.awaitAll(mangaToUpdate)
}
db.insertMergedMangas(mergedMangaReferences).executeAsBlocking()
}
runBlocking {
updateManga.awaitAll(mangaToUpdate)
insertMergedReference.awaitAll(mergedMangaReferences)
}
val loadedMangaList = mangaConfigs.map { it.second.children }.flatten().mapNotNull { it.load(db, sourceManager) }.distinct()
val chapters = db.db.get()
.listOfObjects(Chapter::class.java)
.withQuery(
Query.builder()
.table(ChapterTable.TABLE)
.where("${ChapterTable.COL_MANGA_ID} IN (${mergedMangas.joinToString { it.id.toString() }})")
.build(),
val loadedMangaList = mangaConfigs.map { it.second.children }.flatten().mapNotNull { it.load() }.distinct()
val chapters = runBlocking { handler.awaitList { ehQueries.getChaptersByMangaIds(mergedMangas.map { it.id }, chapterMapper) } }
val mergedMangaChapters = runBlocking { handler.awaitList { ehQueries.getChaptersByMangaIds(loadedMangaList.map { it.manga.id }, chapterMapper) } }
val mergedMangaChaptersMatched = mergedMangaChapters.mapNotNull { chapter -> loadedMangaList.firstOrNull { it.manga.id == chapter.id }?.let { it to chapter } }
val parsedChapters = chapters.filter { it.read || it.lastPageRead != 0L }.mapNotNull { chapter -> readUrlConfig(chapter.url)?.let { chapter to it } }
val chaptersToUpdate = mutableListOf<ChapterUpdate>()
parsedChapters.forEach { parsedChapter ->
mergedMangaChaptersMatched.firstOrNull { it.second.url == parsedChapter.second.url && it.first.source.id == parsedChapter.second.source && it.first.manga.url == parsedChapter.second.mangaUrl }?.let {
chaptersToUpdate += ChapterUpdate(
it.second.id,
read = parsedChapter.first.read,
lastPageRead = parsedChapter.first.lastPageRead,
)
.prepare()
.executeAsBlocking()
val mergedMangaChapters = db.db.get()
.listOfObjects(Chapter::class.java)
.withQuery(
Query.builder()
.table(ChapterTable.TABLE)
.where("${ChapterTable.COL_MANGA_ID} IN (${loadedMangaList.filter { it.manga.id != null }.joinToString { it.manga.id.toString() }})")
.build(),
)
.prepare()
.executeAsBlocking()
val mergedMangaChaptersMatched = mergedMangaChapters.mapNotNull { chapter -> loadedMangaList.firstOrNull { it.manga.id == chapter.id }?.let { it to chapter } }
val parsedChapters = chapters.filter { it.read || it.last_page_read != 0 }.mapNotNull { chapter -> readUrlConfig(chapter.url)?.let { chapter to it } }
val chaptersToUpdate = mutableListOf<Chapter>()
parsedChapters.forEach { parsedChapter ->
mergedMangaChaptersMatched.firstOrNull { it.second.url == parsedChapter.second.url && it.first.source.id == parsedChapter.second.source && it.first.manga.url == parsedChapter.second.mangaUrl }?.let {
chaptersToUpdate += it.second.apply {
read = parsedChapter.first.read
last_page_read = parsedChapter.first.last_page_read
}
}
}
db.deleteChapters(mergedMangaChapters).executeAsBlocking()
db.updateChaptersProgress(chaptersToUpdate).executeAsBlocking()
}
runBlocking {
deleteChapters.await(mergedMangaChapters.map { it.id })
updateChapter.awaitAll(chaptersToUpdate)
}
}
}
@@ -471,18 +456,6 @@ object EXHMigrations {
}
}
private fun backupDatabase(context: Context, oldMigrationVersion: Int) {
val backupLocation = File(File(context.filesDir, "exh_db_bck"), "$oldMigrationVersion.bck.db")
if (backupLocation.exists()) return // Do not backup same version twice
val dbLocation = context.getDatabasePath(db.lowLevel().sqliteOpenHelper().databaseName)
try {
dbLocation.copyTo(backupLocation, overwrite = true)
} catch (t: Throwable) {
xLogW("Failed to backup database!")
}
}
private fun getUrlWithoutDomain(orig: String): String {
return try {
val uri = URI(orig)
@@ -536,8 +509,8 @@ object EXHMigrations {
@SerialName("u")
val url: String,
) {
fun load(db: DatabaseHelper, sourceManager: SourceManager): LoadedMangaSource? {
val manga = db.getManga(url, source).executeAsBlocking() ?: return null
fun load(): LoadedMangaSource? {
val manga = runBlocking { getManga.await(url, source) } ?: return null
val source = sourceManager.getOrStub(source)
return LoadedMangaSource(source, manga)
}
@@ -551,7 +524,7 @@ object EXHMigrations {
}
}
private data class LoadedMangaSource(val source: Source, val manga: Manga)
private data class LoadedMangaSource(val source: Source, val manga: DomainManga)
private fun updateSourceId(newId: Long, oldId: Long) {
runBlocking {
+8 -67
View File
@@ -2,7 +2,6 @@ package exh.debug
import android.app.Application
import androidx.work.WorkManager
import eu.kanade.data.AndroidDatabaseHandler
import eu.kanade.data.DatabaseHandler
import eu.kanade.domain.manga.interactor.GetAllManga
import eu.kanade.domain.manga.interactor.GetExhFavoriteMangaWithMetadata
@@ -12,7 +11,6 @@ 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.tables.MangaTable
import eu.kanade.tachiyomi.data.preference.PreferencesHelper
import eu.kanade.tachiyomi.source.SourceManager
import eu.kanade.tachiyomi.source.online.all.NHentai
@@ -110,16 +108,7 @@ object DebugFunctions {
}
fun addAllMangaInDatabaseToLibrary() {
(handler as AndroidDatabaseHandler).rawQuery {
it.execute(
null,
"""
UPDATE ${MangaTable.TABLE}
SET ${MangaTable.COL_FAVORITE} = 1
""".trimIndent(),
0,
)
}
runBlocking { handler.await { ehQueries.addAllMangaInDatabaseToLibrary() } }
}
fun countMangaInDatabaseInLibrary() = runBlocking { getFavorites.await().size }
@@ -200,16 +189,8 @@ object DebugFunctions {
fun cancelAllScheduledJobs() = app.jobScheduler.cancelAll()
private fun convertSources(from: Long, to: Long) {
(handler as AndroidDatabaseHandler).rawQuery {
it.execute(
null,
"""
UPDATE ${MangaTable.TABLE}
SET ${MangaTable.COL_SOURCE} = $to
WHERE ${MangaTable.COL_SOURCE} = $from
""".trimIndent(),
0,
)
runBlocking {
handler.await { ehQueries.migrateSource(to, from) }
}
}
@@ -292,60 +273,20 @@ object DebugFunctions {
}*/
fun fixReaderViewerBackupBug() {
(handler as AndroidDatabaseHandler).rawQuery {
it.execute(
null,
"""
UPDATE ${MangaTable.TABLE}
SET ${MangaTable.COL_VIEWER} = 0
WHERE ${MangaTable.COL_VIEWER} = -1
""".trimIndent(),
0,
)
}
runBlocking { handler.await { ehQueries.fixReaderViewerBackupBug() } }
}
fun resetReaderViewerForAllManga() {
(handler as AndroidDatabaseHandler).rawQuery {
it.execute(
null,
"""
UPDATE ${MangaTable.TABLE}
SET ${MangaTable.COL_VIEWER} = 0
""".trimIndent(),
0,
)
}
runBlocking { handler.await { ehQueries.resetReaderViewerForAllManga() } }
}
fun migrateAllNhentaiToOtherLang() {
val sources = nHentaiSourceIds.toMutableList()
.also { it.remove(NHentai.otherId) }
.joinToString(separator = ",")
val sources = nHentaiSourceIds - NHentai.otherId
(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,
)
}
runBlocking { handler.await { ehQueries.migrateAllNhentaiToOtherLang(NHentai.otherId, sources) } }
}
fun resetFilteredScanlatorsForAllManga() {
(handler as AndroidDatabaseHandler).rawQuery {
it.execute(
null,
"""
UPDATE ${MangaTable.TABLE}
SET ${MangaTable.COL_FILTERED_SCANLATORS} = NULL
""".trimIndent(),
0,
)
}
runBlocking { handler.await { ehQueries.resetFilteredScanlatorsForAllManga() } }
}
}
+10 -15
View File
@@ -12,11 +12,13 @@ import androidx.work.WorkerParameters
import com.elvishew.xlog.Logger
import com.elvishew.xlog.XLog
import eu.kanade.data.DatabaseHandler
import eu.kanade.data.manga.mangaMapper
import eu.kanade.domain.chapter.interactor.GetChapterByMangaId
import eu.kanade.domain.chapter.interactor.SyncChaptersWithSource
import eu.kanade.domain.chapter.model.Chapter
import eu.kanade.domain.chapter.model.toDbChapter
import eu.kanade.domain.manga.interactor.GetExhFavoriteMangaWithMetadata
import eu.kanade.domain.manga.interactor.GetFlatMetadataById
import eu.kanade.domain.manga.interactor.InsertFlatMetadata
import eu.kanade.domain.manga.interactor.UpdateManga
import eu.kanade.domain.manga.model.Manga
import eu.kanade.domain.manga.model.toDbManga
@@ -33,11 +35,6 @@ import exh.debug.DebugToggles
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.awaitInsertFlatMetadata
import exh.source.EH_SOURCE_ID
import exh.source.EXH_SOURCE_ID
import exh.source.isEhBasedManga
import exh.util.cancellable
import kotlinx.coroutines.flow.asFlow
import kotlinx.coroutines.flow.mapNotNull
@@ -53,7 +50,6 @@ import kotlin.time.Duration.Companion.days
class EHentaiUpdateWorker(private val context: Context, workerParams: WorkerParameters) :
CoroutineWorker(context, workerParams) {
private val handler: DatabaseHandler by injectLazy()
private val prefs: PreferencesHelper by injectLazy()
private val sourceManager: SourceManager by injectLazy()
private val updateHelper: EHentaiUpdateHelper by injectLazy()
@@ -61,6 +57,9 @@ class EHentaiUpdateWorker(private val context: Context, workerParams: WorkerPara
private val updateManga: UpdateManga by injectLazy()
private val syncChaptersWithSource: SyncChaptersWithSource by injectLazy()
private val getChapterByMangaId: GetChapterByMangaId by injectLazy()
private val getFlatMetadataById: GetFlatMetadataById by injectLazy()
private val insertFlatMetadata: InsertFlatMetadata by injectLazy()
private val getExhFavoriteMangaWithMetadata: GetExhFavoriteMangaWithMetadata by injectLazy()
private val updateNotifier by lazy { LibraryUpdateNotifier(context) }
@@ -84,16 +83,12 @@ class EHentaiUpdateWorker(private val context: Context, workerParams: WorkerPara
val startTime = System.currentTimeMillis()
logger.d("Finding manga with metadata...")
val metadataManga = handler.awaitList { mangasQueries.getEhMangaWithMetadata(EH_SOURCE_ID, EXH_SOURCE_ID, mangaMapper) }
val metadataManga = getExhFavoriteMangaWithMetadata.await()
logger.d("Filtering manga and raising metadata...")
val curTime = System.currentTimeMillis()
val allMeta = metadataManga.asFlow().cancellable().mapNotNull { manga ->
if (!manga.isEhBasedManga()) {
return@mapNotNull null
}
val meta = handler.awaitFlatMetadataForManga(manga.id)
val meta = getFlatMetadataById.await(manga.id)
?: return@mapNotNull null
val raisedMeta = meta.raise<EHentaiSearchMetadata>()
@@ -221,12 +216,12 @@ class EHentaiUpdateWorker(private val context: Context, workerParams: WorkerPara
return new to getChapterByMangaId.await(manga.id)
} catch (t: Throwable) {
if (t is EHentai.GalleryNotFoundException) {
val meta = handler.awaitFlatMetadataForManga(manga.id)?.raise<EHentaiSearchMetadata>()
val meta = getFlatMetadataById.await(manga.id)?.raise<EHentaiSearchMetadata>()
if (meta != null) {
// Age dead galleries
logger.d("Aged %s - notfound", manga.id)
meta.aged = true
handler.awaitInsertFlatMetadata(meta.flatten())
insertFlatMetadata.await(meta)
}
throw GalleryNotUpdatedException(false, t)
}
@@ -1,84 +0,0 @@
package exh.merged.sql.mappers
import android.database.Cursor
import androidx.core.content.contentValuesOf
import androidx.core.database.getLongOrNull
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.merged.sql.models.MergedMangaReference
import exh.merged.sql.tables.MergedTable.COL_CHAPTER_PRIORITY
import exh.merged.sql.tables.MergedTable.COL_CHAPTER_SORT_MODE
import exh.merged.sql.tables.MergedTable.COL_DOWNLOAD_CHAPTERS
import exh.merged.sql.tables.MergedTable.COL_GET_CHAPTER_UPDATES
import exh.merged.sql.tables.MergedTable.COL_ID
import exh.merged.sql.tables.MergedTable.COL_IS_INFO_MANGA
import exh.merged.sql.tables.MergedTable.COL_MANGA_ID
import exh.merged.sql.tables.MergedTable.COL_MANGA_SOURCE
import exh.merged.sql.tables.MergedTable.COL_MANGA_URL
import exh.merged.sql.tables.MergedTable.COL_MERGE_ID
import exh.merged.sql.tables.MergedTable.COL_MERGE_URL
import exh.merged.sql.tables.MergedTable.TABLE
class MergedMangaTypeMapping : SQLiteTypeMapping<MergedMangaReference>(
MergedMangaPutResolver(),
MergedMangaGetResolver(),
MergedMangaDeleteResolver(),
)
class MergedMangaPutResolver : DefaultPutResolver<MergedMangaReference>() {
override fun mapToInsertQuery(obj: MergedMangaReference) = InsertQuery.builder()
.table(TABLE)
.build()
override fun mapToUpdateQuery(obj: MergedMangaReference) = UpdateQuery.builder()
.table(TABLE)
.where("$COL_ID = ?")
.whereArgs(obj.id)
.build()
override fun mapToContentValues(obj: MergedMangaReference) = contentValuesOf(
COL_ID to obj.id,
COL_IS_INFO_MANGA to obj.isInfoManga,
COL_GET_CHAPTER_UPDATES to obj.getChapterUpdates,
COL_CHAPTER_SORT_MODE to obj.chapterSortMode,
COL_CHAPTER_PRIORITY to obj.chapterPriority,
COL_DOWNLOAD_CHAPTERS to obj.downloadChapters,
COL_MERGE_ID to obj.mergeId,
COL_MERGE_URL to obj.mergeUrl,
COL_MANGA_ID to obj.mangaId,
COL_MANGA_URL to obj.mangaUrl,
COL_MANGA_SOURCE to obj.mangaSourceId,
)
}
class MergedMangaGetResolver : DefaultGetResolver<MergedMangaReference>() {
override fun mapFromCursor(cursor: Cursor): MergedMangaReference = MergedMangaReference(
id = cursor.getLong(cursor.getColumnIndexOrThrow(COL_ID)),
isInfoManga = cursor.getInt(cursor.getColumnIndexOrThrow(COL_IS_INFO_MANGA)) == 1,
getChapterUpdates = cursor.getInt(cursor.getColumnIndexOrThrow(COL_GET_CHAPTER_UPDATES)) == 1,
chapterSortMode = cursor.getInt(cursor.getColumnIndexOrThrow(COL_CHAPTER_SORT_MODE)),
chapterPriority = cursor.getInt(cursor.getColumnIndexOrThrow(COL_CHAPTER_PRIORITY)),
downloadChapters = cursor.getInt(cursor.getColumnIndexOrThrow(COL_DOWNLOAD_CHAPTERS)) == 1,
mergeId = cursor.getLong(cursor.getColumnIndexOrThrow(COL_MERGE_ID)),
mergeUrl = cursor.getString(cursor.getColumnIndexOrThrow(COL_MERGE_URL)),
mangaId = cursor.getLongOrNull(cursor.getColumnIndexOrThrow(COL_MANGA_ID)),
mangaUrl = cursor.getString(cursor.getColumnIndexOrThrow(COL_MANGA_URL)),
mangaSourceId = cursor.getLong(cursor.getColumnIndexOrThrow(COL_MANGA_SOURCE)),
)
}
class MergedMangaDeleteResolver : DefaultDeleteResolver<MergedMangaReference>() {
override fun mapToDeleteQuery(obj: MergedMangaReference) = DeleteQuery.builder()
.table(TABLE)
.where("$COL_ID = ?")
.whereArgs(obj.id)
.build()
}
@@ -1,69 +0,0 @@
package exh.merged.sql.queries
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.models.Manga
import eu.kanade.tachiyomi.data.database.queries.getMergedMangaForDownloadingQuery
import eu.kanade.tachiyomi.data.database.queries.getMergedMangaQuery
import exh.merged.sql.models.MergedMangaReference
import exh.merged.sql.resolvers.MergeMangaSettingsPutResolver
import exh.merged.sql.resolvers.MergedMangaIdPutResolver
import exh.merged.sql.resolvers.MergedMangaSettingsPutResolver
import exh.merged.sql.tables.MergedTable
interface MergedQueries : DbProvider {
fun getMergedMangaReferences(mergedMangaId: Long) = db.get()
.listOfObjects(MergedMangaReference::class.java)
.withQuery(
Query.builder()
.table(MergedTable.TABLE)
.where("${MergedTable.COL_MERGE_ID} = ?")
.whereArgs(mergedMangaId)
.build(),
)
.prepare()
fun deleteMangaForMergedManga(mergedMangaId: Long) = db.delete()
.byQuery(
DeleteQuery.builder()
.table(MergedTable.TABLE)
.where("${MergedTable.COL_MERGE_ID} = ?")
.whereArgs(mergedMangaId)
.build(),
)
.prepare()
fun getMergedMangas(mergedMangaId: Long) = db.get()
.listOfObjects(Manga::class.java)
.withQuery(
RawQuery.builder()
.query(getMergedMangaQuery())
.args(mergedMangaId)
.build(),
)
.prepare()
fun getMergedMangasForDownloading(mergedMangaId: Long) = db.get()
.listOfObjects(Manga::class.java)
.withQuery(
RawQuery.builder()
.query(getMergedMangaForDownloadingQuery())
.args(mergedMangaId)
.build(),
)
.prepare()
fun insertMergedManga(mergedManga: MergedMangaReference) = db.put().`object`(mergedManga).prepare()
fun insertNewMergedMangaId(mergedManga: MergedMangaReference) = db.put().`object`(mergedManga).withPutResolver(MergedMangaIdPutResolver()).prepare()
fun insertMergedMangas(mergedManga: List<MergedMangaReference>) = db.put().objects(mergedManga).prepare()
fun updateMergedMangaSettings(mergedManga: List<MergedMangaReference>) = db.put().objects(mergedManga).withPutResolver(MergedMangaSettingsPutResolver()).prepare()
fun updateMergeMangaSettings(mergeManga: MergedMangaReference) = db.put().`object`(mergeManga).withPutResolver(MergeMangaSettingsPutResolver()).prepare()
fun deleteMergedManga(mergedManga: MergedMangaReference) = db.delete().`object`(mergedManga).prepare()
}
@@ -1,31 +0,0 @@
package exh.merged.sql.resolvers
import androidx.core.content.contentValuesOf
import com.pushtorefresh.storio.sqlite.StorIOSQLite
import com.pushtorefresh.storio.sqlite.operations.put.PutResolver
import com.pushtorefresh.storio.sqlite.operations.put.PutResult
import com.pushtorefresh.storio.sqlite.queries.UpdateQuery
import eu.kanade.tachiyomi.data.database.inTransactionReturn
import exh.merged.sql.models.MergedMangaReference
import exh.merged.sql.tables.MergedTable
class MergeMangaSettingsPutResolver(val reset: Boolean = false) : PutResolver<MergedMangaReference>() {
override fun performPut(db: StorIOSQLite, mergedMangaReference: MergedMangaReference) = db.inTransactionReturn {
val updateQuery = mapToUpdateQuery(mergedMangaReference)
val contentValues = mapToContentValues(mergedMangaReference)
val numberOfRowsUpdated = db.lowLevel().update(updateQuery, contentValues)
PutResult.newUpdateResult(numberOfRowsUpdated, updateQuery.table())
}
fun mapToUpdateQuery(mergedMangaReference: MergedMangaReference) = UpdateQuery.builder()
.table(MergedTable.TABLE)
.where("${MergedTable.COL_ID} = ?")
.whereArgs(mergedMangaReference.id)
.build()
fun mapToContentValues(mergedMangaReference: MergedMangaReference) = contentValuesOf(
MergedTable.COL_CHAPTER_SORT_MODE to mergedMangaReference.chapterSortMode,
)
}
@@ -1,31 +0,0 @@
package exh.merged.sql.resolvers
import androidx.core.content.contentValuesOf
import com.pushtorefresh.storio.sqlite.StorIOSQLite
import com.pushtorefresh.storio.sqlite.operations.put.PutResolver
import com.pushtorefresh.storio.sqlite.operations.put.PutResult
import com.pushtorefresh.storio.sqlite.queries.UpdateQuery
import eu.kanade.tachiyomi.data.database.inTransactionReturn
import exh.merged.sql.models.MergedMangaReference
import exh.merged.sql.tables.MergedTable
class MergedMangaIdPutResolver : PutResolver<MergedMangaReference>() {
override fun performPut(db: StorIOSQLite, mergedMangaReference: MergedMangaReference) = db.inTransactionReturn {
val updateQuery = mapToUpdateQuery(mergedMangaReference)
val contentValues = mapToContentValues(mergedMangaReference)
val numberOfRowsUpdated = db.lowLevel().update(updateQuery, contentValues)
PutResult.newUpdateResult(numberOfRowsUpdated, updateQuery.table())
}
fun mapToUpdateQuery(mergedMangaReference: MergedMangaReference) = UpdateQuery.builder()
.table(MergedTable.TABLE)
.where("${MergedTable.COL_ID} = ?")
.whereArgs(mergedMangaReference.id)
.build()
fun mapToContentValues(mergedMangaReference: MergedMangaReference) = contentValuesOf(
MergedTable.COL_MANGA_ID to mergedMangaReference.mangaId,
)
}
@@ -1,34 +0,0 @@
package exh.merged.sql.resolvers
import androidx.core.content.contentValuesOf
import com.pushtorefresh.storio.sqlite.StorIOSQLite
import com.pushtorefresh.storio.sqlite.operations.put.PutResolver
import com.pushtorefresh.storio.sqlite.operations.put.PutResult
import com.pushtorefresh.storio.sqlite.queries.UpdateQuery
import eu.kanade.tachiyomi.data.database.inTransactionReturn
import exh.merged.sql.models.MergedMangaReference
import exh.merged.sql.tables.MergedTable
class MergedMangaSettingsPutResolver(val reset: Boolean = false) : PutResolver<MergedMangaReference>() {
override fun performPut(db: StorIOSQLite, mergedMangaReference: MergedMangaReference) = db.inTransactionReturn {
val updateQuery = mapToUpdateQuery(mergedMangaReference)
val contentValues = mapToContentValues(mergedMangaReference)
val numberOfRowsUpdated = db.lowLevel().update(updateQuery, contentValues)
PutResult.newUpdateResult(numberOfRowsUpdated, updateQuery.table())
}
fun mapToUpdateQuery(mergedMangaReference: MergedMangaReference) = UpdateQuery.builder()
.table(MergedTable.TABLE)
.where("${MergedTable.COL_ID} = ?")
.whereArgs(mergedMangaReference.id)
.build()
fun mapToContentValues(mergedMangaReference: MergedMangaReference) = contentValuesOf(
MergedTable.COL_GET_CHAPTER_UPDATES to mergedMangaReference.getChapterUpdates,
MergedTable.COL_DOWNLOAD_CHAPTERS to mergedMangaReference.downloadChapters,
MergedTable.COL_IS_INFO_MANGA to mergedMangaReference.isInfoManga,
MergedTable.COL_CHAPTER_PRIORITY to mergedMangaReference.chapterPriority,
)
}
@@ -1,28 +0,0 @@
package exh.merged.sql.tables
object MergedTable {
const val TABLE = "merged"
const val COL_ID = "_id"
const val COL_IS_INFO_MANGA = "info_manga"
const val COL_GET_CHAPTER_UPDATES = "get_chapter_updates"
const val COL_CHAPTER_SORT_MODE = "chapter_sort_mode"
const val COL_CHAPTER_PRIORITY = "chapter_priority"
const val COL_DOWNLOAD_CHAPTERS = "download_chapters"
const val COL_MERGE_ID = "merge_id"
const val COL_MERGE_URL = "merge_url"
const val COL_MANGA_ID = "manga_id"
const val COL_MANGA_URL = "manga_url"
const val COL_MANGA_SOURCE = "manga_source"
}
@@ -1,9 +1,5 @@
package exh.metadata.metadata.base
import eu.kanade.data.DatabaseHandler
import eu.kanade.data.exh.searchMetadataMapper
import eu.kanade.data.exh.searchTagMapper
import eu.kanade.data.exh.searchTitleMapper
import exh.metadata.sql.models.SearchMetadata
import exh.metadata.sql.models.SearchTag
import exh.metadata.sql.models.SearchTitle
@@ -27,35 +23,3 @@ data class FlatMetadata(
fillBaseFields(this@FlatMetadata)
}
}
@Deprecated("Replace with GetFlatMetadataById")
suspend fun DatabaseHandler.awaitFlatMetadataForManga(mangaId: Long): FlatMetadata? {
return await {
val meta = search_metadataQueries.selectByMangaId(mangaId, searchMetadataMapper).executeAsOneOrNull()
if (meta != null) {
val tags = search_tagsQueries.selectByMangaId(mangaId, searchTagMapper).executeAsList()
val titles = search_titlesQueries.selectByMangaId(mangaId, searchTitleMapper).executeAsList()
FlatMetadata(meta, tags, titles)
} else null
}
}
@Deprecated("Replace with InsertFlatMetadata")
suspend fun DatabaseHandler.awaitInsertFlatMetadata(flatMetadata: FlatMetadata) {
require(flatMetadata.metadata.mangaId != -1L)
await(true) {
flatMetadata.metadata.run {
search_metadataQueries.upsert(mangaId, uploader, extra, indexedExtra, extraVersion)
}
search_tagsQueries.deleteByManga(flatMetadata.metadata.mangaId)
flatMetadata.tags.forEach {
search_tagsQueries.insert(it.mangaId, it.namespace, it.name, it.type)
}
search_titlesQueries.deleteByManga(flatMetadata.metadata.mangaId)
flatMetadata.titles.forEach {
search_titlesQueries.insert(it.mangaId, it.title, it.type)
}
}
}
@@ -1,76 +0,0 @@
package exh.savedsearches.mappers
import android.database.Cursor
import androidx.core.content.contentValuesOf
import androidx.core.database.getLongOrNull
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.savedsearches.mappers.FeedSavedSearchTable.COL_GLOBAL
import exh.savedsearches.mappers.FeedSavedSearchTable.COL_ID
import exh.savedsearches.mappers.FeedSavedSearchTable.COL_SAVED_SEARCH_ID
import exh.savedsearches.mappers.FeedSavedSearchTable.COL_SOURCE
import exh.savedsearches.mappers.FeedSavedSearchTable.TABLE
import exh.savedsearches.models.FeedSavedSearch
private object FeedSavedSearchTable {
const val TABLE = "feed_saved_search"
const val COL_ID = "_id"
const val COL_SOURCE = "source"
const val COL_SAVED_SEARCH_ID = "saved_search"
const val COL_GLOBAL = "global"
}
class FeedSavedSearchTypeMapping : SQLiteTypeMapping<FeedSavedSearch>(
FeedSavedSearchPutResolver(),
FeedSavedSearchGetResolver(),
FeedSavedSearchDeleteResolver(),
)
class FeedSavedSearchPutResolver : DefaultPutResolver<FeedSavedSearch>() {
override fun mapToInsertQuery(obj: FeedSavedSearch) = InsertQuery.builder()
.table(TABLE)
.build()
override fun mapToUpdateQuery(obj: FeedSavedSearch) = UpdateQuery.builder()
.table(TABLE)
.where("$COL_ID = ?")
.whereArgs(obj.id)
.build()
override fun mapToContentValues(obj: FeedSavedSearch) = contentValuesOf(
COL_ID to obj.id,
COL_SOURCE to obj.source,
COL_SAVED_SEARCH_ID to obj.savedSearch,
COL_GLOBAL to obj.global,
)
}
class FeedSavedSearchGetResolver : DefaultGetResolver<FeedSavedSearch>() {
override fun mapFromCursor(cursor: Cursor): FeedSavedSearch = FeedSavedSearch(
id = cursor.getLong(cursor.getColumnIndexOrThrow(COL_ID)),
source = cursor.getLong(cursor.getColumnIndexOrThrow(COL_SOURCE)),
savedSearch = cursor.getLongOrNull(cursor.getColumnIndexOrThrow(COL_SAVED_SEARCH_ID)),
global = cursor.getInt(cursor.getColumnIndexOrThrow(COL_GLOBAL)) == 1,
)
}
class FeedSavedSearchDeleteResolver : DefaultDeleteResolver<FeedSavedSearch>() {
override fun mapToDeleteQuery(obj: FeedSavedSearch) = DeleteQuery.builder()
.table(TABLE)
.where("$COL_ID = ?")
.whereArgs(obj.id)
.build()
}
@@ -1,81 +0,0 @@
package exh.savedsearches.mappers
import android.database.Cursor
import androidx.core.content.contentValuesOf
import androidx.core.database.getStringOrNull
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.savedsearches.mappers.SavedSearchTable.COL_FILTERS_JSON
import exh.savedsearches.mappers.SavedSearchTable.COL_ID
import exh.savedsearches.mappers.SavedSearchTable.COL_NAME
import exh.savedsearches.mappers.SavedSearchTable.COL_QUERY
import exh.savedsearches.mappers.SavedSearchTable.COL_SOURCE
import exh.savedsearches.mappers.SavedSearchTable.TABLE
import exh.savedsearches.models.SavedSearch
private object SavedSearchTable {
const val TABLE = "saved_search"
const val COL_ID = "_id"
const val COL_SOURCE = "source"
const val COL_NAME = "name"
const val COL_QUERY = "query"
const val COL_FILTERS_JSON = "filters_json"
}
class SavedSearchTypeMapping : SQLiteTypeMapping<SavedSearch>(
SavedSearchPutResolver(),
SavedSearchGetResolver(),
SavedSearchDeleteResolver(),
)
class SavedSearchPutResolver : DefaultPutResolver<SavedSearch>() {
override fun mapToInsertQuery(obj: SavedSearch) = InsertQuery.builder()
.table(TABLE)
.build()
override fun mapToUpdateQuery(obj: SavedSearch) = UpdateQuery.builder()
.table(TABLE)
.where("$COL_ID = ?")
.whereArgs(obj.id)
.build()
override fun mapToContentValues(obj: SavedSearch) = contentValuesOf(
COL_ID to obj.id,
COL_SOURCE to obj.source,
COL_NAME to obj.name,
COL_QUERY to obj.query,
COL_FILTERS_JSON to obj.filtersJson,
)
}
class SavedSearchGetResolver : DefaultGetResolver<SavedSearch>() {
override fun mapFromCursor(cursor: Cursor): SavedSearch = SavedSearch(
id = cursor.getLong(cursor.getColumnIndexOrThrow(COL_ID)),
source = cursor.getLong(cursor.getColumnIndexOrThrow(COL_SOURCE)),
name = cursor.getString(cursor.getColumnIndexOrThrow(COL_NAME)),
query = cursor.getStringOrNull(cursor.getColumnIndexOrThrow(COL_QUERY)),
filtersJson = cursor.getStringOrNull(cursor.getColumnIndexOrThrow(COL_FILTERS_JSON)),
)
}
class SavedSearchDeleteResolver : DefaultDeleteResolver<SavedSearch>() {
override fun mapToDeleteQuery(obj: SavedSearch) = DeleteQuery.builder()
.table(TABLE)
.where("$COL_ID = ?")
.whereArgs(obj.id)
.build()
}
@@ -1,13 +1,14 @@
package exh.smartsearch
import eu.kanade.data.DatabaseHandler
import eu.kanade.tachiyomi.data.database.DatabaseHelper
import eu.kanade.domain.manga.interactor.GetManga
import eu.kanade.domain.manga.interactor.InsertManga
import eu.kanade.domain.manga.model.toDbManga
import eu.kanade.tachiyomi.data.database.models.Manga
import eu.kanade.tachiyomi.data.database.models.toDomainManga
import eu.kanade.tachiyomi.source.CatalogueSource
import eu.kanade.tachiyomi.source.model.FilterList
import eu.kanade.tachiyomi.source.model.SManga
import eu.kanade.tachiyomi.util.lang.awaitSingle
import exh.util.executeOnIO
import info.debatty.java.stringsimilarity.NormalizedLevenshtein
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.async
@@ -18,8 +19,8 @@ import java.util.Locale
class SmartSearchEngine(
private val extraSearchParams: String? = null,
) {
private val db: DatabaseHelper by injectLazy()
private val handler: DatabaseHandler by injectLazy()
private val getManga: GetManga by injectLazy()
private val insertManga: InsertManga by injectLazy()
private val normalizedLevenshtein = NormalizedLevenshtein()
@@ -172,15 +173,22 @@ class SmartSearchEngine(
* @return a manga from the database.
*/
suspend fun networkToLocalManga(sManga: SManga, sourceId: Long): Manga {
var localManga = db.getManga(sManga.url, sourceId).executeOnIO()
var localManga = getManga.await(sManga.url, sourceId)
if (localManga == null) {
val newManga = Manga.create(sManga.url, sManga.title, sourceId)
newManga.copyFrom(sManga)
val result = db.insertManga(newManga).executeOnIO()
newManga.id = result.insertedId()
localManga = newManga
newManga.id = -1
val result = run {
val id = insertManga.await(newManga.toDomainManga()!!)
getManga.await(id!!)
}
localManga = result
} else if (!localManga.favorite) {
// if the manga isn't a favorite, set its display title from source
// if it later becomes a favorite, updated title will go to db
localManga = localManga.copy(ogTitle = sManga.title)
}
return localManga
return localManga?.toDbManga()!!
}
companion object {
@@ -1,14 +1,13 @@
package exh.ui.metadata
import android.os.Bundle
import eu.kanade.data.DatabaseHandler
import eu.kanade.domain.manga.interactor.GetFlatMetadataById
import eu.kanade.domain.manga.model.Manga
import eu.kanade.tachiyomi.data.preference.PreferencesHelper
import eu.kanade.tachiyomi.source.Source
import eu.kanade.tachiyomi.source.online.MetadataSource
import eu.kanade.tachiyomi.util.lang.launchIO
import exh.metadata.metadata.base.RaisedSearchMetadata
import exh.metadata.metadata.base.awaitFlatMetadataForManga
import exh.source.getMainSource
import exh.ui.base.CoroutinePresenter
import kotlinx.coroutines.flow.MutableStateFlow
@@ -19,7 +18,7 @@ class MetadataViewPresenter(
val manga: Manga,
val source: Source,
val preferences: PreferencesHelper = Injekt.get(),
private val db: DatabaseHandler = Injekt.get(),
private val getFlatMetadataById: GetFlatMetadataById = Injekt.get(),
) : CoroutinePresenter<MetadataViewController>() {
val meta = MutableStateFlow<RaisedSearchMetadata?>(null)
@@ -28,7 +27,7 @@ class MetadataViewPresenter(
super.onCreate(savedState)
launchIO {
val flatMetadata = db.awaitFlatMetadataForManga(manga.id) ?: return@launchIO
val flatMetadata = getFlatMetadataById.await(manga.id) ?: return@launchIO
val mainSource = source.getMainSource<MetadataSource<*, *>>()
if (mainSource != null) {
meta.value = flatMetadata.raise(mainSource.metaClass)
@@ -1,24 +0,0 @@
package exh.util
import android.database.Cursor
import com.pushtorefresh.storio.operations.PreparedOperation
import com.pushtorefresh.storio.sqlite.operations.get.PreparedGetCursor
import com.pushtorefresh.storio.sqlite.operations.get.PreparedGetListOfObjects
import com.pushtorefresh.storio.sqlite.operations.get.PreparedGetObject
import com.pushtorefresh.storio.sqlite.operations.put.PreparedPutCollectionOfObjects
import com.pushtorefresh.storio.sqlite.operations.put.PreparedPutObject
import com.pushtorefresh.storio.sqlite.operations.put.PutResult
import com.pushtorefresh.storio.sqlite.operations.put.PutResults
import eu.kanade.tachiyomi.util.lang.withIOContext
suspend fun <T> PreparedGetListOfObjects<T>.executeOnIO(): List<T> = withIOContext { executeAsBlocking() }
suspend fun <T> PreparedGetObject<T>.executeOnIO(): T? = withIOContext { executeAsBlocking() }
suspend fun <T> PreparedPutObject<T>.executeOnIO(): PutResult = withIOContext { executeAsBlocking() }
suspend fun <T> PreparedPutCollectionOfObjects<T>.executeOnIO(): PutResults<T> = withIOContext { executeAsBlocking() }
suspend fun PreparedGetCursor.executeOnIO(): Cursor = withIOContext { executeAsBlocking() }
suspend fun <T> PreparedOperation<T>.executeOnIO(): T? = withIOContext { executeAsBlocking() }