Use SQLDelight for all Manga related queries (#7447)

(cherry picked from commit 17951cfd68159d083df54c3e03094d8d66fe02ec)

# 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

View File

@ -148,6 +148,7 @@ dependencies {
implementation(androidx.paging.runtime)
implementation(androidx.paging.compose)
implementation(libs.bundles.sqlite)
implementation(androidx.sqlite)
implementation(libs.sqldelight.android.driver)
implementation(libs.sqldelight.coroutines)
@ -201,11 +202,6 @@ dependencies {
implementation(libs.unifile)
implementation(libs.junrar)
// Database
implementation(libs.bundles.sqlite)
implementation("com.github.inorichi.storio:storio-common:8be19de@aar")
implementation("com.github.inorichi.storio:storio-sqlite:8be19de@aar")
// Preferences
implementation(libs.preferencektx)
implementation(libs.flowpreferences)

View File

@ -91,9 +91,5 @@ class AndroidDatabaseHandler(
// SY -->
fun getLibraryQuery() = LibraryQuery(driver)
fun rawQuery(query: (SqlDriver) -> Unit) {
return query(driver)
}
// SY <--
}

View File

@ -1,7 +1,9 @@
package eu.kanade.data.manga
import eu.kanade.data.listOfStringsAndAdapter
import eu.kanade.domain.chapter.model.Chapter
import eu.kanade.domain.manga.model.Manga
import eu.kanade.tachiyomi.data.database.models.LibraryManga
val mangaMapper: (Long, Long, String, String?, String?, String?, List<String>?, String, Long, String?, Boolean, Long?, Long?, Boolean, Long, Long, Long, Long, List<String>?) -> Manga =
{ id, source, url, artist, author, description, genre, title, status, thumbnailUrl, favorite, lastUpdate, _, initialized, viewer, chapterFlags, coverLastModified, dateAdded, filteredScanlators ->
@ -71,3 +73,30 @@ val mangaChapterMapper: (Long, Long, String, String?, String?, String?, List<Str
scanlator = scanlator,
)
}
val libraryManga: (Long, Long, String, String?, String?, String?, List<String>?, String, Long, String?, Boolean, Long?, Long?, Boolean, Long, Long, Long, Long, List<String>?, Long, Long, Long) -> LibraryManga =
{ _id, source, url, artist, author, description, genre, title, status, thumbnail_url, favorite, last_update, next_update, initialized, viewer, chapter_flags, cover_last_modified, date_added, filtered_scanlators, unread_count, read_count, category ->
LibraryManga().apply {
this.id = _id
this.source = source
this.url = url
this.artist = artist
this.author = author
this.description = description
this.genre = genre?.joinToString()
this.title = title
this.status = status.toInt()
this.thumbnail_url = thumbnail_url
this.favorite = favorite
this.last_update = last_update ?: 0
this.initialized = initialized
this.viewer_flags = viewer.toInt()
this.chapter_flags = chapter_flags.toInt()
this.cover_last_modified = cover_last_modified
this.date_added = date_added
this.filtered_scanlators = filtered_scanlators?.let(listOfStringsAndAdapter::encode)
this.unreadCount = unread_count.toInt()
this.readCount = read_count.toInt()
this.category = category.toInt()
}
}

View File

@ -72,4 +72,57 @@ class MangaMergeRepositoryImpl(
}
}
}
override suspend fun insert(reference: MergedMangaReference): Long? {
return handler.awaitOneOrNull {
mergedQueries.insert(
infoManga = reference.isInfoManga,
getChapterUpdates = reference.getChapterUpdates,
chapterSortMode = reference.chapterSortMode.toLong(),
chapterPriority = reference.chapterPriority.toLong(),
downloadChapters = reference.downloadChapters,
mergeId = reference.mergeId!!,
mergeUrl = reference.mergeUrl,
mangaId = reference.mangaId,
mangaUrl = reference.mangaUrl,
mangaSource = reference.mangaSourceId,
)
mergedQueries.selectLastInsertedRowId()
}
}
override suspend fun insertAll(references: List<MergedMangaReference>) {
handler.await(true) {
references.forEach { reference ->
mergedQueries.insert(
infoManga = reference.isInfoManga,
getChapterUpdates = reference.getChapterUpdates,
chapterSortMode = reference.chapterSortMode.toLong(),
chapterPriority = reference.chapterPriority.toLong(),
downloadChapters = reference.downloadChapters,
mergeId = reference.mergeId!!,
mergeUrl = reference.mergeUrl,
mangaId = reference.mangaId,
mangaUrl = reference.mangaUrl,
mangaSource = reference.mangaSourceId,
)
}
}
}
override suspend fun deleteById(id: Long) {
handler.await {
mergedQueries.deleteById(id)
}
}
override suspend fun deleteByMergeId(mergeId: Long) {
handler.await {
mergedQueries.deleteByMergeId(mergeId)
}
}
override suspend fun getMergeMangaForDownloading(mergeId: Long): List<Manga> {
return handler.awaitList { mergedQueries.selectMergedMangasForDownloadingById(mergeId, mangaMapper) }
}
}

View File

@ -1,5 +1,6 @@
package eu.kanade.data.manga
import eu.kanade.data.AndroidDatabaseHandler
import eu.kanade.data.DatabaseHandler
import eu.kanade.data.listOfStringsAdapter
import eu.kanade.data.listOfStringsAndAdapter
@ -7,6 +8,7 @@ import eu.kanade.data.toLong
import eu.kanade.domain.manga.model.Manga
import eu.kanade.domain.manga.model.MangaUpdate
import eu.kanade.domain.manga.repository.MangaRepository
import eu.kanade.tachiyomi.data.database.models.LibraryManga
import eu.kanade.tachiyomi.util.system.logcat
import kotlinx.coroutines.flow.Flow
import logcat.LogPriority
@ -19,26 +21,28 @@ class MangaRepositoryImpl(
return handler.awaitOne { mangasQueries.getMangaById(id, mangaMapper) }
}
override suspend fun subscribeMangaById(id: Long): Flow<Manga> {
return handler.subscribeToOne { mangasQueries.getMangaById(id, mangaMapper) }
}
override suspend fun getMangaByIdAsFlow(id: Long): Flow<Manga> {
return handler.subscribeToOne { mangasQueries.getMangaById(id, mangaMapper) }
}
override suspend fun getMangaByUrlAndSource(url: String, sourceId: Long): Manga? {
override suspend fun getMangaByUrlAndSourceId(url: String, sourceId: Long): Manga? {
return handler.awaitOneOrNull { mangasQueries.getMangaByUrlAndSource(url, sourceId, mangaMapper) }
}
override suspend fun subscribeMangaByUrlAndSource(url: String, sourceId: Long): Flow<Manga?> {
return handler.subscribeToOneOrNull { mangasQueries.getMangaByUrlAndSource(url, sourceId, mangaMapper) }
}
override suspend fun getFavorites(): List<Manga> {
return handler.awaitList { mangasQueries.getFavorites(mangaMapper) }
}
override suspend fun getLibraryManga(): List<LibraryManga> {
return handler.awaitList { (handler as AndroidDatabaseHandler).getLibraryQuery() }
// return handler.awaitList { mangasQueries.getLibrary(libraryManga) }
}
override fun getLibraryMangaAsFlow(): Flow<List<LibraryManga>> {
return handler.subscribeToList { (handler as AndroidDatabaseHandler).getLibraryQuery() }
// return handler.subscribeToList { mangasQueries.getLibrary(libraryManga) }
}
override fun getFavoritesBySourceId(sourceId: Long): Flow<List<Manga>> {
return handler.subscribeToList { mangasQueries.getFavoriteBySourceId(sourceId, mangaMapper) }
}
@ -68,6 +72,31 @@ class MangaRepositoryImpl(
}
}
override suspend fun insert(manga: Manga): Long? {
return handler.awaitOneOrNull {
mangasQueries.insert(
source = manga.source,
url = manga.url,
artist = manga.artist,
author = manga.author,
description = manga.description,
genre = manga.genre,
title = manga.title,
status = manga.status,
thumbnail_url = manga.thumbnailUrl,
favorite = manga.favorite,
last_update = manga.lastUpdate,
next_update = null,
initialized = manga.initialized,
viewer = manga.viewerFlags,
chapter_flags = manga.chapterFlags,
cover_last_modified = manga.coverLastModified,
date_added = manga.dateAdded,
)
mangasQueries.selectLastInsertedRowId()
}
}
override suspend fun update(update: MangaUpdate): Boolean {
return try {
partialUpdate(update)
@ -115,11 +144,15 @@ class MangaRepositoryImpl(
}
}
override suspend fun getMangaBySource(sourceId: Long): List<Manga> {
override suspend fun getMangaBySourceId(sourceId: Long): List<Manga> {
return handler.awaitList { mangasQueries.getBySource(sourceId, mangaMapper) }
}
override suspend fun getAll(): List<Manga> {
return handler.awaitList { mangasQueries.getAll(mangaMapper) }
}
override suspend fun deleteManga(mangaId: Long) {
handler.await { mangasQueries.deleteById(mangaId) }
}
}

View File

@ -32,10 +32,13 @@ import eu.kanade.domain.history.interactor.UpsertHistory
import eu.kanade.domain.history.repository.HistoryRepository
import eu.kanade.domain.manga.interactor.GetDuplicateLibraryManga
import eu.kanade.domain.manga.interactor.GetFavorites
import eu.kanade.domain.manga.interactor.GetLibraryManga
import eu.kanade.domain.manga.interactor.GetManga
import eu.kanade.domain.manga.interactor.GetMangaWithChapters
import eu.kanade.domain.manga.interactor.InsertManga
import eu.kanade.domain.manga.interactor.ResetViewerFlags
import eu.kanade.domain.manga.interactor.SetMangaChapterFlags
import eu.kanade.domain.manga.interactor.SetMangaViewerFlags
import eu.kanade.domain.manga.interactor.UpdateManga
import eu.kanade.domain.manga.repository.MangaRepository
import eu.kanade.domain.source.interactor.GetEnabledSources
@ -71,11 +74,14 @@ class DomainModule : InjektModule {
addSingletonFactory<MangaRepository> { MangaRepositoryImpl(get()) }
addFactory { GetDuplicateLibraryManga(get()) }
addFactory { GetFavorites(get()) }
addFactory { GetLibraryManga(get()) }
addFactory { GetMangaWithChapters(get(), get()) }
addFactory { GetManga(get()) }
addFactory { GetNextChapter(get()) }
addFactory { ResetViewerFlags(get()) }
addFactory { SetMangaChapterFlags(get()) }
addFactory { SetMangaViewerFlags(get()) }
addFactory { InsertManga(get()) }
addFactory { UpdateManga(get()) }
addFactory { SetMangaCategories(get()) }

View File

@ -3,8 +3,12 @@ package eu.kanade.domain
import eu.kanade.data.manga.FavoritesEntryRepositoryImpl
import eu.kanade.data.manga.MangaMergeRepositoryImpl
import eu.kanade.data.manga.MangaMetadataRepositoryImpl
import eu.kanade.domain.chapter.interactor.DeleteChapters
import eu.kanade.domain.chapter.interactor.GetMergedChapterByMangaId
import eu.kanade.domain.manga.interactor.DeleteByMergeId
import eu.kanade.domain.manga.interactor.DeleteFavoriteEntries
import eu.kanade.domain.manga.interactor.DeleteMangaById
import eu.kanade.domain.manga.interactor.DeleteMergeById
import eu.kanade.domain.manga.interactor.GetAllManga
import eu.kanade.domain.manga.interactor.GetExhFavoriteMangaWithMetadata
import eu.kanade.domain.manga.interactor.GetFavoriteEntries
@ -13,12 +17,15 @@ import eu.kanade.domain.manga.interactor.GetIdsOfFavoriteMangaWithMetadata
import eu.kanade.domain.manga.interactor.GetMangaBySource
import eu.kanade.domain.manga.interactor.GetMergedManga
import eu.kanade.domain.manga.interactor.GetMergedMangaById
import eu.kanade.domain.manga.interactor.GetMergedMangaForDownloading
import eu.kanade.domain.manga.interactor.GetMergedReferencesById
import eu.kanade.domain.manga.interactor.GetSearchTags
import eu.kanade.domain.manga.interactor.GetSearchTitles
import eu.kanade.domain.manga.interactor.InsertFavoriteEntries
import eu.kanade.domain.manga.interactor.InsertFlatMetadata
import eu.kanade.domain.manga.interactor.InsertMergedReference
import eu.kanade.domain.manga.interactor.SetMangaFilteredScanlators
import eu.kanade.domain.manga.interactor.UpdateMergedSettings
import eu.kanade.domain.manga.repository.FavoritesEntryRepository
import eu.kanade.domain.manga.repository.MangaMergeRepository
import eu.kanade.domain.manga.repository.MangaMetadataRepository
@ -44,6 +51,8 @@ class SYDomainModule : InjektModule {
addFactory { SetMangaFilteredScanlators(get()) }
addFactory { GetAllManga(get()) }
addFactory { GetMangaBySource(get()) }
addFactory { DeleteChapters(get()) }
addFactory { DeleteMangaById(get()) }
addSingletonFactory<MangaMetadataRepository> { MangaMetadataRepositoryImpl(get()) }
addFactory { GetFlatMetadataById(get()) }
@ -58,6 +67,11 @@ class SYDomainModule : InjektModule {
addFactory { GetMergedMangaById(get()) }
addFactory { GetMergedReferencesById(get()) }
addFactory { GetMergedChapterByMangaId(get()) }
addFactory { InsertMergedReference(get()) }
addFactory { UpdateMergedSettings(get()) }
addFactory { DeleteByMergeId(get()) }
addFactory { DeleteMergeById(get()) }
addFactory { GetMergedMangaForDownloading(get()) }
addSingletonFactory<FavoritesEntryRepository> { FavoritesEntryRepositoryImpl(get()) }
addFactory { GetFavoriteEntries(get()) }

View File

@ -0,0 +1,12 @@
package eu.kanade.domain.chapter.interactor
import eu.kanade.domain.chapter.repository.ChapterRepository
class DeleteChapters(
private val chapterRepository: ChapterRepository,
) {
suspend fun await(chapters: List<Long>) {
chapterRepository.removeChaptersWithIds(chapters)
}
}

View File

@ -0,0 +1,12 @@
package eu.kanade.domain.manga.interactor
import eu.kanade.domain.manga.repository.MangaMergeRepository
class DeleteByMergeId(
private val mangaMergeRepository: MangaMergeRepository,
) {
suspend fun await(id: Long) {
return mangaMergeRepository.deleteByMergeId(id)
}
}

View File

@ -0,0 +1,12 @@
package eu.kanade.domain.manga.interactor
import eu.kanade.domain.manga.repository.MangaRepository
class DeleteMangaById(
private val mangaRepository: MangaRepository,
) {
suspend fun await(id: Long) {
return mangaRepository.deleteManga(id)
}
}

View File

@ -0,0 +1,12 @@
package eu.kanade.domain.manga.interactor
import eu.kanade.domain.manga.repository.MangaMergeRepository
class DeleteMergeById(
private val mangaMergeRepository: MangaMergeRepository,
) {
suspend fun await(id: Long) {
return mangaMergeRepository.deleteById(id)
}
}

View File

@ -0,0 +1,18 @@
package eu.kanade.domain.manga.interactor
import eu.kanade.domain.manga.repository.MangaRepository
import eu.kanade.tachiyomi.data.database.models.LibraryManga
import kotlinx.coroutines.flow.Flow
class GetLibraryManga(
private val mangaRepository: MangaRepository,
) {
suspend fun await(): List<LibraryManga> {
return mangaRepository.getLibraryManga()
}
fun subscribe(): Flow<List<LibraryManga>> {
return mangaRepository.getLibraryMangaAsFlow()
}
}

View File

@ -20,19 +20,10 @@ class GetManga(
}
suspend fun subscribe(id: Long): Flow<Manga> {
return mangaRepository.subscribeMangaById(id)
return mangaRepository.getMangaByIdAsFlow(id)
}
suspend fun await(url: String, sourceId: Long): Manga? {
return try {
mangaRepository.getMangaByUrlAndSource(url, sourceId)
} catch (e: Exception) {
logcat(LogPriority.ERROR, e)
null
}
}
suspend fun subscribe(url: String, sourceId: Long): Flow<Manga?> {
return mangaRepository.subscribeMangaByUrlAndSource(url, sourceId)
return mangaRepository.getMangaByUrlAndSourceId(url, sourceId)
}
}

View File

@ -8,6 +8,6 @@ class GetMangaBySource(
) {
suspend fun await(sourceId: Long): List<Manga> {
return mangaRepository.getMangaBySource(sourceId)
return mangaRepository.getMangaBySourceId(sourceId)
}
}

View File

@ -14,7 +14,7 @@ class GetMangaWithChapters(
suspend fun subscribe(id: Long): Flow<Pair<Manga, List<Chapter>>> {
return combine(
mangaRepository.subscribeMangaById(id),
mangaRepository.getMangaByIdAsFlow(id),
chapterRepository.getChapterByMangaIdAsFlow(id),
) { manga, chapters ->
Pair(manga, chapters)

View File

@ -0,0 +1,13 @@
package eu.kanade.domain.manga.interactor
import eu.kanade.domain.manga.model.Manga
import eu.kanade.domain.manga.repository.MangaMergeRepository
class GetMergedMangaForDownloading(
private val mangaMergeRepository: MangaMergeRepository,
) {
suspend fun await(mergeId: Long): List<Manga> {
return mangaMergeRepository.getMergeMangaForDownloading(mergeId)
}
}

View File

@ -0,0 +1,13 @@
package eu.kanade.domain.manga.interactor
import eu.kanade.domain.manga.model.Manga
import eu.kanade.domain.manga.repository.MangaRepository
class InsertManga(
private val mangaRepository: MangaRepository,
) {
suspend fun await(manga: Manga): Long? {
return mangaRepository.insert(manga)
}
}

View File

@ -0,0 +1,17 @@
package eu.kanade.domain.manga.interactor
import eu.kanade.domain.manga.repository.MangaMergeRepository
import exh.merged.sql.models.MergedMangaReference
class InsertMergedReference(
private val mangaMergedRepository: MangaMergeRepository,
) {
suspend fun await(reference: MergedMangaReference): Long? {
return mangaMergedRepository.insert(reference)
}
suspend fun awaitAll(references: List<MergedMangaReference>) {
mangaMergedRepository.insertAll(references)
}
}

View File

@ -0,0 +1,33 @@
package eu.kanade.domain.manga.interactor
import eu.kanade.domain.manga.model.MangaUpdate
import eu.kanade.domain.manga.repository.MangaRepository
import eu.kanade.tachiyomi.ui.reader.setting.OrientationType
import eu.kanade.tachiyomi.ui.reader.setting.ReadingModeType
class SetMangaViewerFlags(
private val mangaRepository: MangaRepository,
) {
suspend fun awaitSetMangaReadingMode(id: Long, flag: Long) {
mangaRepository.update(
MangaUpdate(
id = id,
viewerFlags = flag.setFlag(flag, ReadingModeType.MASK.toLong()),
),
)
}
suspend fun awaitSetOrientationType(id: Long, flag: Long) {
mangaRepository.update(
MangaUpdate(
id = id,
viewerFlags = flag.setFlag(flag, OrientationType.MASK.toLong()),
),
)
}
private fun Long.setFlag(flag: Long, mask: Long): Long {
return this and mask.inv() or (flag and mask)
}
}

View File

@ -0,0 +1,17 @@
package eu.kanade.domain.manga.interactor
import eu.kanade.domain.manga.model.MergeMangaSettingsUpdate
import eu.kanade.domain.manga.repository.MangaMergeRepository
class UpdateMergedSettings(
private val mangaMergeRepository: MangaMergeRepository,
) {
suspend fun await(mergeUpdate: MergeMangaSettingsUpdate): Boolean {
return mangaMergeRepository.updateSettings(mergeUpdate)
}
suspend fun awaitAll(values: List<MergeMangaSettingsUpdate>): Boolean {
return mangaMergeRepository.updateAllSettings(values)
}
}

View File

@ -214,6 +214,28 @@ fun Manga.toMangaInfo(): MangaInfo = MangaInfo(
// SY <--
)
fun Manga.toMangaUpdate(): MangaUpdate {
return MangaUpdate(
id = id,
source = source,
favorite = favorite,
lastUpdate = lastUpdate,
dateAdded = dateAdded,
viewerFlags = viewerFlags,
chapterFlags = chapterFlags,
coverLastModified = coverLastModified,
url = url,
title = title,
artist = artist,
author = author,
description = description,
genre = genre,
status = status,
thumbnailUrl = thumbnailUrl,
initialized = initialized,
)
}
fun Manga.isLocal(): Boolean = source == LocalSource.ID
fun Manga.hasCustomCover(coverCache: CoverCache = Injekt.get()): Boolean {

View File

@ -21,4 +21,14 @@ interface MangaMergeRepository {
suspend fun updateSettings(update: MergeMangaSettingsUpdate): Boolean
suspend fun updateAllSettings(values: List<MergeMangaSettingsUpdate>): Boolean
suspend fun insert(reference: MergedMangaReference): Long?
suspend fun insertAll(references: List<MergedMangaReference>)
suspend fun deleteById(id: Long)
suspend fun deleteByMergeId(mergeId: Long)
suspend fun getMergeMangaForDownloading(mergeId: Long): List<Manga>
}

View File

@ -2,22 +2,23 @@ package eu.kanade.domain.manga.repository
import eu.kanade.domain.manga.model.Manga
import eu.kanade.domain.manga.model.MangaUpdate
import eu.kanade.tachiyomi.data.database.models.LibraryManga
import kotlinx.coroutines.flow.Flow
interface MangaRepository {
suspend fun getMangaById(id: Long): Manga
suspend fun subscribeMangaById(id: Long): Flow<Manga>
suspend fun getMangaByIdAsFlow(id: Long): Flow<Manga>
suspend fun getMangaByUrlAndSource(url: String, sourceId: Long): Manga?
suspend fun subscribeMangaByUrlAndSource(url: String, sourceId: Long): Flow<Manga?>
suspend fun getMangaByUrlAndSourceId(url: String, sourceId: Long): Manga?
suspend fun getFavorites(): List<Manga>
suspend fun getLibraryManga(): List<LibraryManga>
fun getLibraryMangaAsFlow(): Flow<List<LibraryManga>>
fun getFavoritesBySourceId(sourceId: Long): Flow<List<Manga>>
suspend fun getDuplicateLibraryManga(title: String, sourceId: Long): Manga?
@ -26,11 +27,15 @@ interface MangaRepository {
suspend fun setMangaCategories(mangaId: Long, categoryIds: List<Long>)
suspend fun insert(manga: Manga): Long?
suspend fun update(update: MangaUpdate): Boolean
suspend fun updateAll(values: List<MangaUpdate>): Boolean
suspend fun getMangaBySource(sourceId: Long): List<Manga>
suspend fun getMangaBySourceId(sourceId: Long): List<Manga>
suspend fun getAll(): List<Manga>
suspend fun deleteManga(mangaId: Long)
}

View File

@ -18,7 +18,6 @@ import eu.kanade.data.listOfStringsAdapter
import eu.kanade.data.listOfStringsAndAdapter
import eu.kanade.tachiyomi.data.cache.ChapterCache
import eu.kanade.tachiyomi.data.cache.CoverCache
import eu.kanade.tachiyomi.data.database.DatabaseHelper
import eu.kanade.tachiyomi.data.database.DbOpenCallback
import eu.kanade.tachiyomi.data.download.DownloadManager
import eu.kanade.tachiyomi.data.library.CustomMangaManager
@ -89,8 +88,6 @@ class AppModule(val app: Application) : InjektModule {
addSingletonFactory { PreferencesHelper(app) }
addSingletonFactory { DatabaseHelper(get()) }
addSingletonFactory { ChapterCache(app) }
addSingletonFactory { CoverCache(app) }
@ -125,8 +122,6 @@ class AppModule(val app: Application) : InjektModule {
get<Database>()
get<DatabaseHelper>()
get<DownloadManager>()
// SY -->

View File

@ -6,13 +6,16 @@ import eu.kanade.data.DatabaseHandler
import eu.kanade.data.manga.mangaMapper
import eu.kanade.data.toLong
import eu.kanade.domain.manga.interactor.GetFavorites
import eu.kanade.domain.manga.interactor.GetFlatMetadataById
import eu.kanade.domain.manga.interactor.GetMergedManga
import eu.kanade.domain.manga.interactor.InsertFlatMetadata
import eu.kanade.tachiyomi.data.database.models.Chapter
import eu.kanade.tachiyomi.data.database.models.Manga
import eu.kanade.tachiyomi.data.library.CustomMangaManager
import eu.kanade.tachiyomi.data.preference.PreferencesHelper
import eu.kanade.tachiyomi.data.track.TrackManager
import eu.kanade.tachiyomi.source.SourceManager
import exh.metadata.metadata.base.FlatMetadata
import uy.kohesive.injekt.Injekt
import uy.kohesive.injekt.api.get
import data.Mangas as DbManga
@ -30,6 +33,8 @@ abstract class AbstractBackupManager(protected val context: Context) {
// SY -->
private val getMergedManga: GetMergedManga = Injekt.get()
protected val customMangaManager: CustomMangaManager = Injekt.get()
private val insertFlatMetadata: InsertFlatMetadata = Injekt.get()
private val getFlatMetadataById: GetFlatMetadataById = Injekt.get()
// SY <--
abstract suspend fun createBackup(uri: Uri, flags: Int, isAutoBackup: Boolean): String
@ -65,6 +70,10 @@ abstract class AbstractBackupManager(protected val context: Context) {
protected suspend fun getMergedManga(): List<DomainManga> {
return getMergedManga.await()
}
protected suspend fun getFlatMetadata(mangaId: Long) = getFlatMetadataById.await(mangaId)
protected suspend fun insertFlatMetadata(flatMetadata: FlatMetadata) = insertFlatMetadata.await(flatMetadata)
// SY <--
/**

View File

@ -42,8 +42,6 @@ import eu.kanade.tachiyomi.data.database.models.Manga
import eu.kanade.tachiyomi.data.database.models.Track
import eu.kanade.tachiyomi.source.online.MetadataSource
import eu.kanade.tachiyomi.util.system.logcat
import exh.metadata.metadata.base.awaitFlatMetadataForManga
import exh.metadata.metadata.base.awaitInsertFlatMetadata
import exh.source.MERGED_SOURCE_ID
import exh.source.getMainSource
import exh.util.nullIfBlank
@ -197,7 +195,7 @@ class FullBackupManager(context: Context) : AbstractBackupManager(context) {
val source = sourceManager.get(manga.source)?.getMainSource<MetadataSource<*, *>>()
if (source != null) {
handler.awaitFlatMetadataForManga(manga.id)?.let { flatMetadata ->
getFlatMetadata(manga.id)?.let { flatMetadata ->
mangaObject.flatMetadata = BackupFlatMetadata.copyFrom(flatMetadata)
}
}
@ -535,18 +533,17 @@ class FullBackupManager(context: Context) : AbstractBackupManager(context) {
mergeId = mergeMangaId
mangaId = mergedManga.id
handler.await {
mergedQueries.insertMerged(
null,
isInfoManga,
getChapterUpdates,
chapterSortMode.toLong(),
chapterPriority.toLong(),
downloadChapters,
mergeId!!,
mergeUrl,
mangaId,
mangaUrl,
mangaSourceId,
mergedQueries.insert(
infoManga = isInfoManga,
getChapterUpdates = getChapterUpdates,
chapterSortMode = chapterSortMode.toLong(),
chapterPriority = chapterPriority.toLong(),
downloadChapters = downloadChapters,
mergeId = mergeId!!,
mergeUrl = mergeUrl,
mangaId = mangaId,
mangaUrl = mangaUrl,
mangaSource = mangaSourceId,
)
}
}
@ -555,8 +552,8 @@ class FullBackupManager(context: Context) : AbstractBackupManager(context) {
}
internal suspend fun restoreFlatMetadata(mangaId: Long, backupFlatMetadata: BackupFlatMetadata) {
if (handler.awaitFlatMetadataForManga(mangaId) == null) {
handler.awaitInsertFlatMetadata(backupFlatMetadata.getFlatMetadata(mangaId))
if (getFlatMetadata(mangaId) == null) {
insertFlatMetadata(backupFlatMetadata.getFlatMetadata(mangaId))
}
}
// SY <--

View File

@ -1,53 +0,0 @@
package eu.kanade.tachiyomi.data.database
import androidx.sqlite.db.SupportSQLiteOpenHelper
import com.pushtorefresh.storio.sqlite.impl.DefaultStorIOSQLite
import eu.kanade.tachiyomi.data.database.mappers.CategoryTypeMapping
import eu.kanade.tachiyomi.data.database.mappers.ChapterTypeMapping
import eu.kanade.tachiyomi.data.database.mappers.HistoryTypeMapping
import eu.kanade.tachiyomi.data.database.mappers.MangaCategoryTypeMapping
import eu.kanade.tachiyomi.data.database.mappers.MangaTypeMapping
import eu.kanade.tachiyomi.data.database.mappers.TrackTypeMapping
import eu.kanade.tachiyomi.data.database.models.Category
import eu.kanade.tachiyomi.data.database.models.Chapter
import eu.kanade.tachiyomi.data.database.models.History
import eu.kanade.tachiyomi.data.database.models.Manga
import eu.kanade.tachiyomi.data.database.models.MangaCategory
import eu.kanade.tachiyomi.data.database.models.Track
import eu.kanade.tachiyomi.data.database.queries.ChapterQueries
import eu.kanade.tachiyomi.data.database.queries.MangaQueries
import exh.merged.sql.mappers.MergedMangaTypeMapping
import exh.merged.sql.models.MergedMangaReference
import exh.merged.sql.queries.MergedQueries
/**
* This class provides operations to manage the database through its interfaces.
*/
class DatabaseHelper(
openHelper: SupportSQLiteOpenHelper,
) :
MangaQueries,
ChapterQueries,
/* SY --> */
MergedQueries
/* SY <-- */ {
override val db = DefaultStorIOSQLite.builder()
.sqliteOpenHelper(openHelper)
.addTypeMapping(Manga::class.java, MangaTypeMapping())
.addTypeMapping(Chapter::class.java, ChapterTypeMapping())
.addTypeMapping(Track::class.java, TrackTypeMapping())
.addTypeMapping(Category::class.java, CategoryTypeMapping())
.addTypeMapping(MangaCategory::class.java, MangaCategoryTypeMapping())
.addTypeMapping(History::class.java, HistoryTypeMapping())
// SY -->
.addTypeMapping(MergedMangaReference::class.java, MergedMangaTypeMapping())
// SY <--
.build()
inline fun inTransaction(block: () -> Unit) = db.inTransaction(block)
// SY -->
fun lowLevel() = db.lowLevel()
// SY <--
}

View File

@ -1,24 +0,0 @@
package eu.kanade.tachiyomi.data.database
import com.pushtorefresh.storio.sqlite.StorIOSQLite
inline fun StorIOSQLite.inTransaction(block: () -> Unit) {
lowLevel().beginTransaction()
try {
block()
lowLevel().setTransactionSuccessful()
} finally {
lowLevel().endTransaction()
}
}
inline fun <T> StorIOSQLite.inTransactionReturn(block: () -> T): T {
lowLevel().beginTransaction()
try {
val result = block()
lowLevel().setTransactionSuccessful()
return result
} finally {
lowLevel().endTransaction()
}
}

View File

@ -1,7 +0,0 @@
package eu.kanade.tachiyomi.data.database
import com.pushtorefresh.storio.sqlite.impl.DefaultStorIOSQLite
interface DbProvider {
val db: DefaultStorIOSQLite
}

View File

@ -1,71 +0,0 @@
package eu.kanade.tachiyomi.data.database.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 eu.kanade.tachiyomi.data.database.models.Category
import eu.kanade.tachiyomi.data.database.models.CategoryImpl
import eu.kanade.tachiyomi.data.database.tables.CategoryTable.COL_FLAGS
import eu.kanade.tachiyomi.data.database.tables.CategoryTable.COL_ID
import eu.kanade.tachiyomi.data.database.tables.CategoryTable.COL_MANGA_ORDER
import eu.kanade.tachiyomi.data.database.tables.CategoryTable.COL_NAME
import eu.kanade.tachiyomi.data.database.tables.CategoryTable.COL_ORDER
import eu.kanade.tachiyomi.data.database.tables.CategoryTable.TABLE
class CategoryTypeMapping : SQLiteTypeMapping<Category>(
CategoryPutResolver(),
CategoryGetResolver(),
CategoryDeleteResolver(),
)
class CategoryPutResolver : DefaultPutResolver<Category>() {
override fun mapToInsertQuery(obj: Category) = InsertQuery.builder()
.table(TABLE)
.build()
override fun mapToUpdateQuery(obj: Category) = UpdateQuery.builder()
.table(TABLE)
.where("$COL_ID = ?")
.whereArgs(obj.id)
.build()
override fun mapToContentValues(obj: Category) =
contentValuesOf(
COL_ID to obj.id,
COL_NAME to obj.name,
COL_ORDER to obj.order,
COL_FLAGS to obj.flags,
COL_MANGA_ORDER to obj.mangaOrder.joinToString("/"),
)
}
class CategoryGetResolver : DefaultGetResolver<Category>() {
override fun mapFromCursor(cursor: Cursor): Category = CategoryImpl().apply {
id = cursor.getInt(cursor.getColumnIndexOrThrow(COL_ID))
name = cursor.getString(cursor.getColumnIndexOrThrow(COL_NAME))
order = cursor.getInt(cursor.getColumnIndexOrThrow(COL_ORDER))
flags = cursor.getInt(cursor.getColumnIndexOrThrow(COL_FLAGS))
// SY -->
val orderString = cursor.getString(cursor.getColumnIndexOrThrow(COL_MANGA_ORDER))
mangaOrder = orderString?.split("/")?.mapNotNull { it.toLongOrNull() }.orEmpty()
// SY <--
}
}
class CategoryDeleteResolver : DefaultDeleteResolver<Category>() {
override fun mapToDeleteQuery(obj: Category) = DeleteQuery.builder()
.table(TABLE)
.where("$COL_ID = ?")
.whereArgs(obj.id)
.build()
}

View File

@ -1,88 +0,0 @@
package eu.kanade.tachiyomi.data.database.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 eu.kanade.tachiyomi.data.database.models.Chapter
import eu.kanade.tachiyomi.data.database.models.ChapterImpl
import eu.kanade.tachiyomi.data.database.tables.ChapterTable.COL_BOOKMARK
import eu.kanade.tachiyomi.data.database.tables.ChapterTable.COL_CHAPTER_NUMBER
import eu.kanade.tachiyomi.data.database.tables.ChapterTable.COL_DATE_FETCH
import eu.kanade.tachiyomi.data.database.tables.ChapterTable.COL_DATE_UPLOAD
import eu.kanade.tachiyomi.data.database.tables.ChapterTable.COL_ID
import eu.kanade.tachiyomi.data.database.tables.ChapterTable.COL_LAST_PAGE_READ
import eu.kanade.tachiyomi.data.database.tables.ChapterTable.COL_MANGA_ID
import eu.kanade.tachiyomi.data.database.tables.ChapterTable.COL_NAME
import eu.kanade.tachiyomi.data.database.tables.ChapterTable.COL_READ
import eu.kanade.tachiyomi.data.database.tables.ChapterTable.COL_SCANLATOR
import eu.kanade.tachiyomi.data.database.tables.ChapterTable.COL_SOURCE_ORDER
import eu.kanade.tachiyomi.data.database.tables.ChapterTable.COL_URL
import eu.kanade.tachiyomi.data.database.tables.ChapterTable.TABLE
class ChapterTypeMapping : SQLiteTypeMapping<Chapter>(
ChapterPutResolver(),
ChapterGetResolver(),
ChapterDeleteResolver(),
)
class ChapterPutResolver : DefaultPutResolver<Chapter>() {
override fun mapToInsertQuery(obj: Chapter) = InsertQuery.builder()
.table(TABLE)
.build()
override fun mapToUpdateQuery(obj: Chapter) = UpdateQuery.builder()
.table(TABLE)
.where("$COL_ID = ?")
.whereArgs(obj.id)
.build()
override fun mapToContentValues(obj: Chapter) =
contentValuesOf(
COL_ID to obj.id,
COL_MANGA_ID to obj.manga_id,
COL_URL to obj.url,
COL_NAME to obj.name,
COL_READ to obj.read,
COL_SCANLATOR to obj.scanlator,
COL_BOOKMARK to obj.bookmark,
COL_DATE_FETCH to obj.date_fetch,
COL_DATE_UPLOAD to obj.date_upload,
COL_LAST_PAGE_READ to obj.last_page_read,
COL_CHAPTER_NUMBER to obj.chapter_number,
COL_SOURCE_ORDER to obj.source_order,
)
}
class ChapterGetResolver : DefaultGetResolver<Chapter>() {
override fun mapFromCursor(cursor: Cursor): Chapter = ChapterImpl().apply {
id = cursor.getLong(cursor.getColumnIndexOrThrow(COL_ID))
manga_id = cursor.getLong(cursor.getColumnIndexOrThrow(COL_MANGA_ID))
url = cursor.getString(cursor.getColumnIndexOrThrow(COL_URL))
name = cursor.getString(cursor.getColumnIndexOrThrow(COL_NAME))
scanlator = cursor.getString(cursor.getColumnIndexOrThrow(COL_SCANLATOR))
read = cursor.getInt(cursor.getColumnIndexOrThrow(COL_READ)) == 1
bookmark = cursor.getInt(cursor.getColumnIndexOrThrow(COL_BOOKMARK)) == 1
date_fetch = cursor.getLong(cursor.getColumnIndexOrThrow(COL_DATE_FETCH))
date_upload = cursor.getLong(cursor.getColumnIndexOrThrow(COL_DATE_UPLOAD))
last_page_read = cursor.getInt(cursor.getColumnIndexOrThrow(COL_LAST_PAGE_READ))
chapter_number = cursor.getFloat(cursor.getColumnIndexOrThrow(COL_CHAPTER_NUMBER))
source_order = cursor.getInt(cursor.getColumnIndexOrThrow(COL_SOURCE_ORDER))
}
}
class ChapterDeleteResolver : DefaultDeleteResolver<Chapter>() {
override fun mapToDeleteQuery(obj: Chapter) = DeleteQuery.builder()
.table(TABLE)
.where("$COL_ID = ?")
.whereArgs(obj.id)
.build()
}

View File

@ -1,64 +0,0 @@
package eu.kanade.tachiyomi.data.database.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 eu.kanade.tachiyomi.data.database.models.History
import eu.kanade.tachiyomi.data.database.models.HistoryImpl
import eu.kanade.tachiyomi.data.database.tables.HistoryTable.COL_CHAPTER_ID
import eu.kanade.tachiyomi.data.database.tables.HistoryTable.COL_ID
import eu.kanade.tachiyomi.data.database.tables.HistoryTable.COL_LAST_READ
import eu.kanade.tachiyomi.data.database.tables.HistoryTable.COL_TIME_READ
import eu.kanade.tachiyomi.data.database.tables.HistoryTable.TABLE
class HistoryTypeMapping : SQLiteTypeMapping<History>(
HistoryPutResolver(),
HistoryGetResolver(),
HistoryDeleteResolver(),
)
open class HistoryPutResolver : DefaultPutResolver<History>() {
override fun mapToInsertQuery(obj: History) = InsertQuery.builder()
.table(TABLE)
.build()
override fun mapToUpdateQuery(obj: History) = UpdateQuery.builder()
.table(TABLE)
.where("$COL_ID = ?")
.whereArgs(obj.id)
.build()
override fun mapToContentValues(obj: History) =
contentValuesOf(
COL_ID to obj.id,
COL_CHAPTER_ID to obj.chapter_id,
COL_LAST_READ to obj.last_read,
COL_TIME_READ to obj.time_read,
)
}
class HistoryGetResolver : DefaultGetResolver<History>() {
override fun mapFromCursor(cursor: Cursor): History = HistoryImpl().apply {
id = cursor.getLong(cursor.getColumnIndexOrThrow(COL_ID))
chapter_id = cursor.getLong(cursor.getColumnIndexOrThrow(COL_CHAPTER_ID))
last_read = cursor.getLong(cursor.getColumnIndexOrThrow(COL_LAST_READ))
time_read = cursor.getLong(cursor.getColumnIndexOrThrow(COL_TIME_READ))
}
}
class HistoryDeleteResolver : DefaultDeleteResolver<History>() {
override fun mapToDeleteQuery(obj: History) = DeleteQuery.builder()
.table(TABLE)
.where("$COL_ID = ?")
.whereArgs(obj.id)
.build()
}

View File

@ -1,60 +0,0 @@
package eu.kanade.tachiyomi.data.database.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 eu.kanade.tachiyomi.data.database.models.MangaCategory
import eu.kanade.tachiyomi.data.database.tables.MangaCategoryTable.COL_CATEGORY_ID
import eu.kanade.tachiyomi.data.database.tables.MangaCategoryTable.COL_ID
import eu.kanade.tachiyomi.data.database.tables.MangaCategoryTable.COL_MANGA_ID
import eu.kanade.tachiyomi.data.database.tables.MangaCategoryTable.TABLE
class MangaCategoryTypeMapping : SQLiteTypeMapping<MangaCategory>(
MangaCategoryPutResolver(),
MangaCategoryGetResolver(),
MangaCategoryDeleteResolver(),
)
class MangaCategoryPutResolver : DefaultPutResolver<MangaCategory>() {
override fun mapToInsertQuery(obj: MangaCategory) = InsertQuery.builder()
.table(TABLE)
.build()
override fun mapToUpdateQuery(obj: MangaCategory) = UpdateQuery.builder()
.table(TABLE)
.where("$COL_ID = ?")
.whereArgs(obj.id)
.build()
override fun mapToContentValues(obj: MangaCategory) =
contentValuesOf(
COL_ID to obj.id,
COL_MANGA_ID to obj.manga_id,
COL_CATEGORY_ID to obj.category_id,
)
}
class MangaCategoryGetResolver : DefaultGetResolver<MangaCategory>() {
override fun mapFromCursor(cursor: Cursor): MangaCategory = MangaCategory().apply {
id = cursor.getLong(cursor.getColumnIndexOrThrow(COL_ID))
manga_id = cursor.getLong(cursor.getColumnIndexOrThrow(COL_MANGA_ID))
category_id = cursor.getInt(cursor.getColumnIndexOrThrow(COL_CATEGORY_ID))
}
}
class MangaCategoryDeleteResolver : DefaultDeleteResolver<MangaCategory>() {
override fun mapToDeleteQuery(obj: MangaCategory) = DeleteQuery.builder()
.table(TABLE)
.where("$COL_ID = ?")
.whereArgs(obj.id)
.build()
}

View File

@ -1,114 +0,0 @@
package eu.kanade.tachiyomi.data.database.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 eu.kanade.tachiyomi.data.database.models.Manga
import eu.kanade.tachiyomi.data.database.models.MangaImpl
import eu.kanade.tachiyomi.data.database.tables.MangaTable.COL_ARTIST
import eu.kanade.tachiyomi.data.database.tables.MangaTable.COL_AUTHOR
import eu.kanade.tachiyomi.data.database.tables.MangaTable.COL_CHAPTER_FLAGS
import eu.kanade.tachiyomi.data.database.tables.MangaTable.COL_COVER_LAST_MODIFIED
import eu.kanade.tachiyomi.data.database.tables.MangaTable.COL_DATE_ADDED
import eu.kanade.tachiyomi.data.database.tables.MangaTable.COL_DESCRIPTION
import eu.kanade.tachiyomi.data.database.tables.MangaTable.COL_FAVORITE
import eu.kanade.tachiyomi.data.database.tables.MangaTable.COL_FILTERED_SCANLATORS
import eu.kanade.tachiyomi.data.database.tables.MangaTable.COL_GENRE
import eu.kanade.tachiyomi.data.database.tables.MangaTable.COL_ID
import eu.kanade.tachiyomi.data.database.tables.MangaTable.COL_INITIALIZED
import eu.kanade.tachiyomi.data.database.tables.MangaTable.COL_LAST_UPDATE
import eu.kanade.tachiyomi.data.database.tables.MangaTable.COL_SOURCE
import eu.kanade.tachiyomi.data.database.tables.MangaTable.COL_STATUS
import eu.kanade.tachiyomi.data.database.tables.MangaTable.COL_THUMBNAIL_URL
import eu.kanade.tachiyomi.data.database.tables.MangaTable.COL_TITLE
import eu.kanade.tachiyomi.data.database.tables.MangaTable.COL_URL
import eu.kanade.tachiyomi.data.database.tables.MangaTable.COL_VIEWER
import eu.kanade.tachiyomi.data.database.tables.MangaTable.TABLE
class MangaTypeMapping : SQLiteTypeMapping<Manga>(
MangaPutResolver(),
MangaGetResolver(),
MangaDeleteResolver(),
)
class MangaPutResolver : DefaultPutResolver<Manga>() {
override fun mapToInsertQuery(obj: Manga) = InsertQuery.builder()
.table(TABLE)
.build()
override fun mapToUpdateQuery(obj: Manga) = UpdateQuery.builder()
.table(TABLE)
.where("$COL_ID = ?")
.whereArgs(obj.id)
.build()
override fun mapToContentValues(obj: Manga) =
contentValuesOf(
COL_ID to obj.id,
COL_SOURCE to obj.source,
COL_URL to obj.url,
// SY -->
COL_ARTIST to obj.originalArtist,
COL_AUTHOR to obj.originalAuthor,
COL_DESCRIPTION to obj.originalDescription,
COL_GENRE to obj.originalGenre,
COL_TITLE to obj.originalTitle,
COL_STATUS to obj.originalStatus,
// SY <--
COL_THUMBNAIL_URL to obj.thumbnail_url,
COL_FAVORITE to obj.favorite,
COL_LAST_UPDATE to obj.last_update,
COL_INITIALIZED to obj.initialized,
COL_VIEWER to obj.viewer_flags,
COL_CHAPTER_FLAGS to obj.chapter_flags,
COL_COVER_LAST_MODIFIED to obj.cover_last_modified,
COL_DATE_ADDED to obj.date_added,
COL_FILTERED_SCANLATORS to obj.filtered_scanlators,
)
}
interface BaseMangaGetResolver {
fun mapBaseFromCursor(manga: Manga, cursor: Cursor) = manga.apply {
id = cursor.getLong(cursor.getColumnIndexOrThrow(COL_ID))
source = cursor.getLong(cursor.getColumnIndexOrThrow(COL_SOURCE))
url = cursor.getString(cursor.getColumnIndexOrThrow(COL_URL))
artist = cursor.getString(cursor.getColumnIndexOrThrow(COL_ARTIST))
author = cursor.getString(cursor.getColumnIndexOrThrow(COL_AUTHOR))
description = cursor.getString(cursor.getColumnIndexOrThrow(COL_DESCRIPTION))
genre = cursor.getString(cursor.getColumnIndexOrThrow(COL_GENRE))
title = cursor.getString(cursor.getColumnIndexOrThrow(COL_TITLE))
status = cursor.getInt(cursor.getColumnIndexOrThrow(COL_STATUS))
thumbnail_url = cursor.getString(cursor.getColumnIndexOrThrow(COL_THUMBNAIL_URL))
favorite = cursor.getInt(cursor.getColumnIndexOrThrow(COL_FAVORITE)) == 1
last_update = cursor.getLong(cursor.getColumnIndexOrThrow(COL_LAST_UPDATE))
initialized = cursor.getInt(cursor.getColumnIndexOrThrow(COL_INITIALIZED)) == 1
viewer_flags = cursor.getInt(cursor.getColumnIndexOrThrow(COL_VIEWER))
chapter_flags = cursor.getInt(cursor.getColumnIndexOrThrow(COL_CHAPTER_FLAGS))
cover_last_modified = cursor.getLong(cursor.getColumnIndexOrThrow(COL_COVER_LAST_MODIFIED))
date_added = cursor.getLong(cursor.getColumnIndexOrThrow(COL_DATE_ADDED))
filtered_scanlators = cursor.getString(cursor.getColumnIndexOrThrow(COL_FILTERED_SCANLATORS))
}
}
open class MangaGetResolver : DefaultGetResolver<Manga>(), BaseMangaGetResolver {
override fun mapFromCursor(cursor: Cursor): Manga {
return mapBaseFromCursor(MangaImpl(), cursor)
}
}
class MangaDeleteResolver : DefaultDeleteResolver<Manga>() {
override fun mapToDeleteQuery(obj: Manga) = DeleteQuery.builder()
.table(TABLE)
.where("$COL_ID = ?")
.whereArgs(obj.id)
.build()
}

View File

@ -1,91 +0,0 @@
package eu.kanade.tachiyomi.data.database.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 eu.kanade.tachiyomi.data.database.models.Track
import eu.kanade.tachiyomi.data.database.models.TrackImpl
import eu.kanade.tachiyomi.data.database.tables.TrackTable.COL_FINISH_DATE
import eu.kanade.tachiyomi.data.database.tables.TrackTable.COL_ID
import eu.kanade.tachiyomi.data.database.tables.TrackTable.COL_LAST_CHAPTER_READ
import eu.kanade.tachiyomi.data.database.tables.TrackTable.COL_LIBRARY_ID
import eu.kanade.tachiyomi.data.database.tables.TrackTable.COL_MANGA_ID
import eu.kanade.tachiyomi.data.database.tables.TrackTable.COL_MEDIA_ID
import eu.kanade.tachiyomi.data.database.tables.TrackTable.COL_SCORE
import eu.kanade.tachiyomi.data.database.tables.TrackTable.COL_START_DATE
import eu.kanade.tachiyomi.data.database.tables.TrackTable.COL_STATUS
import eu.kanade.tachiyomi.data.database.tables.TrackTable.COL_SYNC_ID
import eu.kanade.tachiyomi.data.database.tables.TrackTable.COL_TITLE
import eu.kanade.tachiyomi.data.database.tables.TrackTable.COL_TOTAL_CHAPTERS
import eu.kanade.tachiyomi.data.database.tables.TrackTable.COL_TRACKING_URL
import eu.kanade.tachiyomi.data.database.tables.TrackTable.TABLE
class TrackTypeMapping : SQLiteTypeMapping<Track>(
TrackPutResolver(),
TrackGetResolver(),
TrackDeleteResolver(),
)
class TrackPutResolver : DefaultPutResolver<Track>() {
override fun mapToInsertQuery(obj: Track) = InsertQuery.builder()
.table(TABLE)
.build()
override fun mapToUpdateQuery(obj: Track) = UpdateQuery.builder()
.table(TABLE)
.where("$COL_ID = ?")
.whereArgs(obj.id)
.build()
override fun mapToContentValues(obj: Track) =
contentValuesOf(
COL_ID to obj.id,
COL_MANGA_ID to obj.manga_id,
COL_SYNC_ID to obj.sync_id,
COL_MEDIA_ID to obj.media_id,
COL_LIBRARY_ID to obj.library_id,
COL_TITLE to obj.title,
COL_LAST_CHAPTER_READ to obj.last_chapter_read,
COL_TOTAL_CHAPTERS to obj.total_chapters,
COL_STATUS to obj.status,
COL_TRACKING_URL to obj.tracking_url,
COL_SCORE to obj.score,
COL_START_DATE to obj.started_reading_date,
COL_FINISH_DATE to obj.finished_reading_date,
)
}
class TrackGetResolver : DefaultGetResolver<Track>() {
override fun mapFromCursor(cursor: Cursor): Track = TrackImpl().apply {
id = cursor.getLong(cursor.getColumnIndexOrThrow(COL_ID))
manga_id = cursor.getLong(cursor.getColumnIndexOrThrow(COL_MANGA_ID))
sync_id = cursor.getInt(cursor.getColumnIndexOrThrow(COL_SYNC_ID))
media_id = cursor.getLong(cursor.getColumnIndexOrThrow(COL_MEDIA_ID))
library_id = cursor.getLong(cursor.getColumnIndexOrThrow(COL_LIBRARY_ID))
title = cursor.getString(cursor.getColumnIndexOrThrow(COL_TITLE))
last_chapter_read = cursor.getFloat(cursor.getColumnIndexOrThrow(COL_LAST_CHAPTER_READ))
total_chapters = cursor.getInt(cursor.getColumnIndexOrThrow(COL_TOTAL_CHAPTERS))
status = cursor.getInt(cursor.getColumnIndexOrThrow(COL_STATUS))
score = cursor.getFloat(cursor.getColumnIndexOrThrow(COL_SCORE))
tracking_url = cursor.getString(cursor.getColumnIndexOrThrow(COL_TRACKING_URL))
started_reading_date = cursor.getLong(cursor.getColumnIndexOrThrow(COL_START_DATE))
finished_reading_date = cursor.getLong(cursor.getColumnIndexOrThrow(COL_FINISH_DATE))
}
}
class TrackDeleteResolver : DefaultDeleteResolver<Track>() {
override fun mapToDeleteQuery(obj: Track) = DeleteQuery.builder()
.table(TABLE)
.where("$COL_ID = ?")
.whereArgs(obj.id)
.build()
}

View File

@ -1,37 +0,0 @@
package eu.kanade.tachiyomi.data.database.queries
import com.pushtorefresh.storio.sqlite.queries.Query
import eu.kanade.tachiyomi.data.database.DbProvider
import eu.kanade.tachiyomi.data.database.models.Chapter
import eu.kanade.tachiyomi.data.database.models.Manga
import eu.kanade.tachiyomi.data.database.resolvers.ChapterProgressPutResolver
import eu.kanade.tachiyomi.data.database.tables.ChapterTable
interface ChapterQueries : DbProvider {
// SY -->
fun getChapters(manga: Manga) = getChapters(manga.id)
fun getChapters(mangaId: Long?) = db.get()
.listOfObjects(Chapter::class.java)
.withQuery(
Query.builder()
.table(ChapterTable.TABLE)
.where("${ChapterTable.COL_MANGA_ID} = ?")
.whereArgs(mangaId)
.build(),
)
.prepare()
// SY <--
fun deleteChapters(chapters: List<Chapter>) = db.delete().objects(chapters).prepare()
fun updateChapterProgress(chapter: Chapter) = db.put()
.`object`(chapter)
.withPutResolver(ChapterProgressPutResolver())
.prepare()
fun updateChaptersProgress(chapters: List<Chapter>) = db.put()
.objects(chapters)
.withPutResolver(ChapterProgressPutResolver())
.prepare()
}

View File

@ -1,79 +0,0 @@
package eu.kanade.tachiyomi.data.database.queries
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.LibraryManga
import eu.kanade.tachiyomi.data.database.models.Manga
import eu.kanade.tachiyomi.data.database.resolvers.LibraryMangaGetResolver
import eu.kanade.tachiyomi.data.database.resolvers.MangaFlagsPutResolver
import eu.kanade.tachiyomi.data.database.tables.CategoryTable
import eu.kanade.tachiyomi.data.database.tables.ChapterTable
import eu.kanade.tachiyomi.data.database.tables.MangaCategoryTable
import eu.kanade.tachiyomi.data.database.tables.MangaTable
interface MangaQueries : DbProvider {
fun getLibraryMangas() = db.get()
.listOfObjects(LibraryManga::class.java)
.withQuery(
RawQuery.builder()
.query(libraryQuery)
.observesTables(MangaTable.TABLE, ChapterTable.TABLE, MangaCategoryTable.TABLE, CategoryTable.TABLE)
.build(),
)
.withGetResolver(LibraryMangaGetResolver.INSTANCE)
.prepare()
fun getFavoriteMangas() = db.get()
.listOfObjects(Manga::class.java)
.withQuery(
Query.builder()
.table(MangaTable.TABLE)
.where("${MangaTable.COL_FAVORITE} = ?")
.whereArgs(1)
.build(),
)
.prepare()
fun getManga(url: String, sourceId: Long) = db.get()
.`object`(Manga::class.java)
.withQuery(
Query.builder()
.table(MangaTable.TABLE)
.where("${MangaTable.COL_URL} = ? AND ${MangaTable.COL_SOURCE} = ?")
.whereArgs(url, sourceId)
.build(),
)
.prepare()
fun getManga(id: Long) = db.get()
.`object`(Manga::class.java)
.withQuery(
Query.builder()
.table(MangaTable.TABLE)
.where("${MangaTable.COL_ID} = ?")
.whereArgs(id)
.build(),
)
.prepare()
fun insertManga(manga: Manga) = db.put().`object`(manga).prepare()
fun updateChapterFlags(manga: Manga) = db.put()
.`object`(manga)
.withPutResolver(MangaFlagsPutResolver(MangaTable.COL_CHAPTER_FLAGS, Manga::chapter_flags))
.prepare()
fun updateChapterFlags(manga: List<Manga>) = db.put()
.objects(manga)
.withPutResolver(MangaFlagsPutResolver(MangaTable.COL_CHAPTER_FLAGS, Manga::chapter_flags))
.prepare()
fun updateViewerFlags(manga: Manga) = db.put()
.`object`(manga)
.withPutResolver(MangaFlagsPutResolver(MangaTable.COL_VIEWER, Manga::viewer_flags))
.prepare()
fun deleteManga(manga: Manga) = db.delete().`object`(manga).prepare()
}

View File

@ -1,131 +0,0 @@
package eu.kanade.tachiyomi.data.database.queries
import exh.source.MERGED_SOURCE_ID
import eu.kanade.tachiyomi.data.database.tables.ChapterTable as Chapter
import eu.kanade.tachiyomi.data.database.tables.MangaCategoryTable as MangaCategory
import eu.kanade.tachiyomi.data.database.tables.MangaTable as Manga
import exh.merged.sql.tables.MergedTable as Merged
// SY -->
/**
* Query to get the manga merged into a merged manga
*/
fun getMergedMangaQuery() =
"""
SELECT ${Manga.TABLE}.*
FROM (
SELECT ${Merged.COL_MANGA_ID} FROM ${Merged.TABLE} WHERE ${Merged.COL_MERGE_ID} = ?
) AS M
JOIN ${Manga.TABLE}
ON ${Manga.TABLE}.${Manga.COL_ID} = M.${Merged.COL_MANGA_ID}
"""
/**
* Query to get the manga merged into a merged manga
*/
fun getMergedMangaForDownloadingQuery() =
"""
SELECT ${Manga.TABLE}.*
FROM (
SELECT ${Merged.COL_MANGA_ID} FROM ${Merged.TABLE} WHERE ${Merged.COL_MERGE_ID} = ? AND ${Merged.COL_DOWNLOAD_CHAPTERS} = 1
) AS M
JOIN ${Manga.TABLE}
ON ${Manga.TABLE}.${Manga.COL_ID} = M.${Merged.COL_MANGA_ID}
"""
/**
* Query to get all the manga that are merged into other manga
*/
fun getAllMergedMangaQuery() =
"""
SELECT ${Manga.TABLE}.*
FROM (
SELECT ${Merged.COL_MANGA_ID} FROM ${Merged.TABLE}
) AS M
JOIN ${Manga.TABLE}
ON ${Manga.TABLE}.${Manga.COL_ID} = M.${Merged.COL_MANGA_ID}
"""
/**
* Query to get the manga merged into a merged manga using the Url
*/
fun getMergedMangaFromUrlQuery() =
"""
SELECT ${Manga.TABLE}.*
FROM (
SELECT ${Merged.COL_MANGA_ID} FROM ${Merged.TABLE} WHERE ${Merged.COL_MERGE_URL} = ?
) AS M
JOIN ${Manga.TABLE}
ON ${Manga.TABLE}.${Manga.COL_ID} = M.${Merged.COL_MANGA_ID}
"""
/**
* Query to get the chapters of all manga in a merged manga
*/
fun getMergedChaptersQuery() =
"""
SELECT ${Chapter.TABLE}.*
FROM (
SELECT ${Merged.COL_MANGA_ID} FROM ${Merged.TABLE} WHERE ${Merged.COL_MERGE_ID} = ?
) AS M
JOIN ${Chapter.TABLE}
ON ${Chapter.TABLE}.${Chapter.COL_MANGA_ID} = M.${Merged.COL_MANGA_ID}
"""
/**
* Query to get the manga from the library, with their categories, read and unread count.
*/
val libraryQuery =
"""
SELECT M.*, COALESCE(MC.${MangaCategory.COL_CATEGORY_ID}, 0) AS ${Manga.COL_CATEGORY}
FROM (
SELECT ${Manga.TABLE}.*, COALESCE(C.unreadCount, 0) AS ${Manga.COMPUTED_COL_UNREAD_COUNT}, COALESCE(R.readCount, 0) AS ${Manga.COMPUTED_COL_READ_COUNT}
FROM ${Manga.TABLE}
LEFT JOIN (
SELECT ${Chapter.TABLE}.${Chapter.COL_MANGA_ID}, COUNT(*) AS unreadCount
FROM ${Chapter.TABLE}
WHERE ${Chapter.COL_READ} = 0
GROUP BY ${Chapter.TABLE}.${Chapter.COL_MANGA_ID}
) AS C
ON ${Manga.TABLE}.${Manga.COL_ID} = C.${Chapter.COL_MANGA_ID}
LEFT JOIN (
SELECT ${Chapter.COL_MANGA_ID}, COUNT(*) AS readCount
FROM ${Chapter.TABLE}
WHERE ${Chapter.COL_READ} = 1
GROUP BY ${Chapter.COL_MANGA_ID}
) AS R
ON ${Manga.TABLE}.${Manga.COL_ID} = R.${Chapter.COL_MANGA_ID}
WHERE ${Manga.COL_FAVORITE} = 1 AND ${Manga.COL_SOURCE} <> $MERGED_SOURCE_ID
GROUP BY ${Manga.TABLE}.${Manga.COL_ID}
UNION
SELECT ${Manga.TABLE}.*, COALESCE(C.unreadCount, 0) AS ${Manga.COMPUTED_COL_UNREAD_COUNT}, COALESCE(R.readCount, 0) AS ${Manga.COMPUTED_COL_READ_COUNT}
FROM ${Manga.TABLE}
LEFT JOIN (
SELECT ${Merged.TABLE}.${Merged.COL_MERGE_ID}, COUNT(*) as unreadCount
FROM ${Merged.TABLE}
JOIN ${Chapter.TABLE}
ON ${Chapter.TABLE}.${Chapter.COL_MANGA_ID} = ${Merged.TABLE}.${Merged.COL_MANGA_ID}
WHERE ${Chapter.TABLE}.${Chapter.COL_READ} = 0
GROUP BY ${Merged.TABLE}.${Merged.COL_MERGE_ID}
) AS C
ON ${Manga.TABLE}.${Manga.COL_ID} = C.${Merged.COL_MERGE_ID}
LEFT JOIN (
SELECT ${Merged.TABLE}.${Merged.COL_MERGE_ID}, COUNT(*) as readCount
FROM ${Merged.TABLE}
JOIN ${Chapter.TABLE}
ON ${Chapter.TABLE}.${Chapter.COL_MANGA_ID} = ${Merged.TABLE}.${Merged.COL_MANGA_ID}
WHERE ${Chapter.TABLE}.${Chapter.COL_READ} = 1
GROUP BY ${Merged.TABLE}.${Merged.COL_MERGE_ID}
) AS R
ON ${Manga.TABLE}.${Manga.COL_ID} = R.${Merged.COL_MERGE_ID}
WHERE ${Manga.COL_FAVORITE} = 1 AND ${Manga.COL_SOURCE} = $MERGED_SOURCE_ID
GROUP BY ${Manga.TABLE}.${Manga.COL_ID}
ORDER BY ${Manga.COL_TITLE}
) AS M
LEFT JOIN (
SELECT * FROM ${MangaCategory.TABLE}
) AS MC
ON MC.${MangaCategory.COL_MANGA_ID} = M.${Manga.COL_ID};
"""
// SY <--

View File

@ -1,34 +0,0 @@
package eu.kanade.tachiyomi.data.database.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 eu.kanade.tachiyomi.data.database.models.Chapter
import eu.kanade.tachiyomi.data.database.tables.ChapterTable
class ChapterProgressPutResolver : PutResolver<Chapter>() {
override fun performPut(db: StorIOSQLite, chapter: Chapter) = db.inTransactionReturn {
val updateQuery = mapToUpdateQuery(chapter)
val contentValues = mapToContentValues(chapter)
val numberOfRowsUpdated = db.lowLevel().update(updateQuery, contentValues)
PutResult.newUpdateResult(numberOfRowsUpdated, updateQuery.table())
}
fun mapToUpdateQuery(chapter: Chapter) = UpdateQuery.builder()
.table(ChapterTable.TABLE)
.where("${ChapterTable.COL_ID} = ?")
.whereArgs(chapter.id)
.build()
fun mapToContentValues(chapter: Chapter) =
contentValuesOf(
ChapterTable.COL_READ to chapter.read,
ChapterTable.COL_BOOKMARK to chapter.bookmark,
ChapterTable.COL_LAST_PAGE_READ to chapter.last_page_read,
)
}

View File

@ -1,32 +0,0 @@
package eu.kanade.tachiyomi.data.database.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 eu.kanade.tachiyomi.data.database.models.History
import eu.kanade.tachiyomi.data.database.tables.HistoryTable
class HistoryChapterIdPutResolver : PutResolver<History>() {
override fun performPut(db: StorIOSQLite, history: History) = db.inTransactionReturn {
val updateQuery = mapToUpdateQuery(history)
val contentValues = mapToContentValues(history)
val numberOfRowsUpdated = db.lowLevel().update(updateQuery, contentValues)
PutResult.newUpdateResult(numberOfRowsUpdated, updateQuery.table())
}
fun mapToUpdateQuery(history: History) = UpdateQuery.builder()
.table(HistoryTable.TABLE)
.where("${HistoryTable.COL_ID} = ?")
.whereArgs(history.id)
.build()
fun mapToContentValues(history: History) =
contentValuesOf(
HistoryTable.COL_CHAPTER_ID to history.chapter_id,
)
}

View File

@ -1,52 +0,0 @@
package eu.kanade.tachiyomi.data.database.resolvers
import androidx.annotation.NonNull
import androidx.core.content.contentValuesOf
import com.pushtorefresh.storio.sqlite.StorIOSQLite
import com.pushtorefresh.storio.sqlite.operations.put.PutResult
import com.pushtorefresh.storio.sqlite.queries.Query
import com.pushtorefresh.storio.sqlite.queries.UpdateQuery
import eu.kanade.tachiyomi.data.database.inTransactionReturn
import eu.kanade.tachiyomi.data.database.mappers.HistoryPutResolver
import eu.kanade.tachiyomi.data.database.models.History
import eu.kanade.tachiyomi.data.database.tables.HistoryTable
class HistoryUpsertResolver : HistoryPutResolver() {
/**
* Updates last_read time of chapter
*/
override fun performPut(@NonNull db: StorIOSQLite, @NonNull history: History): PutResult = db.inTransactionReturn {
val updateQuery = mapToUpdateQuery(history)
val cursor = db.lowLevel().query(
Query.builder()
.table(updateQuery.table())
.where(updateQuery.where())
.whereArgs(updateQuery.whereArgs())
.build(),
)
cursor.use { putCursor ->
if (putCursor.count == 0) {
val insertQuery = mapToInsertQuery(history)
val insertedId = db.lowLevel().insert(insertQuery, mapToContentValues(history))
PutResult.newInsertResult(insertedId, insertQuery.table())
} else {
val numberOfRowsUpdated = db.lowLevel().update(updateQuery, mapToUpdateContentValues(history))
PutResult.newUpdateResult(numberOfRowsUpdated, updateQuery.table())
}
}
}
override fun mapToUpdateQuery(obj: History) = UpdateQuery.builder()
.table(HistoryTable.TABLE)
.where("${HistoryTable.COL_CHAPTER_ID} = ?")
.whereArgs(obj.chapter_id)
.build()
private fun mapToUpdateContentValues(history: History) =
contentValuesOf(
HistoryTable.COL_LAST_READ to history.last_read,
)
}

View File

@ -1,25 +0,0 @@
package eu.kanade.tachiyomi.data.database.resolvers
import android.database.Cursor
import com.pushtorefresh.storio.sqlite.operations.get.DefaultGetResolver
import eu.kanade.tachiyomi.data.database.mappers.BaseMangaGetResolver
import eu.kanade.tachiyomi.data.database.models.LibraryManga
import eu.kanade.tachiyomi.data.database.tables.MangaTable
class LibraryMangaGetResolver : DefaultGetResolver<LibraryManga>(), BaseMangaGetResolver {
companion object {
val INSTANCE = LibraryMangaGetResolver()
}
override fun mapFromCursor(cursor: Cursor): LibraryManga {
val manga = LibraryManga()
mapBaseFromCursor(manga, cursor)
manga.unreadCount = cursor.getInt(cursor.getColumnIndexOrThrow(MangaTable.COMPUTED_COL_UNREAD_COUNT))
manga.category = cursor.getInt(cursor.getColumnIndexOrThrow(MangaTable.COL_CATEGORY))
manga.readCount = cursor.getInt(cursor.getColumnIndexOrThrow(MangaTable.COMPUTED_COL_READ_COUNT))
return manga
}
}

View File

@ -1,27 +0,0 @@
package eu.kanade.tachiyomi.data.database.resolvers
import android.database.Cursor
import com.pushtorefresh.storio.sqlite.operations.get.DefaultGetResolver
import eu.kanade.tachiyomi.data.database.mappers.ChapterGetResolver
import eu.kanade.tachiyomi.data.database.mappers.MangaGetResolver
import eu.kanade.tachiyomi.data.database.models.MangaChapter
class MangaChapterGetResolver : DefaultGetResolver<MangaChapter>() {
companion object {
val INSTANCE = MangaChapterGetResolver()
}
private val mangaGetResolver = MangaGetResolver()
private val chapterGetResolver = ChapterGetResolver()
override fun mapFromCursor(cursor: Cursor): MangaChapter {
val manga = mangaGetResolver.mapFromCursor(cursor)
val chapter = chapterGetResolver.mapFromCursor(cursor)
manga.id = chapter.manga_id
manga.url = cursor.getString(cursor.getColumnIndexOrThrow("mangaUrl"))
return MangaChapter(manga, chapter)
}
}

View File

@ -1,33 +0,0 @@
package eu.kanade.tachiyomi.data.database.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 eu.kanade.tachiyomi.data.database.models.Manga
import eu.kanade.tachiyomi.data.database.tables.MangaTable
class MangaFavoritePutResolver : PutResolver<Manga>() {
override fun performPut(db: StorIOSQLite, manga: Manga) = db.inTransactionReturn {
val updateQuery = mapToUpdateQuery(manga)
val contentValues = mapToContentValues(manga)
val numberOfRowsUpdated = db.lowLevel().update(updateQuery, contentValues)
PutResult.newUpdateResult(numberOfRowsUpdated, updateQuery.table())
}
fun mapToUpdateQuery(manga: Manga) = UpdateQuery.builder()
.table(MangaTable.TABLE)
.where("${MangaTable.COL_ID} = ?")
.whereArgs(manga.id)
.build()
fun mapToContentValues(manga: Manga) =
contentValuesOf(
MangaTable.COL_FAVORITE to manga.favorite,
MangaTable.COL_DATE_ADDED to manga.date_added,
)
}

View File

@ -1,32 +0,0 @@
package eu.kanade.tachiyomi.data.database.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 eu.kanade.tachiyomi.data.database.models.Manga
import eu.kanade.tachiyomi.data.database.tables.MangaTable
// [EXH]
class MangaFilteredScanlatorsPutResolver : PutResolver<Manga>() {
override fun performPut(db: StorIOSQLite, manga: Manga) = db.inTransactionReturn {
val updateQuery = mapToUpdateQuery(manga)
val contentValues = mapToContentValues(manga)
val numberOfRowsUpdated = db.lowLevel().update(updateQuery, contentValues)
PutResult.newUpdateResult(numberOfRowsUpdated, updateQuery.table())
}
fun mapToUpdateQuery(manga: Manga) = UpdateQuery.builder()
.table(MangaTable.TABLE)
.where("${MangaTable.COL_ID} = ?")
.whereArgs(manga.id)
.build()
fun mapToContentValues(manga: Manga) = contentValuesOf(
MangaTable.COL_FILTERED_SCANLATORS to manga.filtered_scanlators,
)
}

View File

@ -1,33 +0,0 @@
package eu.kanade.tachiyomi.data.database.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 eu.kanade.tachiyomi.data.database.models.Manga
import eu.kanade.tachiyomi.data.database.tables.MangaTable
import kotlin.reflect.KProperty1
class MangaFlagsPutResolver(private val colName: String, private val fieldGetter: KProperty1<Manga, Int>) : PutResolver<Manga>() {
override fun performPut(db: StorIOSQLite, manga: Manga) = db.inTransactionReturn {
val updateQuery = mapToUpdateQuery(manga)
val contentValues = mapToContentValues(manga)
val numberOfRowsUpdated = db.lowLevel().update(updateQuery, contentValues)
PutResult.newUpdateResult(numberOfRowsUpdated, updateQuery.table())
}
fun mapToUpdateQuery(manga: Manga) = UpdateQuery.builder()
.table(MangaTable.TABLE)
.where("${MangaTable.COL_ID} = ?")
.whereArgs(manga.id)
.build()
fun mapToContentValues(manga: Manga) =
contentValuesOf(
colName to fieldGetter.get(manga),
)
}

View File

@ -1,50 +0,0 @@
package eu.kanade.tachiyomi.data.database.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 eu.kanade.tachiyomi.data.database.models.Manga
import eu.kanade.tachiyomi.data.database.tables.MangaTable
import exh.util.nullIfZero
class MangaInfoPutResolver(val reset: Boolean = false) : PutResolver<Manga>() {
override fun performPut(db: StorIOSQLite, manga: Manga) = db.inTransactionReturn {
val updateQuery = mapToUpdateQuery(manga)
val contentValues = if (reset) resetToContentValues(manga) else mapToContentValues(manga)
val numberOfRowsUpdated = db.lowLevel().update(updateQuery, contentValues)
PutResult.newUpdateResult(numberOfRowsUpdated, updateQuery.table())
}
fun mapToUpdateQuery(manga: Manga) = UpdateQuery.builder()
.table(MangaTable.TABLE)
.where("${MangaTable.COL_ID} = ?")
.whereArgs(manga.id)
.build()
fun mapToContentValues(manga: Manga) = contentValuesOf(
MangaTable.COL_TITLE to manga.originalTitle,
MangaTable.COL_GENRE to manga.originalGenre,
MangaTable.COL_AUTHOR to manga.originalAuthor,
MangaTable.COL_ARTIST to manga.originalArtist,
MangaTable.COL_DESCRIPTION to manga.originalDescription,
MangaTable.COL_STATUS to manga.originalStatus,
)
private fun resetToContentValues(manga: Manga) = contentValuesOf(
MangaTable.COL_TITLE to manga.title.split(splitter).last(),
MangaTable.COL_GENRE to manga.genre?.split(splitter)?.lastOrNull(),
MangaTable.COL_AUTHOR to manga.author?.split(splitter)?.lastOrNull(),
MangaTable.COL_ARTIST to manga.artist?.split(splitter)?.lastOrNull(),
MangaTable.COL_DESCRIPTION to manga.description?.split(splitter)?.lastOrNull(),
MangaTable.COL_STATUS to manga.status.nullIfZero()?.toString()?.split(splitter)?.lastOrNull(),
)
companion object {
const val splitter = "▒ ▒∩▒"
}
}

View File

@ -1,35 +0,0 @@
package eu.kanade.tachiyomi.data.database.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 eu.kanade.tachiyomi.data.database.models.Manga
import eu.kanade.tachiyomi.data.database.tables.MangaTable
class MangaMigrationPutResolver : PutResolver<Manga>() {
override fun performPut(db: StorIOSQLite, manga: Manga) = db.inTransactionReturn {
val updateQuery = mapToUpdateQuery(manga)
val contentValues = mapToContentValues(manga)
val numberOfRowsUpdated = db.lowLevel().update(updateQuery, contentValues)
PutResult.newUpdateResult(numberOfRowsUpdated, updateQuery.table())
}
fun mapToUpdateQuery(manga: Manga) = UpdateQuery.builder()
.table(MangaTable.TABLE)
.where("${MangaTable.COL_ID} = ?")
.whereArgs(manga.id)
.build()
fun mapToContentValues(manga: Manga) = contentValuesOf(
MangaTable.COL_FAVORITE to manga.favorite,
MangaTable.COL_DATE_ADDED to manga.date_added,
MangaTable.COL_TITLE to manga.title,
MangaTable.COL_CHAPTER_FLAGS to manga.chapter_flags,
MangaTable.COL_VIEWER to manga.viewer_flags,
)
}

View File

@ -1,32 +0,0 @@
package eu.kanade.tachiyomi.data.database.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 eu.kanade.tachiyomi.data.database.models.Manga
import eu.kanade.tachiyomi.data.database.tables.MangaTable
// SY
class MangaThumbnailPutResolver : PutResolver<Manga>() {
override fun performPut(db: StorIOSQLite, manga: Manga) = db.inTransactionReturn {
val updateQuery = mapToUpdateQuery(manga)
val contentValues = mapToContentValues(manga)
val numberOfRowsUpdated = db.lowLevel().update(updateQuery, contentValues)
PutResult.newUpdateResult(numberOfRowsUpdated, updateQuery.table())
}
fun mapToUpdateQuery(manga: Manga) = UpdateQuery.builder()
.table(MangaTable.TABLE)
.where("${MangaTable.COL_ID} = ?")
.whereArgs(manga.id)
.build()
fun mapToContentValues(manga: Manga) = contentValuesOf(
MangaTable.COL_THUMBNAIL_URL to manga.thumbnail_url,
)
}

View File

@ -1,32 +0,0 @@
package eu.kanade.tachiyomi.data.database.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 eu.kanade.tachiyomi.data.database.models.Manga
import eu.kanade.tachiyomi.data.database.tables.MangaTable
// [EXH]
class MangaUrlPutResolver : PutResolver<Manga>() {
override fun performPut(db: StorIOSQLite, manga: Manga) = db.inTransactionReturn {
val updateQuery = mapToUpdateQuery(manga)
val contentValues = mapToContentValues(manga)
val numberOfRowsUpdated = db.lowLevel().update(updateQuery, contentValues)
PutResult.newUpdateResult(numberOfRowsUpdated, updateQuery.table())
}
fun mapToUpdateQuery(manga: Manga) = UpdateQuery.builder()
.table(MangaTable.TABLE)
.where("${MangaTable.COL_ID} = ?")
.whereArgs(manga.id)
.build()
fun mapToContentValues(manga: Manga) = contentValuesOf(
MangaTable.COL_URL to manga.url,
)
}

View File

@ -1,18 +0,0 @@
package eu.kanade.tachiyomi.data.database.tables
object CategoryTable {
const val TABLE = "categories"
const val COL_ID = "_id"
const val COL_NAME = "name"
const val COL_ORDER = "sort"
const val COL_FLAGS = "flags"
// SY -->
const val COL_MANGA_ORDER = "manga_order"
// SY <--
}

View File

@ -1,30 +0,0 @@
package eu.kanade.tachiyomi.data.database.tables
object ChapterTable {
const val TABLE = "chapters"
const val COL_ID = "_id"
const val COL_MANGA_ID = "manga_id"
const val COL_URL = "url"
const val COL_NAME = "name"
const val COL_READ = "read"
const val COL_SCANLATOR = "scanlator"
const val COL_BOOKMARK = "bookmark"
const val COL_DATE_FETCH = "date_fetch"
const val COL_DATE_UPLOAD = "date_upload"
const val COL_LAST_PAGE_READ = "last_page_read"
const val COL_CHAPTER_NUMBER = "chapter_number"
const val COL_SOURCE_ORDER = "source_order"
}

View File

@ -1,29 +0,0 @@
package eu.kanade.tachiyomi.data.database.tables
object HistoryTable {
/**
* Table name
*/
const val TABLE = "history"
/**
* Id column name
*/
const val COL_ID = "_id"
/**
* Chapter id column name
*/
const val COL_CHAPTER_ID = "chapter_id"
/**
* Last read column name
*/
const val COL_LAST_READ = "last_read"
/**
* Time read column name
*/
const val COL_TIME_READ = "time_read"
}

View File

@ -1,12 +0,0 @@
package eu.kanade.tachiyomi.data.database.tables
object MangaCategoryTable {
const val TABLE = "mangas_categories"
const val COL_ID = "_id"
const val COL_MANGA_ID = "manga_id"
const val COL_CATEGORY_ID = "category_id"
}

View File

@ -1,54 +0,0 @@
package eu.kanade.tachiyomi.data.database.tables
object MangaTable {
const val TABLE = "mangas"
const val COL_ID = "_id"
const val COL_SOURCE = "source"
const val COL_URL = "url"
const val COL_ARTIST = "artist"
const val COL_AUTHOR = "author"
const val COL_DESCRIPTION = "description"
const val COL_GENRE = "genre"
const val COL_TITLE = "title"
const val COL_STATUS = "status"
const val COL_THUMBNAIL_URL = "thumbnail_url"
const val COL_FAVORITE = "favorite"
const val COL_LAST_UPDATE = "last_update"
// Not actually used anymore
const val COL_NEXT_UPDATE = "next_update"
const val COL_DATE_ADDED = "date_added"
const val COL_INITIALIZED = "initialized"
const val COL_VIEWER = "viewer"
const val COL_CHAPTER_FLAGS = "chapter_flags"
// SY -->
const val COL_FILTERED_SCANLATORS = "filtered_scanlators"
// SY <--
const val COL_CATEGORY = "category"
const val COL_COVER_LAST_MODIFIED = "cover_last_modified"
// Not an actual value but computed when created
const val COMPUTED_COL_UNREAD_COUNT = "unread_count"
const val COMPUTED_COL_READ_COUNT = "read_count"
}

View File

@ -1,32 +0,0 @@
package eu.kanade.tachiyomi.data.database.tables
object TrackTable {
const val TABLE = "manga_sync"
const val COL_ID = "_id"
const val COL_MANGA_ID = "manga_id"
const val COL_SYNC_ID = "sync_id"
const val COL_MEDIA_ID = "remote_id"
const val COL_LIBRARY_ID = "library_id"
const val COL_TITLE = "title"
const val COL_LAST_CHAPTER_READ = "last_chapter_read"
const val COL_STATUS = "status"
const val COL_SCORE = "score"
const val COL_TOTAL_CHAPTERS = "total_chapters"
const val COL_TRACKING_URL = "remote_url"
const val COL_START_DATE = "start_date"
const val COL_FINISH_DATE = "finish_date"
}

View File

@ -5,7 +5,6 @@ import com.hippo.unifile.UniFile
import com.jakewharton.rxrelay.BehaviorRelay
import eu.kanade.domain.category.interactor.GetCategories
import eu.kanade.tachiyomi.R
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.download.model.Download
@ -34,7 +33,6 @@ import uy.kohesive.injekt.injectLazy
*/
class DownloadManager(
private val context: Context,
private val db: DatabaseHelper = Injekt.get(),
private val getCategories: GetCategories = Injekt.get(),
) {

View File

@ -14,18 +14,21 @@ import eu.kanade.domain.chapter.interactor.SyncChaptersWithSource
import eu.kanade.domain.chapter.interactor.SyncChaptersWithTrackServiceTwoWay
import eu.kanade.domain.chapter.model.toDbChapter
import eu.kanade.domain.manga.interactor.GetFavorites
import eu.kanade.domain.manga.interactor.GetLibraryManga
import eu.kanade.domain.manga.interactor.GetManga
import eu.kanade.domain.manga.interactor.GetMergedMangaForDownloading
import eu.kanade.domain.manga.interactor.InsertFlatMetadata
import eu.kanade.domain.manga.interactor.InsertManga
import eu.kanade.domain.manga.interactor.UpdateManga
import eu.kanade.domain.manga.model.toDbManga
import eu.kanade.domain.manga.model.toMangaInfo
import eu.kanade.domain.manga.model.toMangaUpdate
import eu.kanade.domain.track.interactor.GetTracks
import eu.kanade.domain.track.interactor.InsertTrack
import eu.kanade.domain.track.model.toDbTrack
import eu.kanade.domain.track.model.toDomainTrack
import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.data.cache.CoverCache
import eu.kanade.tachiyomi.data.database.DatabaseHelper
import eu.kanade.tachiyomi.data.database.models.Chapter
import eu.kanade.tachiyomi.data.database.models.LibraryManga
import eu.kanade.tachiyomi.data.database.models.Manga
@ -47,6 +50,7 @@ import eu.kanade.tachiyomi.data.track.TrackStatus
import eu.kanade.tachiyomi.source.SourceManager
import eu.kanade.tachiyomi.source.UnmeteredSource
import eu.kanade.tachiyomi.source.model.SManga
import eu.kanade.tachiyomi.source.model.toMangaInfo
import eu.kanade.tachiyomi.source.model.toSChapter
import eu.kanade.tachiyomi.source.model.toSManga
import eu.kanade.tachiyomi.source.online.all.MergedSource
@ -67,7 +71,6 @@ import exh.source.LIBRARY_UPDATE_EXCLUDED_SOURCES
import exh.source.MERGED_SOURCE_ID
import exh.source.isMdBasedSource
import exh.source.mangaDexSourceIds
import exh.util.executeOnIO
import exh.util.nullIfBlank
import kotlinx.coroutines.CoroutineExceptionHandler
import kotlinx.coroutines.CoroutineScope
@ -102,12 +105,12 @@ import eu.kanade.domain.manga.model.Manga as DomainManga
* destroyed.
*/
class LibraryUpdateService(
val db: DatabaseHelper = Injekt.get(),
val sourceManager: SourceManager = Injekt.get(),
val preferences: PreferencesHelper = Injekt.get(),
val downloadManager: DownloadManager = Injekt.get(),
val trackManager: TrackManager = Injekt.get(),
val coverCache: CoverCache = Injekt.get(),
private val getLibraryManga: GetLibraryManga = Injekt.get(),
private val getManga: GetManga = Injekt.get(),
private val updateManga: UpdateManga = Injekt.get(),
private val getChapterByMangaId: GetChapterByMangaId = Injekt.get(),
@ -119,6 +122,8 @@ class LibraryUpdateService(
// SY -->
private val getFavorites: GetFavorites = Injekt.get(),
private val insertFlatMetadata: InsertFlatMetadata = Injekt.get(),
private val insertManga: InsertManga = Injekt.get(),
private val getMergedMangaForDownloading: GetMergedMangaForDownloading = Injekt.get(),
// SY <--
) : Service() {
@ -301,7 +306,7 @@ class LibraryUpdateService(
* @param categoryId the ID of the category to update, or -1 if no category specified.
*/
fun addMangaToQueue(categoryId: Long, group: Int, groupExtra: String?) {
val libraryManga = db.getLibraryMangas().executeAsBlocking()
val libraryManga = runBlocking { getLibraryManga.await() }
// SY -->
val groupLibraryUpdateType = preferences.groupLibraryUpdateType().get()
// SY <--
@ -499,12 +504,12 @@ class LibraryUpdateService(
// may don't like it and they could ban the user.
// SY -->
if (manga.source == MERGED_SOURCE_ID) {
val downloadingManga = db.getMergedMangasForDownloading(manga.id!!).executeAsBlocking()
.associateBy { it.id!! }
val downloadingManga = runBlocking { getMergedMangaForDownloading.await(manga.id!!) }
.associateBy { it.id }
chapters.groupBy { it.manga_id }
.forEach {
downloadManager.downloadChapters(
downloadingManga[it.key] ?: return@forEach,
downloadingManga[it.key]?.toDbManga() ?: return@forEach,
chapters,
false,
)
@ -593,7 +598,14 @@ class LibraryUpdateService(
mangaWithNotif.prepUpdateCover(coverCache, sManga, true)
sManga.thumbnail_url?.let {
mangaWithNotif.thumbnail_url = it
db.insertManga(mangaWithNotif).executeAsBlocking()
try {
updateManga.await(
mangaWithNotif.toDomainManga()!!
.toMangaUpdate(),
)
} catch (e: Exception) {
logcat(LogPriority.ERROR) { "Manga don't exist anymore" }
}
}
} catch (e: Throwable) {
// Ignore errors and continue
@ -714,24 +726,29 @@ class LibraryUpdateService(
count++
notifier.showProgressNotification(listOf(networkManga), count, size)
var dbManga = db.getManga(networkManga.url, mangaDex.id)
.executeOnIO()
var dbManga = getManga.await(networkManga.url, mangaDex.id)
if (dbManga == null) {
dbManga = Manga.create(
val newManga = Manga.create(
networkManga.url,
networkManga.title,
mangaDex.id,
)
dbManga.date_added = System.currentTimeMillis()
newManga.favorite = true
newManga.date_added = System.currentTimeMillis()
newManga.id = -1
val result = runBlocking {
val id = insertManga.await(newManga.toDomainManga()!!)
getManga.await(id!!)
}
dbManga = result ?: return
} else if (!dbManga.favorite) {
updateManga.awaitUpdateFavorite(dbManga.id, true)
}
dbManga.copyFrom(networkManga)
dbManga.favorite = true
val id = db.insertManga(dbManga).executeOnIO().insertedId()
if (id != null) {
metadata.mangaId = id
insertFlatMetadata.await(metadata)
}
updateManga.awaitUpdateFromSource(dbManga, networkManga.toMangaInfo(), true)
metadata.mangaId = dbManga.id
insertFlatMetadata.await(metadata)
}
notifier.cancelProgressNotification()

View File

@ -4,7 +4,11 @@ import eu.kanade.domain.category.interactor.GetCategories
import eu.kanade.domain.chapter.interactor.GetMergedChapterByMangaId
import eu.kanade.domain.chapter.interactor.SyncChaptersWithSource
import eu.kanade.domain.chapter.model.toDbChapter
import eu.kanade.tachiyomi.data.database.DatabaseHelper
import eu.kanade.domain.manga.interactor.GetManga
import eu.kanade.domain.manga.interactor.GetMergedReferencesById
import eu.kanade.domain.manga.interactor.InsertManga
import eu.kanade.domain.manga.model.toDbManga
import eu.kanade.domain.manga.model.toMangaInfo
import eu.kanade.tachiyomi.data.database.models.Chapter
import eu.kanade.tachiyomi.data.database.models.Manga
import eu.kanade.tachiyomi.data.database.models.toDomainManga
@ -22,7 +26,6 @@ import eu.kanade.tachiyomi.source.model.toSManga
import eu.kanade.tachiyomi.source.online.HttpSource
import eu.kanade.tachiyomi.util.lang.withIOContext
import eu.kanade.tachiyomi.util.shouldDownloadNewChapters
import exh.log.xLogW
import exh.merged.sql.models.MergedMangaReference
import exh.source.MERGED_SOURCE_ID
import kotlinx.coroutines.CancellationException
@ -42,8 +45,10 @@ import eu.kanade.domain.chapter.model.Chapter as DomainChapter
import eu.kanade.domain.manga.model.Manga as DomainManga
class MergedSource : HttpSource() {
private val db: DatabaseHelper by injectLazy()
private val getManga: GetManga by injectLazy()
private val getMergedReferencesById: GetMergedReferencesById by injectLazy()
private val getMergedChaptersByMangaId: GetMergedChapterByMangaId by injectLazy()
private val insertManga: InsertManga by injectLazy()
private val getCategories: GetCategories by injectLazy()
private val sourceManager: SourceManager by injectLazy()
private val downloadManager: DownloadManager by injectLazy()
@ -74,9 +79,8 @@ class MergedSource : HttpSource() {
override suspend fun getMangaDetails(manga: MangaInfo): MangaInfo {
return withIOContext {
val mergedManga = db.getManga(manga.key, id).executeAsBlocking()
?: throw Exception("merged manga not in db")
val mangaReferences = db.getMergedMangaReferences(mergedManga.id!!).executeAsBlocking()
val mergedManga = getManga.await(manga.key, id) ?: throw Exception("merged manga not in db")
val mangaReferences = getMergedReferencesById.await(mergedManga.id)
.apply {
if (isEmpty()) {
throw IllegalArgumentException(
@ -93,7 +97,7 @@ class MergedSource : HttpSource() {
val mangaInfoReference = mangaReferences.firstOrNull { it.isInfoManga }
?: mangaReferences.firstOrNull { it.mangaId != it.mergeId }
val dbManga = mangaInfoReference?.run {
db.getManga(mangaUrl, mangaSourceId).executeAsBlocking()?.toMangaInfo()
getManga.await(mangaUrl, mangaSourceId)?.toMangaInfo()
}
(dbManga ?: mergedManga.toMangaInfo()).copy(
key = manga.key,
@ -102,8 +106,8 @@ class MergedSource : HttpSource() {
}
// TODO more chapter dedupe
fun transformMergedChapters(mangaId: Long, chapterList: List<DomainChapter>, editScanlators: Boolean, dedupe: Boolean): List<DomainChapter> {
val mangaReferences = db.getMergedMangaReferences(mangaId).executeAsBlocking()
suspend fun transformMergedChapters(mangaId: Long, chapterList: List<DomainChapter>, editScanlators: Boolean, dedupe: Boolean): List<DomainChapter> {
val mangaReferences = getMergedReferencesById.await(mangaId)
val chapters = if (editScanlators) {
val sources = mangaReferences.map { sourceManager.getOrStub(it.mangaSourceId) to it.mangaId }
chapterList.map { chapter ->
@ -127,7 +131,11 @@ class MergedSource : HttpSource() {
}
fun getChaptersAsBlocking(mangaId: Long, editScanlators: Boolean = false, dedupe: Boolean = true): List<DomainChapter> {
return transformMergedChapters(mangaId, runBlocking { getMergedChaptersByMangaId.await(mangaId) }, editScanlators, dedupe)
return runBlocking { getChapters(mangaId, editScanlators, dedupe) }
}
suspend fun getChapters(mangaId: Long, editScanlators: Boolean = false, dedupe: Boolean = true): List<DomainChapter> {
return transformMergedChapters(mangaId, getMergedChaptersByMangaId.await(mangaId), editScanlators, dedupe)
}
private fun dedupeChapterList(mangaReferences: List<MergedMangaReference>, chapterList: List<DomainChapter>): List<DomainChapter> {
@ -165,7 +173,7 @@ class MergedSource : HttpSource() {
suspend fun fetchChaptersAndSync(manga: DomainManga, downloadChapters: Boolean = true): Pair<List<DomainChapter>, List<DomainChapter>> {
val syncChaptersWithSource = Injekt.get<SyncChaptersWithSource>()
val mangaReferences = db.getMergedMangaReferences(manga.id).executeAsBlocking()
val mangaReferences = getMergedReferencesById.await(manga.id)
if (mangaReferences.isEmpty()) {
throw IllegalArgumentException("Manga references are empty, chapters unavailable, merge is likely corrupted")
}
@ -183,15 +191,15 @@ class MergedSource : HttpSource() {
values.map {
try {
val (source, loadedManga, reference) =
it.load(db, sourceManager)
it.load(sourceManager, getManga, insertManga)
if (loadedManga != null && reference.getChapterUpdates) {
val chapterList = source.getChapterList(loadedManga.toMangaInfo())
.map(ChapterInfo::toSChapter)
val results =
syncChaptersWithSource.await(chapterList, loadedManga.toDomainManga()!!, source)
syncChaptersWithSource.await(chapterList, loadedManga, source)
if (ifDownloadNewChapters && reference.downloadChapters) {
downloadManager.downloadChapters(
loadedManga,
loadedManga.toDbManga(),
results.first.map(DomainChapter::toDbChapter),
)
}
@ -218,26 +226,25 @@ class MergedSource : HttpSource() {
}
}
suspend fun MergedMangaReference.load(db: DatabaseHelper, sourceManager: SourceManager): LoadedMangaSource {
var manga = db.getManga(mangaUrl, mangaSourceId).executeAsBlocking()
suspend fun MergedMangaReference.load(sourceManager: SourceManager, getManga: GetManga, insertManga: InsertManga): LoadedMangaSource {
var manga = getManga.await(mangaUrl, mangaSourceId)
val source = sourceManager.getOrStub(manga?.source ?: mangaSourceId)
if (manga == null) {
manga = Manga.create(mangaSourceId).apply {
val newManga = Manga.create(mangaSourceId).apply {
url = mangaUrl
}
manga.copyFrom(source.getMangaDetails(manga.toMangaInfo()).toSManga())
try {
manga.id = db.insertManga(manga).executeAsBlocking().insertedId()
mangaId = manga.id
db.insertNewMergedMangaId(this).executeAsBlocking()
} catch (e: Exception) {
xLogW("Error inserting merged manga id", e)
newManga.copyFrom(source.getMangaDetails(newManga.toMangaInfo()).toSManga())
newManga.id = -1
val result = run {
val id = insertManga.await(newManga.toDomainManga()!!)
getManga.await(id!!)
}
manga = result
}
return LoadedMangaSource(source, manga, this)
}
data class LoadedMangaSource(val source: Source, val manga: Manga?, val reference: MergedMangaReference)
data class LoadedMangaSource(val source: Source, val manga: DomainManga?, val reference: MergedMangaReference)
override val lang = "all"
override val supportsLatest = false

View File

@ -4,8 +4,13 @@ import android.os.Bundle
import eu.kanade.data.DatabaseHandler
import eu.kanade.data.exh.feedSavedSearchMapper
import eu.kanade.data.exh.savedSearchMapper
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.interactor.UpdateManga
import eu.kanade.domain.manga.model.toDbManga
import eu.kanade.domain.manga.model.toMangaUpdate
import eu.kanade.tachiyomi.data.database.models.Manga
import eu.kanade.tachiyomi.data.database.models.toDomainManga
import eu.kanade.tachiyomi.data.database.models.toMangaInfo
import eu.kanade.tachiyomi.data.preference.PreferencesHelper
import eu.kanade.tachiyomi.source.CatalogueSource
@ -23,6 +28,7 @@ import exh.savedsearches.models.FeedSavedSearch
import exh.savedsearches.models.SavedSearch
import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.onEach
import kotlinx.coroutines.runBlocking
import kotlinx.serialization.decodeFromString
import kotlinx.serialization.json.Json
import logcat.LogPriority
@ -40,14 +46,16 @@ import xyz.nulldev.ts.api.http.serializer.FilterSerializer
* Function calls should be done from here. UI calls should be done from the controller.
*
* @param sourceManager manages the different sources.
* @param database manages the database calls.
* @param handler manages the database calls.
* @param preferences manages the preference calls.
*/
open class FeedPresenter(
val sourceManager: SourceManager = Injekt.get(),
val database: DatabaseHandler = Injekt.get(),
val db: DatabaseHelper = Injekt.get(),
val handler: DatabaseHandler = Injekt.get(),
val preferences: PreferencesHelper = Injekt.get(),
private val getManga: GetManga = Injekt.get(),
private val insertManga: InsertManga = Injekt.get(),
private val updateManga: UpdateManga = Injekt.get(),
) : BasePresenter<FeedController>() {
/**
@ -68,7 +76,7 @@ open class FeedPresenter(
override fun onCreate(savedState: Bundle?) {
super.onCreate(savedState)
database.subscribeToList { feed_saved_searchQueries.selectAllGlobal() }
handler.subscribeToList { feed_saved_searchQueries.selectAllGlobal() }
.onEach {
getFeed()
}
@ -82,7 +90,7 @@ open class FeedPresenter(
}
suspend fun hasTooManyFeeds(): Boolean {
return database.awaitOne { feed_saved_searchQueries.countGlobal() } > 10
return handler.awaitOne { feed_saved_searchQueries.countGlobal() } > 10
}
fun getEnabledSources(): List<CatalogueSource> {
@ -97,12 +105,12 @@ open class FeedPresenter(
}
suspend fun getSourceSavedSearches(source: CatalogueSource): List<SavedSearch> {
return database.awaitList { saved_searchQueries.selectBySource(source.id, savedSearchMapper) }
return handler.awaitList { saved_searchQueries.selectBySource(source.id, savedSearchMapper) }
}
fun createFeed(source: CatalogueSource, savedSearch: SavedSearch?) {
launchIO {
database.await {
handler.await {
feed_saved_searchQueries.insertFeedSavedSearch(
_id = null,
source = source.id,
@ -115,17 +123,17 @@ open class FeedPresenter(
fun deleteFeed(feed: FeedSavedSearch) {
launchIO {
database.await {
handler.await {
feed_saved_searchQueries.deleteById(feed.id ?: return@await)
}
}
}
private suspend fun getSourcesToGetFeed(): List<Pair<FeedSavedSearch, SavedSearch?>> {
val savedSearches = database.awaitList {
val savedSearches = handler.awaitList {
feed_saved_searchQueries.selectGlobalFeedSavedSearch(savedSearchMapper)
}.associateBy { it.id }
return database.awaitList { feed_saved_searchQueries.selectAllGlobal(feedSavedSearchMapper) }
return handler.awaitList { feed_saved_searchQueries.selectAllGlobal(feedSavedSearchMapper) }
.map { it to savedSearches[it.savedSearch] }
}
@ -263,7 +271,7 @@ open class FeedPresenter(
val networkManga = source.getMangaDetails(manga.toMangaInfo())
manga.copyFrom(networkManga.toSManga())
manga.initialized = true
db.insertManga(manga).executeAsBlocking()
updateManga.await(manga.toDomainManga()!!.toMangaUpdate())
manga
}
.onErrorResumeNext { Observable.just(manga) }
@ -276,15 +284,22 @@ open class FeedPresenter(
* @param sManga the manga from the source.
* @return a manga from the database.
*/
protected open fun networkToLocalManga(sManga: SManga, sourceId: Long): Manga {
var localManga = db.getManga(sManga.url, sourceId).executeAsBlocking()
private fun networkToLocalManga(sManga: SManga, sourceId: Long): Manga {
var localManga = runBlocking { 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).executeAsBlocking()
newManga.id = result.insertedId()
localManga = newManga
newManga.id = -1
val result = runBlocking {
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()!!
}
}

View File

@ -5,8 +5,9 @@ import android.view.Menu
import android.view.MenuInflater
import androidx.appcompat.widget.SearchView
import androidx.core.os.bundleOf
import eu.kanade.domain.manga.interactor.GetManga
import eu.kanade.domain.manga.model.toDbManga
import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.data.database.DatabaseHelper
import eu.kanade.tachiyomi.data.database.models.Manga
import eu.kanade.tachiyomi.source.CatalogueSource
import eu.kanade.tachiyomi.source.SourceManager
@ -17,6 +18,7 @@ import eu.kanade.tachiyomi.ui.browse.source.globalsearch.GlobalSearchPresenter
import kotlinx.coroutines.flow.filter
import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.onEach
import kotlinx.coroutines.runBlocking
import reactivecircus.flowbinding.appcompat.QueryTextEvent
import reactivecircus.flowbinding.appcompat.queryTextEvents
import uy.kohesive.injekt.Injekt
@ -34,7 +36,11 @@ class SearchController(
) {
constructor(targetController: MigrationListController?, mangaId: Long, sources: LongArray) :
this(
Injekt.get<DatabaseHelper>().getManga(mangaId).executeAsBlocking(),
runBlocking {
Injekt.get<GetManga>()
.await(mangaId)
?.toDbManga()
},
sources.map { Injekt.get<SourceManager>().getOrStub(it) }.filterIsInstance<CatalogueSource>(),
) {
this.targetController = targetController

View File

@ -9,11 +9,14 @@ import eu.kanade.domain.category.interactor.SetMangaCategories
import eu.kanade.domain.chapter.interactor.GetChapterByMangaId
import eu.kanade.domain.chapter.interactor.SyncChaptersWithTrackServiceTwoWay
import eu.kanade.domain.manga.interactor.GetDuplicateLibraryManga
import eu.kanade.domain.manga.interactor.GetManga
import eu.kanade.domain.manga.interactor.InsertManga
import eu.kanade.domain.manga.interactor.UpdateManga
import eu.kanade.domain.manga.model.toDbManga
import eu.kanade.domain.manga.model.toMangaUpdate
import eu.kanade.domain.track.interactor.InsertTrack
import eu.kanade.domain.track.model.toDomainTrack
import eu.kanade.tachiyomi.data.cache.CoverCache
import eu.kanade.tachiyomi.data.database.DatabaseHelper
import eu.kanade.tachiyomi.data.database.models.Manga
import eu.kanade.tachiyomi.data.database.models.toDomainManga
import eu.kanade.tachiyomi.data.database.models.toMangaInfo
@ -87,14 +90,16 @@ open class BrowseSourcePresenter(
private val savedSearch: Long? = null,
// SY <--
private val sourceManager: SourceManager = Injekt.get(),
private val db: DatabaseHelper = Injekt.get(),
private val database: DatabaseHandler = Injekt.get(),
private val prefs: PreferencesHelper = Injekt.get(),
private val coverCache: CoverCache = Injekt.get(),
private val getManga: GetManga = Injekt.get(),
private val getDuplicateLibraryManga: GetDuplicateLibraryManga = Injekt.get(),
private val getCategories: GetCategories = Injekt.get(),
private val getChapterByMangaId: GetChapterByMangaId = Injekt.get(),
private val setMangaCategories: SetMangaCategories = Injekt.get(),
private val insertManga: InsertManga = Injekt.get(),
private val updateManga: UpdateManga = Injekt.get(),
private val insertTrack: InsertTrack = Injekt.get(),
private val syncChaptersWithTrackServiceTwoWay: SyncChaptersWithTrackServiceTwoWay = Injekt.get(),
) : BasePresenter<BrowseSourceController>() {
@ -278,19 +283,22 @@ open class BrowseSourcePresenter(
* @return a manga from the database.
*/
private fun networkToLocalManga(sManga: SManga, sourceId: Long): Manga {
var localManga = db.getManga(sManga.url, sourceId).executeAsBlocking()
var localManga = runBlocking { 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).executeAsBlocking()
newManga.id = result.insertedId()
localManga = newManga
newManga.id = -1
val result = runBlocking {
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.title = sManga.title
localManga = localManga.copy(ogTitle = sManga.title)
}
return localManga
return localManga?.toDbManga()!!
}
/**
@ -325,7 +333,11 @@ open class BrowseSourcePresenter(
val networkManga = source.getMangaDetails(manga.toMangaInfo())
manga.copyFrom(networkManga.toSManga())
manga.initialized = true
db.insertManga(manga).executeAsBlocking()
updateManga.await(
manga
.toDomainManga()
?.toMangaUpdate()!!,
)
} catch (e: Exception) {
logcat(LogPriority.ERROR, e)
}
@ -352,7 +364,13 @@ open class BrowseSourcePresenter(
autoAddTrack(manga)
}
db.insertManga(manga).executeAsBlocking()
runBlocking {
updateManga.await(
manga
.toDomainManga()
?.toMangaUpdate()!!,
)
}
}
private fun autoAddTrack(manga: Manga) {

View File

@ -5,8 +5,13 @@ import eu.davidea.flexibleadapter.items.IFlexible
import eu.kanade.data.DatabaseHandler
import eu.kanade.data.exh.feedSavedSearchMapper
import eu.kanade.data.exh.savedSearchMapper
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.interactor.UpdateManga
import eu.kanade.domain.manga.model.toDbManga
import eu.kanade.domain.manga.model.toMangaUpdate
import eu.kanade.tachiyomi.data.database.models.Manga
import eu.kanade.tachiyomi.data.database.models.toDomainManga
import eu.kanade.tachiyomi.data.database.models.toMangaInfo
import eu.kanade.tachiyomi.data.preference.PreferencesHelper
import eu.kanade.tachiyomi.source.CatalogueSource
@ -28,6 +33,7 @@ import exh.savedsearches.models.SavedSearch
import kotlinx.coroutines.CancellationException
import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.onEach
import kotlinx.coroutines.runBlocking
import kotlinx.serialization.decodeFromString
import kotlinx.serialization.json.Json
import kotlinx.serialization.json.JsonArray
@ -40,7 +46,6 @@ import rx.subjects.PublishSubject
import uy.kohesive.injekt.Injekt
import uy.kohesive.injekt.api.get
import xyz.nulldev.ts.api.http.serializer.FilterSerializer
import java.lang.RuntimeException
sealed class SourceFeed {
object Latest : SourceFeed()
@ -53,14 +58,16 @@ sealed class SourceFeed {
* Function calls should be done from here. UI calls should be done from the controller.
*
* @param source the source.
* @param database manages the database calls.
* @param handler manages the database calls.
* @param preferences manages the preference calls.
*/
open class SourceFeedPresenter(
val source: CatalogueSource,
val database: DatabaseHandler = Injekt.get(),
val db: DatabaseHelper = Injekt.get(),
val handler: DatabaseHandler = Injekt.get(),
val preferences: PreferencesHelper = Injekt.get(),
private val getManga: GetManga = Injekt.get(),
private val insertManga: InsertManga = Injekt.get(),
private val updateManga: UpdateManga = Injekt.get(),
) : BasePresenter<SourceFeedController>() {
/**
@ -98,7 +105,7 @@ open class SourceFeedPresenter(
sourceFilters = source.getFilterList()
database.subscribeToList { feed_saved_searchQueries.selectSourceFeedSavedSearch(source.id, savedSearchMapper) }
handler.subscribeToList { feed_saved_searchQueries.selectSourceFeedSavedSearch(source.id, savedSearchMapper) }
.onEach {
getFeed()
}
@ -113,19 +120,19 @@ open class SourceFeedPresenter(
suspend fun hasTooManyFeeds(): Boolean {
return withIOContext {
database.awaitList {
handler.awaitList {
feed_saved_searchQueries.selectSourceFeedSavedSearch(source.id)
}.size > 10
}
}
suspend fun getSourceSavedSearches(): List<SavedSearch> {
return database.awaitList { saved_searchQueries.selectBySource(source.id, savedSearchMapper) }
return handler.awaitList { saved_searchQueries.selectBySource(source.id, savedSearchMapper) }
}
fun createFeed(savedSearchId: Long) {
launchIO {
database.await {
handler.await {
feed_saved_searchQueries.insertFeedSavedSearch(
_id = null,
source = source.id,
@ -138,12 +145,12 @@ open class SourceFeedPresenter(
fun deleteFeed(feed: FeedSavedSearch) {
launchIO {
database.await { feed_saved_searchQueries.deleteById(feed.id ?: return@await) }
handler.await { feed_saved_searchQueries.deleteById(feed.id ?: return@await) }
}
}
private suspend fun getSourcesToGetFeed(): List<SourceFeed> {
val savedSearches = database.awaitList { feed_saved_searchQueries.selectSourceFeedSavedSearch(source.id, savedSearchMapper) }
val savedSearches = handler.awaitList { feed_saved_searchQueries.selectSourceFeedSavedSearch(source.id, savedSearchMapper) }
.associateBy { it.id!! }
return listOfNotNull(
@ -151,7 +158,7 @@ open class SourceFeedPresenter(
SourceFeed.Latest
} else null,
SourceFeed.Browse,
) + database.awaitList { feed_saved_searchQueries.selectBySource(source.id, feedSavedSearchMapper) }
) + handler.awaitList { feed_saved_searchQueries.selectBySource(source.id, feedSavedSearchMapper) }
.map { SourceFeed.SourceSavedSearch(it, savedSearches[it.savedSearch]!!) }
}
@ -284,7 +291,7 @@ open class SourceFeedPresenter(
val networkManga = source.getMangaDetails(manga.toMangaInfo())
manga.copyFrom(networkManga.toSManga())
manga.initialized = true
db.insertManga(manga).executeAsBlocking()
updateManga.await(manga.toDomainManga()!!.toMangaUpdate())
manga
}
.onErrorResumeNext { Observable.just(manga) }
@ -297,21 +304,28 @@ open class SourceFeedPresenter(
* @param sManga the manga from the source.
* @return a manga from the database.
*/
protected open fun networkToLocalManga(sManga: SManga, sourceId: Long): Manga {
var localManga = db.getManga(sManga.url, sourceId).executeAsBlocking()
private fun networkToLocalManga(sManga: SManga, sourceId: Long): Manga {
var localManga = runBlocking { 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).executeAsBlocking()
newManga.id = result.insertedId()
localManga = newManga
newManga.id = -1
val result = runBlocking {
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()!!
}
suspend fun loadSearch(searchId: Long): EXHSavedSearch? {
return withIOContext {
val search = database.awaitOneOrNull {
val search = handler.awaitOneOrNull {
saved_searchQueries.selectById(searchId, savedSearchMapper)
} ?: return@withIOContext null
EXHSavedSearch(
@ -334,7 +348,7 @@ open class SourceFeedPresenter(
suspend fun loadSearches(): List<EXHSavedSearch> {
return withIOContext {
database.awaitList { saved_searchQueries.selectBySource(source.id, savedSearchMapper) }.map {
handler.awaitList { saved_searchQueries.selectBySource(source.id, savedSearchMapper) }.map {
val filtersJson = it.filtersJson ?: return@map EXHSavedSearch(
id = it.id!!,
name = it.name,

View File

@ -1,8 +1,13 @@
package eu.kanade.tachiyomi.ui.browse.source.globalsearch
import android.os.Bundle
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.interactor.UpdateManga
import eu.kanade.domain.manga.model.toDbManga
import eu.kanade.domain.manga.model.toMangaUpdate
import eu.kanade.tachiyomi.data.database.models.Manga
import eu.kanade.tachiyomi.data.database.models.toDomainManga
import eu.kanade.tachiyomi.data.database.models.toMangaInfo
import eu.kanade.tachiyomi.data.preference.PreferencesHelper
import eu.kanade.tachiyomi.extension.ExtensionManager
@ -16,6 +21,7 @@ import eu.kanade.tachiyomi.ui.base.presenter.BasePresenter
import eu.kanade.tachiyomi.ui.browse.source.browse.BrowseSourcePresenter
import eu.kanade.tachiyomi.util.lang.runAsObservable
import eu.kanade.tachiyomi.util.system.logcat
import kotlinx.coroutines.runBlocking
import logcat.LogPriority
import rx.Observable
import rx.Subscription
@ -39,8 +45,10 @@ open class GlobalSearchPresenter(
private val initialExtensionFilter: String? = null,
private val sourcesToUse: List<CatalogueSource>? = null,
val sourceManager: SourceManager = Injekt.get(),
val db: DatabaseHelper = Injekt.get(),
val preferences: PreferencesHelper = Injekt.get(),
private val getManga: GetManga = Injekt.get(),
private val insertManga: InsertManga = Injekt.get(),
private val updateManga: UpdateManga = Injekt.get(),
) : BasePresenter<GlobalSearchController>() {
/**
@ -250,7 +258,7 @@ open class GlobalSearchPresenter(
val networkManga = source.getMangaDetails(manga.toMangaInfo())
manga.copyFrom(networkManga.toSManga())
manga.initialized = true
db.insertManga(manga).executeAsBlocking()
runBlocking { updateManga.await(manga.toDomainManga()!!.toMangaUpdate()) }
return manga
}
@ -262,18 +270,21 @@ open class GlobalSearchPresenter(
* @return a manga from the database.
*/
protected open fun networkToLocalManga(sManga: SManga, sourceId: Long): Manga {
var localManga = db.getManga(sManga.url, sourceId).executeAsBlocking()
var localManga = runBlocking { 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).executeAsBlocking()
newManga.id = result.insertedId()
localManga = newManga
newManga.id = -1
val result = runBlocking {
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.title = sManga.title
localManga = localManga.copy(ogTitle = sManga.title)
}
return localManga
return localManga!!.toDbManga()
}
}

View File

@ -1,7 +1,6 @@
package eu.kanade.tachiyomi.ui.category.sources
import android.os.Bundle
import eu.kanade.tachiyomi.data.database.DatabaseHelper
import eu.kanade.tachiyomi.data.preference.PreferencesHelper
import eu.kanade.tachiyomi.ui.base.presenter.BasePresenter
import kotlinx.coroutines.flow.launchIn
@ -14,9 +13,7 @@ import uy.kohesive.injekt.api.get
/**
* Presenter of [SourceCategoryController]. Used to manage the categories of the library.
*/
class SourceCategoryPresenter(
private val db: DatabaseHelper = Injekt.get(),
) : BasePresenter<SourceCategoryController>() {
class SourceCategoryPresenter : BasePresenter<SourceCategoryController>() {
/**
* List containing categories.

View File

@ -3,7 +3,6 @@ package eu.kanade.tachiyomi.ui.library
import android.os.Bundle
import com.jakewharton.rxrelay.BehaviorRelay
import eu.kanade.core.util.asObservable
import eu.kanade.data.AndroidDatabaseHandler
import eu.kanade.data.DatabaseHandler
import eu.kanade.domain.category.interactor.GetCategories
import eu.kanade.domain.category.interactor.SetMangaCategories
@ -13,6 +12,7 @@ import eu.kanade.domain.chapter.interactor.GetMergedChapterByMangaId
import eu.kanade.domain.chapter.interactor.UpdateChapter
import eu.kanade.domain.chapter.model.ChapterUpdate
import eu.kanade.domain.chapter.model.toDbChapter
import eu.kanade.domain.manga.interactor.GetLibraryManga
import eu.kanade.domain.manga.interactor.GetMergedMangaById
import eu.kanade.domain.manga.interactor.UpdateManga
import eu.kanade.domain.manga.model.Manga
@ -74,6 +74,7 @@ typealias LibraryMap = Map<Long, List<LibraryItem>>
*/
class LibraryPresenter(
private val handler: DatabaseHandler = Injekt.get(),
private val getLibraryManga: GetLibraryManga = Injekt.get(),
private val getTracks: GetTracks = Injekt.get(),
private val getCategories: GetCategories = Injekt.get(),
private val getChapterByMangaId: GetChapterByMangaId = Injekt.get(),
@ -561,39 +562,7 @@ class LibraryPresenter(
val defaultLibraryDisplayMode = preferences.libraryDisplayMode()
val shouldSetFromCategory = preferences.categorizedDisplaySettings()
// TODO: Move this to domain/data layer
return handler
.subscribeToList {
// SY -->
(handler as AndroidDatabaseHandler).getLibraryQuery()
/*mangasQueries.getLibrary { _id: Long, source: Long, url: String, artist: String?, author: String?, description: String?, genre: List<String>?, title: String, status: Long, thumbnail_url: String?, favorite: Boolean, last_update: Long?, next_update: Long?, initialized: Boolean, viewer: Long, chapter_flags: Long, cover_last_modified: Long, date_added: Long, filteredScanlators: List<String>?, unread_count: Long, read_count: Long, category: Long ->
LibraryManga().apply {
this.id = _id
this.source = source
this.url = url
this.artist = artist
this.author = author
this.description = description
this.genre = genre?.joinToString()
this.title = title
this.status = status.toInt()
this.thumbnail_url = thumbnail_url
this.favorite = favorite
this.last_update = last_update ?: 0
this.initialized = initialized
this.viewer_flags = viewer.toInt()
this.chapter_flags = chapter_flags.toInt()
this.cover_last_modified = cover_last_modified
this.date_added = date_added
this.filtered_scanlators = filteredScanlators?.let(listOfStringsAndAdapter::encode)
this.unreadCount = unread_count.toInt()
this.readCount = read_count.toInt()
this.category = category.toInt()
}
}*/
// SY <--
}
.asObservable()
return getLibraryManga.subscribe().asObservable()
.map { list ->
list.map { libraryManga ->
// Display mode based on user preference: take it from global library setting or category

View File

@ -237,7 +237,7 @@ class MangaController :
private fun openMergedSettingsDialog() {
EditMergedSettingsDialog(
this,
presenter.manga!!.toDbManga(),
presenter.manga ?: return,
).showDialog(router)
}

View File

@ -12,16 +12,22 @@ import eu.kanade.domain.chapter.interactor.SyncChaptersWithTrackServiceTwoWay
import eu.kanade.domain.chapter.interactor.UpdateChapter
import eu.kanade.domain.chapter.model.ChapterUpdate
import eu.kanade.domain.chapter.model.toDbChapter
import eu.kanade.domain.manga.interactor.DeleteByMergeId
import eu.kanade.domain.manga.interactor.DeleteMangaById
import eu.kanade.domain.manga.interactor.GetDuplicateLibraryManga
import eu.kanade.domain.manga.interactor.GetFlatMetadataById
import eu.kanade.domain.manga.interactor.GetManga
import eu.kanade.domain.manga.interactor.GetMangaWithChapters
import eu.kanade.domain.manga.interactor.GetMergedMangaById
import eu.kanade.domain.manga.interactor.GetMergedReferencesById
import eu.kanade.domain.manga.interactor.InsertManga
import eu.kanade.domain.manga.interactor.InsertMergedReference
import eu.kanade.domain.manga.interactor.SetMangaChapterFlags
import eu.kanade.domain.manga.interactor.SetMangaFilteredScanlators
import eu.kanade.domain.manga.interactor.UpdateManga
import eu.kanade.domain.manga.interactor.UpdateMergedSettings
import eu.kanade.domain.manga.model.MangaUpdate
import eu.kanade.domain.manga.model.MergeMangaSettingsUpdate
import eu.kanade.domain.manga.model.TriStateFilter
import eu.kanade.domain.manga.model.isLocal
import eu.kanade.domain.manga.model.toDbManga
@ -32,7 +38,6 @@ import eu.kanade.domain.track.interactor.InsertTrack
import eu.kanade.domain.track.model.toDbTrack
import eu.kanade.domain.track.model.toDomainTrack
import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.data.database.DatabaseHelper
import eu.kanade.tachiyomi.data.database.models.Chapter
import eu.kanade.tachiyomi.data.database.models.Track
import eu.kanade.tachiyomi.data.database.models.toDomainChapter
@ -127,6 +132,11 @@ class MangaPresenter(
private val getMergedChapterByMangaId: GetMergedChapterByMangaId = Injekt.get(),
private val getMergedMangaById: GetMergedMangaById = Injekt.get(),
private val getMergedReferencesById: GetMergedReferencesById = Injekt.get(),
private val insertMergedReference: InsertMergedReference = Injekt.get(),
private val updateMergedSettings: UpdateMergedSettings = Injekt.get(),
private val insertManga: InsertManga = Injekt.get(),
private val deleteMangaById: DeleteMangaById = Injekt.get(),
private val deleteByMergeId: DeleteByMergeId = Injekt.get(),
private val getFlatMetadata: GetFlatMetadataById = Injekt.get(),
// SY <--
private val getDuplicateLibraryManga: GetDuplicateLibraryManga = Injekt.get(),
@ -223,7 +233,7 @@ class MangaPresenter(
presenterScope.launchIO {
if (!getMangaAndChapters.awaitManga(mangaId).favorite) {
ChapterSettingsHelper.applySettingDefaults(mangaId, setMangaChapterFlags)
ChapterSettingsHelper.applySettingDefaults(mangaId)
}
getMangaAndChapters.subscribe(mangaId)
@ -431,8 +441,6 @@ class MangaPresenter(
}
suspend fun smartSearchMerge(context: Context, manga: DomainManga, originalMangaId: Long): DomainManga {
val db = Injekt.get<DatabaseHelper>()
val originalManga = getManga.await(originalMangaId)
?: throw IllegalArgumentException(context.getString(R.string.merge_unknown_manga, originalMangaId))
if (originalManga.source == MERGED_SOURCE_ID) {
@ -474,7 +482,7 @@ class MangaPresenter(
}
// todo
db.insertMergedMangas(mangaReferences).executeAsBlocking()
insertMergedReference.awaitAll(mangaReferences)
return originalManga
} else {
@ -494,8 +502,10 @@ class MangaPresenter(
throw IllegalArgumentException(context.getString(R.string.merge_duplicate))
} else if (!existingManga.favorite) {
withContext(NonCancellable) {
db.deleteManga(existingManga!!.toDbManga()).executeAsBlocking()
db.deleteMangaForMergedManga(existingManga!!.id).executeAsBlocking()
existingManga?.id?.let {
deleteByMergeId.await(it)
deleteMangaById.await(it)
}
}
}
existingManga = getManga.await(mergedManga.url, mergedManga.source)
@ -503,13 +513,13 @@ class MangaPresenter(
// Reload chapters immediately
mergedManga.initialized = false
val newId = db.insertManga(mergedManga).executeAsBlocking().insertedId()
if (newId != null) mergedManga.id = newId
mergedManga.id = -1
val newId = insertManga.await(mergedManga.toDomainManga()!!)
mergedManga.id = newId ?: throw NullPointerException("Invalid new manga id")
getCategories.await(originalMangaId)
.let {
setMangaCategories.await(mergedManga.id!!, it.map { it.id })
setMangaCategories.await(newId, it.map { it.id })
}
val originalMangaReference = MergedMangaReference(
@ -554,7 +564,7 @@ class MangaPresenter(
mangaSourceId = MERGED_SOURCE_ID,
)
db.insertMergedMangas(listOf(originalMangaReference, newMangaReference, mergedMangaReference)).executeAsBlocking()
insertMergedReference.awaitAll(listOf(originalMangaReference, newMangaReference, mergedMangaReference))
return mergedManga.toDomainManga()!!
}
@ -562,14 +572,21 @@ class MangaPresenter(
// Note that if the manga are merged in a different order, this won't trigger, but I don't care lol
}
fun updateMergeSettings(mergeReference: MergedMangaReference?, mergedMangaReferences: List<MergedMangaReference>) {
fun updateMergeSettings(mergedMangaReferences: List<MergedMangaReference>) {
launchIO {
// todo
val db = Injekt.get<DatabaseHelper>()
mergeReference?.let {
db.updateMergeMangaSettings(it).executeAsBlocking()
if (mergedMangaReferences.isNotEmpty()) {
updateMergedSettings.awaitAll(
mergedMangaReferences.map {
MergeMangaSettingsUpdate(
it.id!!,
it.isInfoManga,
it.getChapterUpdates,
it.chapterPriority,
it.downloadChapters,
)
},
)
}
if (mergedMangaReferences.isNotEmpty()) db.updateMergedMangaSettings(mergedMangaReferences).executeAsBlocking()
}
}

View File

@ -5,8 +5,8 @@ import androidx.recyclerview.widget.RecyclerView
import eu.davidea.flexibleadapter.FlexibleAdapter
import eu.davidea.flexibleadapter.items.AbstractFlexibleItem
import eu.davidea.flexibleadapter.items.IFlexible
import eu.kanade.domain.manga.model.Manga
import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.data.database.models.Manga
import eu.kanade.tachiyomi.databinding.EditMergedSettingsItemBinding
import exh.merged.sql.models.MergedMangaReference

View File

@ -7,15 +7,22 @@ import androidx.core.os.bundleOf
import androidx.recyclerview.widget.ConcatAdapter
import androidx.recyclerview.widget.LinearLayoutManager
import com.google.android.material.dialog.MaterialAlertDialogBuilder
import eu.kanade.domain.manga.interactor.DeleteMergeById
import eu.kanade.domain.manga.interactor.GetManga
import eu.kanade.domain.manga.interactor.GetMergedMangaById
import eu.kanade.domain.manga.interactor.GetMergedReferencesById
import eu.kanade.domain.manga.model.Manga
import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.data.database.DatabaseHelper
import eu.kanade.tachiyomi.data.database.models.Manga
import eu.kanade.tachiyomi.databinding.EditMergedSettingsDialogBinding
import eu.kanade.tachiyomi.ui.base.controller.DialogController
import eu.kanade.tachiyomi.ui.manga.MangaController
import eu.kanade.tachiyomi.util.lang.launchIO
import eu.kanade.tachiyomi.util.system.toast
import exh.merged.sql.models.MergedMangaReference
import exh.source.MERGED_SOURCE_ID
import kotlinx.coroutines.runBlocking
import uy.kohesive.injekt.Injekt
import uy.kohesive.injekt.api.get
import uy.kohesive.injekt.injectLazy
class EditMergedSettingsDialog : DialogController, EditMergedMangaAdapter.EditMergedMangaItemListener {
@ -27,13 +34,15 @@ class EditMergedSettingsDialog : DialogController, EditMergedMangaAdapter.EditMe
lateinit var binding: EditMergedSettingsDialogBinding
private val db: DatabaseHelper by injectLazy()
private val getMergedMangaById: GetMergedMangaById by injectLazy()
private val getMergedReferencesById: GetMergedReferencesById by injectLazy()
private val deleteMergeById: DeleteMergeById by injectLazy()
private val mangaController
get() = targetController as MangaController
constructor(target: MangaController, manga: Manga) : super(
bundleOf(KEY_MANGA to manga.id!!),
bundleOf(KEY_MANGA to manga.id),
) {
targetController = target
this.manga = manga
@ -41,8 +50,7 @@ class EditMergedSettingsDialog : DialogController, EditMergedMangaAdapter.EditMe
@Suppress("unused")
constructor(bundle: Bundle) : super(bundle) {
manga = db.getManga(bundle.getLong(KEY_MANGA))
.executeAsBlocking()!!
manga = runBlocking { Injekt.get<GetManga>().await(bundle.getLong(KEY_MANGA))!! }
}
private var mergedHeaderAdapter: EditMergedSettingsHeaderAdapter? = null
@ -62,8 +70,8 @@ class EditMergedSettingsDialog : DialogController, EditMergedMangaAdapter.EditMe
}
fun onViewCreated() {
val mergedManga = db.getMergedMangas(manga.id!!).executeAsBlocking()
val mergedReferences = db.getMergedMangaReferences(manga.id!!).executeAsBlocking()
val mergedManga = runBlocking { getMergedMangaById.await(manga.id) }
val mergedReferences = runBlocking { getMergedReferencesById.await(manga.id) }
if (mergedReferences.isEmpty() || mergedReferences.size == 1) {
activity?.toast(R.string.merged_references_invalid)
router.popCurrentController()
@ -85,7 +93,7 @@ class EditMergedSettingsDialog : DialogController, EditMergedMangaAdapter.EditMe
}
private fun onPositiveButtonClick() {
mangaController.presenter.updateMergeSettings(mergeReference, mergedMangas.map { it.second })
mangaController.presenter.updateMergeSettings(listOfNotNull(mergeReference) + mergedMangas.map { it.second })
}
override fun onItemReleased(position: Int) {
@ -105,7 +113,9 @@ class EditMergedSettingsDialog : DialogController, EditMergedMangaAdapter.EditMe
.setTitle(R.string.delete_merged_manga)
.setMessage(R.string.delete_merged_manga_desc)
.setPositiveButton(android.R.string.ok) { _, _ ->
db.deleteMergedManga(mergeMangaReference).executeAsBlocking()
launchIO {
deleteMergeById.await(mergeMangaReference.id!!)
}
dialog?.dismiss()
mangaController.router.popController(mangaController)
}

View File

@ -14,12 +14,14 @@ import eu.kanade.domain.history.interactor.UpsertHistory
import eu.kanade.domain.history.model.HistoryUpdate
import eu.kanade.domain.manga.interactor.GetFlatMetadataById
import eu.kanade.domain.manga.interactor.GetManga
import eu.kanade.domain.manga.interactor.GetMergedManga
import eu.kanade.domain.manga.interactor.GetMergedReferencesById
import eu.kanade.domain.manga.interactor.SetMangaViewerFlags
import eu.kanade.domain.manga.model.isLocal
import eu.kanade.domain.manga.model.toDbManga
import eu.kanade.domain.track.interactor.GetTracks
import eu.kanade.domain.track.interactor.InsertTrack
import eu.kanade.domain.track.model.toDbTrack
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.models.toDomainManga
@ -86,7 +88,6 @@ import java.util.concurrent.TimeUnit
* Presenter used by the activity to perform background operations.
*/
class ReaderPresenter(
private val db: DatabaseHelper = Injekt.get(),
private val sourceManager: SourceManager = Injekt.get(),
private val downloadManager: DownloadManager = Injekt.get(),
private val preferences: PreferencesHelper = Injekt.get(),
@ -97,8 +98,11 @@ class ReaderPresenter(
private val insertTrack: InsertTrack = Injekt.get(),
private val upsertHistory: UpsertHistory = Injekt.get(),
private val updateChapter: UpdateChapter = Injekt.get(),
private val setMangaViewerFlags: SetMangaViewerFlags = Injekt.get(),
// SY -->
private val getFlatMetadataById: GetFlatMetadataById,
private val getFlatMetadataById: GetFlatMetadataById = Injekt.get(),
private val getMergedManga: GetMergedManga = Injekt.get(),
private val getMergedReferencesById: GetMergedReferencesById = Injekt.get(),
// SY <--
) : BasePresenter<ReaderActivity>() {
@ -314,8 +318,8 @@ class ReaderPresenter(
val context = Injekt.get<Application>()
val source = sourceManager.getOrStub(manga.source)
val mergedReferences = if (source is MergedSource) db.getMergedMangaReferences(manga.id!!).executeAsBlocking() else emptyList()
mergedManga = if (source is MergedSource) db.getMergedMangas(manga.id!!).executeAsBlocking().associateBy { it.id!! } else emptyMap()
val mergedReferences = if (source is MergedSource) runBlocking { getMergedReferencesById.await(manga.id!!) } else emptyList()
mergedManga = if (source is MergedSource) runBlocking { getMergedManga.await() }.map { it.toDbManga() }.associateBy { it.id!! } else emptyMap()
loader = ChapterLoader(context, downloadManager, manga, source, sourceManager, mergedReferences, mergedManga ?: emptyMap())
Observable.just(manga).subscribeLatestCache(ReaderActivity::setManga)
@ -648,7 +652,14 @@ class ReaderPresenter(
fun toggleBookmark(chapterId: Long, bookmarked: Boolean) {
val chapter = chapterList.find { it.chapter.id == chapterId }?.chapter ?: return
chapter.bookmark = bookmarked
db.updateChapterProgress(chapter).executeAsBlocking()
launchIO {
updateChapter.await(
ChapterUpdate(
id = chapter.id!!.toLong(),
bookmark = bookmarked,
),
)
}
}
// SY <--
@ -677,7 +688,9 @@ class ReaderPresenter(
fun setMangaReadingMode(readingModeType: Int) {
val manga = manga ?: return
manga.readingModeType = readingModeType
db.updateViewerFlags(manga).executeAsBlocking()
runBlocking {
setMangaViewerFlags.awaitSetMangaReadingMode(manga.id!!.toLong(), readingModeType.toLong())
}
Observable.timer(250, TimeUnit.MILLISECONDS, AndroidSchedulers.mainThread())
.subscribeFirst({ view, _ ->
@ -712,7 +725,9 @@ class ReaderPresenter(
fun setMangaOrientationType(rotationType: Int) {
val manga = manga ?: return
manga.orientationType = rotationType
db.updateViewerFlags(manga).executeAsBlocking()
runBlocking {
setMangaViewerFlags.awaitSetOrientationType(manga.id!!.toLong(), rotationType.toLong())
}
logcat(LogPriority.INFO) { "Manga orientation is ${manga.orientationType}" }

View File

@ -1,7 +1,7 @@
package eu.kanade.tachiyomi.util.chapter
import eu.kanade.domain.manga.interactor.GetFavorites
import eu.kanade.domain.manga.interactor.SetMangaChapterFlags
import eu.kanade.tachiyomi.data.database.DatabaseHelper
import eu.kanade.tachiyomi.data.database.models.Manga
import eu.kanade.tachiyomi.data.preference.PreferencesHelper
import eu.kanade.tachiyomi.util.lang.launchIO
@ -10,7 +10,8 @@ import uy.kohesive.injekt.injectLazy
object ChapterSettingsHelper {
private val prefs: PreferencesHelper by injectLazy()
private val db: DatabaseHelper by injectLazy()
private val getFavorites: GetFavorites by injectLazy()
private val setMangaChapterFlags: SetMangaChapterFlags by injectLazy()
/**
* Updates the global Chapter Settings in Preferences.
@ -23,19 +24,20 @@ object ChapterSettingsHelper {
* Updates a single manga's Chapter Settings to match what's set in Preferences.
*/
fun applySettingDefaults(manga: Manga) {
with(manga) {
readFilter = prefs.filterChapterByRead()
downloadedFilter = prefs.filterChapterByDownloaded()
bookmarkedFilter = prefs.filterChapterByBookmarked()
sorting = prefs.sortChapterBySourceOrNumber()
displayMode = prefs.displayChapterByNameOrNumber()
setChapterOrder(prefs.sortChapterByAscendingOrDescending())
launchIO {
setMangaChapterFlags.awaitSetAllFlags(
mangaId = manga.id!!,
unreadFilter = prefs.filterChapterByRead().toLong(),
downloadedFilter = prefs.filterChapterByDownloaded().toLong(),
bookmarkedFilter = prefs.filterChapterByBookmarked().toLong(),
sortingMode = prefs.sortChapterBySourceOrNumber().toLong(),
sortingDirection = prefs.sortChapterByAscendingOrDescending().toLong(),
displayMode = prefs.displayChapterByNameOrNumber().toLong(),
)
}
db.updateChapterFlags(manga).executeAsBlocking()
}
suspend fun applySettingDefaults(mangaId: Long, setMangaChapterFlags: SetMangaChapterFlags) {
suspend fun applySettingDefaults(mangaId: Long) {
setMangaChapterFlags.awaitSetAllFlags(
mangaId = mangaId,
unreadFilter = prefs.filterChapterByRead().toLong(),
@ -52,21 +54,18 @@ object ChapterSettingsHelper {
*/
fun updateAllMangasWithGlobalDefaults() {
launchIO {
val updatedMangas = db.getFavoriteMangas()
.executeAsBlocking()
getFavorites.await()
.map { manga ->
with(manga) {
readFilter = prefs.filterChapterByRead()
downloadedFilter = prefs.filterChapterByDownloaded()
bookmarkedFilter = prefs.filterChapterByBookmarked()
sorting = prefs.sortChapterBySourceOrNumber()
displayMode = prefs.displayChapterByNameOrNumber()
setChapterOrder(prefs.sortChapterByAscendingOrDescending())
}
manga
setMangaChapterFlags.awaitSetAllFlags(
mangaId = manga.id,
unreadFilter = prefs.filterChapterByRead().toLong(),
downloadedFilter = prefs.filterChapterByDownloaded().toLong(),
bookmarkedFilter = prefs.filterChapterByBookmarked().toLong(),
sortingMode = prefs.sortChapterBySourceOrNumber().toLong(),
sortingDirection = prefs.sortChapterByAscendingOrDescending().toLong(),
displayMode = prefs.displayChapterByNameOrNumber().toLong(),
)
}
db.updateChapterFlags(updatedMangas).executeAsBlocking()
}
}
}

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 {

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() } }
}
}

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)
}

View File

@ -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()
}

View File

@ -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()
}

View File

@ -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,
)
}

View File

@ -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,
)
}

View File

@ -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,
)
}

View File

@ -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"
}

View File

@ -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)
}
}
}

View File

@ -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()
}

View File

@ -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()
}

View File

@ -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 {

View File

@ -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)

View File

@ -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() }

View File

@ -4,4 +4,29 @@ DELETE FROM manga_sync WHERE sync_id = :syncId;
migrateSource:
UPDATE mangas
SET source = :newId
WHERE source = :oldId;
WHERE source = :oldId;
getChaptersByMangaIds:
SELECT * FROM chapters WHERE manga_id IN :mangaIds;
resetFilteredScanlatorsForAllManga:
UPDATE mangas
SET filtered_scanlators = NULL;
migrateAllNhentaiToOtherLang:
UPDATE mangas
SET source = :nh
WHERE favorite = 1 AND source IN :sources;
resetReaderViewerForAllManga:
UPDATE mangas
SET viewer = 0;
fixReaderViewerBackupBug:
UPDATE mangas
SET viewer = 0
WHERE viewer = -1;
addAllMangaInDatabaseToLibrary:
UPDATE mangas
SET favorite = 1;

View File

@ -166,6 +166,44 @@ WHERE favorite = 0 AND source IN :sourceIdsAND AND _id NOT IN (
SELECT manga_id FROM chapters WHERE read = 1 OR last_page_read != 0
);
INSERT INTO mangas(
source,
url,
artist,
author,
description,
genre,
title,
status,
thumbnail_url,
favorite,
last_update,
next_update,
initialized,
viewer,
chapter_flags,
cover_last_modified,
date_added
) VALUES (
:source,
:url,
:artist,
:author,
:description,
:genre,
:title,
:status,
:thumbnailUrl,
:favorite,
:lastUpdate,
0,
:initialized,
:viewerFlags,
:chapterFlags,
:coverLastModified,
:dateAdded
);
update:
UPDATE mangas SET
source = coalesce(:source, source),
@ -208,6 +246,9 @@ SELECT * FROM mangas WHERE source = :sourceId;
getAll:
SELECT * FROM mangas;
deleteById:
DELETE FROM mangas WHERE _id = :id;
selectLastInsertRow:
SELECT *
FROM mangas

View File

@ -73,9 +73,31 @@ FROM (
JOIN chapters
ON chapters.manga_id = M.manga_id;
insertMerged:
INSERT INTO merged (_id, info_manga, get_chapter_updates, chapter_sort_mode, chapter_priority, download_chapters, merge_id, merge_url, manga_id, manga_url, manga_source)
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?);
insert:
INSERT INTO merged(
info_manga,
get_chapter_updates,
chapter_sort_mode,
chapter_priority,
download_chapters,
merge_id,
merge_url,
manga_id,
manga_url,
manga_source
)
VALUES (
:infoManga,
:getChapterUpdates,
:chapterSortMode,
:chapterPriority,
:downloadChapters,
:mergeId,
:mergeUrl,
:mangaId,
:mangaUrl,
:mangaSource
);
updateSettingsById:
UPDATE merged
@ -89,5 +111,8 @@ WHERE _id = :id;
deleteById:
DELETE FROM merged WHERE _id = ?;
deleteBy:
deleteAll:
DELETE FROM merged;
selectLastInsertedRowId:
SELECT last_insert_rowid();