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.runtime)
implementation(androidx.paging.compose) implementation(androidx.paging.compose)
implementation(libs.bundles.sqlite)
implementation(androidx.sqlite) implementation(androidx.sqlite)
implementation(libs.sqldelight.android.driver) implementation(libs.sqldelight.android.driver)
implementation(libs.sqldelight.coroutines) implementation(libs.sqldelight.coroutines)
@ -201,11 +202,6 @@ dependencies {
implementation(libs.unifile) implementation(libs.unifile)
implementation(libs.junrar) 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 // Preferences
implementation(libs.preferencektx) implementation(libs.preferencektx)
implementation(libs.flowpreferences) implementation(libs.flowpreferences)

View File

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

View File

@ -1,7 +1,9 @@
package eu.kanade.data.manga package eu.kanade.data.manga
import eu.kanade.data.listOfStringsAndAdapter
import eu.kanade.domain.chapter.model.Chapter import eu.kanade.domain.chapter.model.Chapter
import eu.kanade.domain.manga.model.Manga 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 = 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 -> { 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, 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 package eu.kanade.data.manga
import eu.kanade.data.AndroidDatabaseHandler
import eu.kanade.data.DatabaseHandler import eu.kanade.data.DatabaseHandler
import eu.kanade.data.listOfStringsAdapter import eu.kanade.data.listOfStringsAdapter
import eu.kanade.data.listOfStringsAndAdapter 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.Manga
import eu.kanade.domain.manga.model.MangaUpdate import eu.kanade.domain.manga.model.MangaUpdate
import eu.kanade.domain.manga.repository.MangaRepository import eu.kanade.domain.manga.repository.MangaRepository
import eu.kanade.tachiyomi.data.database.models.LibraryManga
import eu.kanade.tachiyomi.util.system.logcat import eu.kanade.tachiyomi.util.system.logcat
import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.Flow
import logcat.LogPriority import logcat.LogPriority
@ -19,26 +21,28 @@ class MangaRepositoryImpl(
return handler.awaitOne { mangasQueries.getMangaById(id, mangaMapper) } 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> { override suspend fun getMangaByIdAsFlow(id: Long): Flow<Manga> {
return handler.subscribeToOne { mangasQueries.getMangaById(id, mangaMapper) } 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) } 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> { override suspend fun getFavorites(): List<Manga> {
return handler.awaitList { mangasQueries.getFavorites(mangaMapper) } 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>> { override fun getFavoritesBySourceId(sourceId: Long): Flow<List<Manga>> {
return handler.subscribeToList { mangasQueries.getFavoriteBySourceId(sourceId, mangaMapper) } 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 { override suspend fun update(update: MangaUpdate): Boolean {
return try { return try {
partialUpdate(update) 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) } return handler.awaitList { mangasQueries.getBySource(sourceId, mangaMapper) }
} }
override suspend fun getAll(): List<Manga> { override suspend fun getAll(): List<Manga> {
return handler.awaitList { mangasQueries.getAll(mangaMapper) } 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.history.repository.HistoryRepository
import eu.kanade.domain.manga.interactor.GetDuplicateLibraryManga import eu.kanade.domain.manga.interactor.GetDuplicateLibraryManga
import eu.kanade.domain.manga.interactor.GetFavorites 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.GetManga
import eu.kanade.domain.manga.interactor.GetMangaWithChapters 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.ResetViewerFlags
import eu.kanade.domain.manga.interactor.SetMangaChapterFlags 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.interactor.UpdateManga
import eu.kanade.domain.manga.repository.MangaRepository import eu.kanade.domain.manga.repository.MangaRepository
import eu.kanade.domain.source.interactor.GetEnabledSources import eu.kanade.domain.source.interactor.GetEnabledSources
@ -71,11 +74,14 @@ class DomainModule : InjektModule {
addSingletonFactory<MangaRepository> { MangaRepositoryImpl(get()) } addSingletonFactory<MangaRepository> { MangaRepositoryImpl(get()) }
addFactory { GetDuplicateLibraryManga(get()) } addFactory { GetDuplicateLibraryManga(get()) }
addFactory { GetFavorites(get()) } addFactory { GetFavorites(get()) }
addFactory { GetLibraryManga(get()) }
addFactory { GetMangaWithChapters(get(), get()) } addFactory { GetMangaWithChapters(get(), get()) }
addFactory { GetManga(get()) } addFactory { GetManga(get()) }
addFactory { GetNextChapter(get()) } addFactory { GetNextChapter(get()) }
addFactory { ResetViewerFlags(get()) } addFactory { ResetViewerFlags(get()) }
addFactory { SetMangaChapterFlags(get()) } addFactory { SetMangaChapterFlags(get()) }
addFactory { SetMangaViewerFlags(get()) }
addFactory { InsertManga(get()) }
addFactory { UpdateManga(get()) } addFactory { UpdateManga(get()) }
addFactory { SetMangaCategories(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.FavoritesEntryRepositoryImpl
import eu.kanade.data.manga.MangaMergeRepositoryImpl import eu.kanade.data.manga.MangaMergeRepositoryImpl
import eu.kanade.data.manga.MangaMetadataRepositoryImpl import eu.kanade.data.manga.MangaMetadataRepositoryImpl
import eu.kanade.domain.chapter.interactor.DeleteChapters
import eu.kanade.domain.chapter.interactor.GetMergedChapterByMangaId 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.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.GetAllManga
import eu.kanade.domain.manga.interactor.GetExhFavoriteMangaWithMetadata import eu.kanade.domain.manga.interactor.GetExhFavoriteMangaWithMetadata
import eu.kanade.domain.manga.interactor.GetFavoriteEntries 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.GetMangaBySource
import eu.kanade.domain.manga.interactor.GetMergedManga import eu.kanade.domain.manga.interactor.GetMergedManga
import eu.kanade.domain.manga.interactor.GetMergedMangaById 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.GetMergedReferencesById
import eu.kanade.domain.manga.interactor.GetSearchTags import eu.kanade.domain.manga.interactor.GetSearchTags
import eu.kanade.domain.manga.interactor.GetSearchTitles import eu.kanade.domain.manga.interactor.GetSearchTitles
import eu.kanade.domain.manga.interactor.InsertFavoriteEntries import eu.kanade.domain.manga.interactor.InsertFavoriteEntries
import eu.kanade.domain.manga.interactor.InsertFlatMetadata 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.SetMangaFilteredScanlators
import eu.kanade.domain.manga.interactor.UpdateMergedSettings
import eu.kanade.domain.manga.repository.FavoritesEntryRepository import eu.kanade.domain.manga.repository.FavoritesEntryRepository
import eu.kanade.domain.manga.repository.MangaMergeRepository import eu.kanade.domain.manga.repository.MangaMergeRepository
import eu.kanade.domain.manga.repository.MangaMetadataRepository import eu.kanade.domain.manga.repository.MangaMetadataRepository
@ -44,6 +51,8 @@ class SYDomainModule : InjektModule {
addFactory { SetMangaFilteredScanlators(get()) } addFactory { SetMangaFilteredScanlators(get()) }
addFactory { GetAllManga(get()) } addFactory { GetAllManga(get()) }
addFactory { GetMangaBySource(get()) } addFactory { GetMangaBySource(get()) }
addFactory { DeleteChapters(get()) }
addFactory { DeleteMangaById(get()) }
addSingletonFactory<MangaMetadataRepository> { MangaMetadataRepositoryImpl(get()) } addSingletonFactory<MangaMetadataRepository> { MangaMetadataRepositoryImpl(get()) }
addFactory { GetFlatMetadataById(get()) } addFactory { GetFlatMetadataById(get()) }
@ -58,6 +67,11 @@ class SYDomainModule : InjektModule {
addFactory { GetMergedMangaById(get()) } addFactory { GetMergedMangaById(get()) }
addFactory { GetMergedReferencesById(get()) } addFactory { GetMergedReferencesById(get()) }
addFactory { GetMergedChapterByMangaId(get()) } addFactory { GetMergedChapterByMangaId(get()) }
addFactory { InsertMergedReference(get()) }
addFactory { UpdateMergedSettings(get()) }
addFactory { DeleteByMergeId(get()) }
addFactory { DeleteMergeById(get()) }
addFactory { GetMergedMangaForDownloading(get()) }
addSingletonFactory<FavoritesEntryRepository> { FavoritesEntryRepositoryImpl(get()) } addSingletonFactory<FavoritesEntryRepository> { FavoritesEntryRepositoryImpl(get()) }
addFactory { GetFavoriteEntries(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> { suspend fun subscribe(id: Long): Flow<Manga> {
return mangaRepository.subscribeMangaById(id) return mangaRepository.getMangaByIdAsFlow(id)
} }
suspend fun await(url: String, sourceId: Long): Manga? { suspend fun await(url: String, sourceId: Long): Manga? {
return try { return mangaRepository.getMangaByUrlAndSourceId(url, sourceId)
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)
} }
} }

View File

@ -8,6 +8,6 @@ class GetMangaBySource(
) { ) {
suspend fun await(sourceId: Long): List<Manga> { 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>>> { suspend fun subscribe(id: Long): Flow<Pair<Manga, List<Chapter>>> {
return combine( return combine(
mangaRepository.subscribeMangaById(id), mangaRepository.getMangaByIdAsFlow(id),
chapterRepository.getChapterByMangaIdAsFlow(id), chapterRepository.getChapterByMangaIdAsFlow(id),
) { manga, chapters -> ) { manga, chapters ->
Pair(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 <-- // 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.isLocal(): Boolean = source == LocalSource.ID
fun Manga.hasCustomCover(coverCache: CoverCache = Injekt.get()): Boolean { fun Manga.hasCustomCover(coverCache: CoverCache = Injekt.get()): Boolean {

View File

@ -21,4 +21,14 @@ interface MangaMergeRepository {
suspend fun updateSettings(update: MergeMangaSettingsUpdate): Boolean suspend fun updateSettings(update: MergeMangaSettingsUpdate): Boolean
suspend fun updateAllSettings(values: List<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.Manga
import eu.kanade.domain.manga.model.MangaUpdate import eu.kanade.domain.manga.model.MangaUpdate
import eu.kanade.tachiyomi.data.database.models.LibraryManga
import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.Flow
interface MangaRepository { interface MangaRepository {
suspend fun getMangaById(id: Long): Manga suspend fun getMangaById(id: Long): Manga
suspend fun subscribeMangaById(id: Long): Flow<Manga>
suspend fun getMangaByIdAsFlow(id: Long): Flow<Manga> suspend fun getMangaByIdAsFlow(id: Long): Flow<Manga>
suspend fun getMangaByUrlAndSource(url: String, sourceId: Long): Manga? suspend fun getMangaByUrlAndSourceId(url: String, sourceId: Long): Manga?
suspend fun subscribeMangaByUrlAndSource(url: String, sourceId: Long): Flow<Manga?>
suspend fun getFavorites(): List<Manga> suspend fun getFavorites(): List<Manga>
suspend fun getLibraryManga(): List<LibraryManga>
fun getLibraryMangaAsFlow(): Flow<List<LibraryManga>>
fun getFavoritesBySourceId(sourceId: Long): Flow<List<Manga>> fun getFavoritesBySourceId(sourceId: Long): Flow<List<Manga>>
suspend fun getDuplicateLibraryManga(title: String, sourceId: Long): 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 setMangaCategories(mangaId: Long, categoryIds: List<Long>)
suspend fun insert(manga: Manga): Long?
suspend fun update(update: MangaUpdate): Boolean suspend fun update(update: MangaUpdate): Boolean
suspend fun updateAll(values: List<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 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.data.listOfStringsAndAdapter
import eu.kanade.tachiyomi.data.cache.ChapterCache import eu.kanade.tachiyomi.data.cache.ChapterCache
import eu.kanade.tachiyomi.data.cache.CoverCache 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.database.DbOpenCallback
import eu.kanade.tachiyomi.data.download.DownloadManager import eu.kanade.tachiyomi.data.download.DownloadManager
import eu.kanade.tachiyomi.data.library.CustomMangaManager import eu.kanade.tachiyomi.data.library.CustomMangaManager
@ -89,8 +88,6 @@ class AppModule(val app: Application) : InjektModule {
addSingletonFactory { PreferencesHelper(app) } addSingletonFactory { PreferencesHelper(app) }
addSingletonFactory { DatabaseHelper(get()) }
addSingletonFactory { ChapterCache(app) } addSingletonFactory { ChapterCache(app) }
addSingletonFactory { CoverCache(app) } addSingletonFactory { CoverCache(app) }
@ -125,8 +122,6 @@ class AppModule(val app: Application) : InjektModule {
get<Database>() get<Database>()
get<DatabaseHelper>()
get<DownloadManager>() get<DownloadManager>()
// SY --> // SY -->

View File

@ -6,13 +6,16 @@ import eu.kanade.data.DatabaseHandler
import eu.kanade.data.manga.mangaMapper import eu.kanade.data.manga.mangaMapper
import eu.kanade.data.toLong import eu.kanade.data.toLong
import eu.kanade.domain.manga.interactor.GetFavorites 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.GetMergedManga
import eu.kanade.domain.manga.interactor.InsertFlatMetadata
import eu.kanade.tachiyomi.data.database.models.Chapter import eu.kanade.tachiyomi.data.database.models.Chapter
import eu.kanade.tachiyomi.data.database.models.Manga import eu.kanade.tachiyomi.data.database.models.Manga
import eu.kanade.tachiyomi.data.library.CustomMangaManager import eu.kanade.tachiyomi.data.library.CustomMangaManager
import eu.kanade.tachiyomi.data.preference.PreferencesHelper import eu.kanade.tachiyomi.data.preference.PreferencesHelper
import eu.kanade.tachiyomi.data.track.TrackManager import eu.kanade.tachiyomi.data.track.TrackManager
import eu.kanade.tachiyomi.source.SourceManager import eu.kanade.tachiyomi.source.SourceManager
import exh.metadata.metadata.base.FlatMetadata
import uy.kohesive.injekt.Injekt import uy.kohesive.injekt.Injekt
import uy.kohesive.injekt.api.get import uy.kohesive.injekt.api.get
import data.Mangas as DbManga import data.Mangas as DbManga
@ -30,6 +33,8 @@ abstract class AbstractBackupManager(protected val context: Context) {
// SY --> // SY -->
private val getMergedManga: GetMergedManga = Injekt.get() private val getMergedManga: GetMergedManga = Injekt.get()
protected val customMangaManager: CustomMangaManager = Injekt.get() protected val customMangaManager: CustomMangaManager = Injekt.get()
private val insertFlatMetadata: InsertFlatMetadata = Injekt.get()
private val getFlatMetadataById: GetFlatMetadataById = Injekt.get()
// SY <-- // SY <--
abstract suspend fun createBackup(uri: Uri, flags: Int, isAutoBackup: Boolean): String 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> { protected suspend fun getMergedManga(): List<DomainManga> {
return getMergedManga.await() return getMergedManga.await()
} }
protected suspend fun getFlatMetadata(mangaId: Long) = getFlatMetadataById.await(mangaId)
protected suspend fun insertFlatMetadata(flatMetadata: FlatMetadata) = insertFlatMetadata.await(flatMetadata)
// SY <-- // 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.data.database.models.Track
import eu.kanade.tachiyomi.source.online.MetadataSource import eu.kanade.tachiyomi.source.online.MetadataSource
import eu.kanade.tachiyomi.util.system.logcat 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.MERGED_SOURCE_ID
import exh.source.getMainSource import exh.source.getMainSource
import exh.util.nullIfBlank import exh.util.nullIfBlank
@ -197,7 +195,7 @@ class FullBackupManager(context: Context) : AbstractBackupManager(context) {
val source = sourceManager.get(manga.source)?.getMainSource<MetadataSource<*, *>>() val source = sourceManager.get(manga.source)?.getMainSource<MetadataSource<*, *>>()
if (source != null) { if (source != null) {
handler.awaitFlatMetadataForManga(manga.id)?.let { flatMetadata -> getFlatMetadata(manga.id)?.let { flatMetadata ->
mangaObject.flatMetadata = BackupFlatMetadata.copyFrom(flatMetadata) mangaObject.flatMetadata = BackupFlatMetadata.copyFrom(flatMetadata)
} }
} }
@ -535,18 +533,17 @@ class FullBackupManager(context: Context) : AbstractBackupManager(context) {
mergeId = mergeMangaId mergeId = mergeMangaId
mangaId = mergedManga.id mangaId = mergedManga.id
handler.await { handler.await {
mergedQueries.insertMerged( mergedQueries.insert(
null, infoManga = isInfoManga,
isInfoManga, getChapterUpdates = getChapterUpdates,
getChapterUpdates, chapterSortMode = chapterSortMode.toLong(),
chapterSortMode.toLong(), chapterPriority = chapterPriority.toLong(),
chapterPriority.toLong(), downloadChapters = downloadChapters,
downloadChapters, mergeId = mergeId!!,
mergeId!!, mergeUrl = mergeUrl,
mergeUrl, mangaId = mangaId,
mangaId, mangaUrl = mangaUrl,
mangaUrl, mangaSource = mangaSourceId,
mangaSourceId,
) )
} }
} }
@ -555,8 +552,8 @@ class FullBackupManager(context: Context) : AbstractBackupManager(context) {
} }
internal suspend fun restoreFlatMetadata(mangaId: Long, backupFlatMetadata: BackupFlatMetadata) { internal suspend fun restoreFlatMetadata(mangaId: Long, backupFlatMetadata: BackupFlatMetadata) {
if (handler.awaitFlatMetadataForManga(mangaId) == null) { if (getFlatMetadata(mangaId) == null) {
handler.awaitInsertFlatMetadata(backupFlatMetadata.getFlatMetadata(mangaId)) insertFlatMetadata(backupFlatMetadata.getFlatMetadata(mangaId))
} }
} }
// SY <-- // 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 com.jakewharton.rxrelay.BehaviorRelay
import eu.kanade.domain.category.interactor.GetCategories import eu.kanade.domain.category.interactor.GetCategories
import eu.kanade.tachiyomi.R 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.Chapter
import eu.kanade.tachiyomi.data.database.models.Manga import eu.kanade.tachiyomi.data.database.models.Manga
import eu.kanade.tachiyomi.data.download.model.Download import eu.kanade.tachiyomi.data.download.model.Download
@ -34,7 +33,6 @@ import uy.kohesive.injekt.injectLazy
*/ */
class DownloadManager( class DownloadManager(
private val context: Context, private val context: Context,
private val db: DatabaseHelper = Injekt.get(),
private val getCategories: GetCategories = 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.interactor.SyncChaptersWithTrackServiceTwoWay
import eu.kanade.domain.chapter.model.toDbChapter import eu.kanade.domain.chapter.model.toDbChapter
import eu.kanade.domain.manga.interactor.GetFavorites 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.GetManga
import eu.kanade.domain.manga.interactor.GetMergedMangaForDownloading
import eu.kanade.domain.manga.interactor.InsertFlatMetadata 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.interactor.UpdateManga
import eu.kanade.domain.manga.model.toDbManga import eu.kanade.domain.manga.model.toDbManga
import eu.kanade.domain.manga.model.toMangaInfo 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.GetTracks
import eu.kanade.domain.track.interactor.InsertTrack import eu.kanade.domain.track.interactor.InsertTrack
import eu.kanade.domain.track.model.toDbTrack import eu.kanade.domain.track.model.toDbTrack
import eu.kanade.domain.track.model.toDomainTrack import eu.kanade.domain.track.model.toDomainTrack
import eu.kanade.tachiyomi.R import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.data.cache.CoverCache 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.Chapter
import eu.kanade.tachiyomi.data.database.models.LibraryManga import eu.kanade.tachiyomi.data.database.models.LibraryManga
import eu.kanade.tachiyomi.data.database.models.Manga 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.SourceManager
import eu.kanade.tachiyomi.source.UnmeteredSource import eu.kanade.tachiyomi.source.UnmeteredSource
import eu.kanade.tachiyomi.source.model.SManga 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.toSChapter
import eu.kanade.tachiyomi.source.model.toSManga import eu.kanade.tachiyomi.source.model.toSManga
import eu.kanade.tachiyomi.source.online.all.MergedSource 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.MERGED_SOURCE_ID
import exh.source.isMdBasedSource import exh.source.isMdBasedSource
import exh.source.mangaDexSourceIds import exh.source.mangaDexSourceIds
import exh.util.executeOnIO
import exh.util.nullIfBlank import exh.util.nullIfBlank
import kotlinx.coroutines.CoroutineExceptionHandler import kotlinx.coroutines.CoroutineExceptionHandler
import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.CoroutineScope
@ -102,12 +105,12 @@ import eu.kanade.domain.manga.model.Manga as DomainManga
* destroyed. * destroyed.
*/ */
class LibraryUpdateService( class LibraryUpdateService(
val db: DatabaseHelper = Injekt.get(),
val sourceManager: SourceManager = Injekt.get(), val sourceManager: SourceManager = Injekt.get(),
val preferences: PreferencesHelper = Injekt.get(), val preferences: PreferencesHelper = Injekt.get(),
val downloadManager: DownloadManager = Injekt.get(), val downloadManager: DownloadManager = Injekt.get(),
val trackManager: TrackManager = Injekt.get(), val trackManager: TrackManager = Injekt.get(),
val coverCache: CoverCache = Injekt.get(), val coverCache: CoverCache = Injekt.get(),
private val getLibraryManga: GetLibraryManga = Injekt.get(),
private val getManga: GetManga = Injekt.get(), private val getManga: GetManga = Injekt.get(),
private val updateManga: UpdateManga = Injekt.get(), private val updateManga: UpdateManga = Injekt.get(),
private val getChapterByMangaId: GetChapterByMangaId = Injekt.get(), private val getChapterByMangaId: GetChapterByMangaId = Injekt.get(),
@ -119,6 +122,8 @@ class LibraryUpdateService(
// SY --> // SY -->
private val getFavorites: GetFavorites = Injekt.get(), private val getFavorites: GetFavorites = Injekt.get(),
private val insertFlatMetadata: InsertFlatMetadata = Injekt.get(), private val insertFlatMetadata: InsertFlatMetadata = Injekt.get(),
private val insertManga: InsertManga = Injekt.get(),
private val getMergedMangaForDownloading: GetMergedMangaForDownloading = Injekt.get(),
// SY <-- // SY <--
) : Service() { ) : Service() {
@ -301,7 +306,7 @@ class LibraryUpdateService(
* @param categoryId the ID of the category to update, or -1 if no category specified. * @param categoryId the ID of the category to update, or -1 if no category specified.
*/ */
fun addMangaToQueue(categoryId: Long, group: Int, groupExtra: String?) { fun addMangaToQueue(categoryId: Long, group: Int, groupExtra: String?) {
val libraryManga = db.getLibraryMangas().executeAsBlocking() val libraryManga = runBlocking { getLibraryManga.await() }
// SY --> // SY -->
val groupLibraryUpdateType = preferences.groupLibraryUpdateType().get() val groupLibraryUpdateType = preferences.groupLibraryUpdateType().get()
// SY <-- // SY <--
@ -499,12 +504,12 @@ class LibraryUpdateService(
// may don't like it and they could ban the user. // may don't like it and they could ban the user.
// SY --> // SY -->
if (manga.source == MERGED_SOURCE_ID) { if (manga.source == MERGED_SOURCE_ID) {
val downloadingManga = db.getMergedMangasForDownloading(manga.id!!).executeAsBlocking() val downloadingManga = runBlocking { getMergedMangaForDownloading.await(manga.id!!) }
.associateBy { it.id!! } .associateBy { it.id }
chapters.groupBy { it.manga_id } chapters.groupBy { it.manga_id }
.forEach { .forEach {
downloadManager.downloadChapters( downloadManager.downloadChapters(
downloadingManga[it.key] ?: return@forEach, downloadingManga[it.key]?.toDbManga() ?: return@forEach,
chapters, chapters,
false, false,
) )
@ -593,7 +598,14 @@ class LibraryUpdateService(
mangaWithNotif.prepUpdateCover(coverCache, sManga, true) mangaWithNotif.prepUpdateCover(coverCache, sManga, true)
sManga.thumbnail_url?.let { sManga.thumbnail_url?.let {
mangaWithNotif.thumbnail_url = it 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) { } catch (e: Throwable) {
// Ignore errors and continue // Ignore errors and continue
@ -714,24 +726,29 @@ class LibraryUpdateService(
count++ count++
notifier.showProgressNotification(listOf(networkManga), count, size) notifier.showProgressNotification(listOf(networkManga), count, size)
var dbManga = db.getManga(networkManga.url, mangaDex.id) var dbManga = getManga.await(networkManga.url, mangaDex.id)
.executeOnIO()
if (dbManga == null) { if (dbManga == null) {
dbManga = Manga.create( val newManga = Manga.create(
networkManga.url, networkManga.url,
networkManga.title, networkManga.title,
mangaDex.id, 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) updateManga.awaitUpdateFromSource(dbManga, networkManga.toMangaInfo(), true)
dbManga.favorite = true metadata.mangaId = dbManga.id
val id = db.insertManga(dbManga).executeOnIO().insertedId() insertFlatMetadata.await(metadata)
if (id != null) {
metadata.mangaId = id
insertFlatMetadata.await(metadata)
}
} }
notifier.cancelProgressNotification() 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.GetMergedChapterByMangaId
import eu.kanade.domain.chapter.interactor.SyncChaptersWithSource import eu.kanade.domain.chapter.interactor.SyncChaptersWithSource
import eu.kanade.domain.chapter.model.toDbChapter 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.Chapter
import eu.kanade.tachiyomi.data.database.models.Manga import eu.kanade.tachiyomi.data.database.models.Manga
import eu.kanade.tachiyomi.data.database.models.toDomainManga 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.source.online.HttpSource
import eu.kanade.tachiyomi.util.lang.withIOContext import eu.kanade.tachiyomi.util.lang.withIOContext
import eu.kanade.tachiyomi.util.shouldDownloadNewChapters import eu.kanade.tachiyomi.util.shouldDownloadNewChapters
import exh.log.xLogW
import exh.merged.sql.models.MergedMangaReference import exh.merged.sql.models.MergedMangaReference
import exh.source.MERGED_SOURCE_ID import exh.source.MERGED_SOURCE_ID
import kotlinx.coroutines.CancellationException 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 import eu.kanade.domain.manga.model.Manga as DomainManga
class MergedSource : HttpSource() { 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 getMergedChaptersByMangaId: GetMergedChapterByMangaId by injectLazy()
private val insertManga: InsertManga by injectLazy()
private val getCategories: GetCategories by injectLazy() private val getCategories: GetCategories by injectLazy()
private val sourceManager: SourceManager by injectLazy() private val sourceManager: SourceManager by injectLazy()
private val downloadManager: DownloadManager by injectLazy() private val downloadManager: DownloadManager by injectLazy()
@ -74,9 +79,8 @@ class MergedSource : HttpSource() {
override suspend fun getMangaDetails(manga: MangaInfo): MangaInfo { override suspend fun getMangaDetails(manga: MangaInfo): MangaInfo {
return withIOContext { return withIOContext {
val mergedManga = db.getManga(manga.key, id).executeAsBlocking() val mergedManga = getManga.await(manga.key, id) ?: throw Exception("merged manga not in db")
?: throw Exception("merged manga not in db") val mangaReferences = getMergedReferencesById.await(mergedManga.id)
val mangaReferences = db.getMergedMangaReferences(mergedManga.id!!).executeAsBlocking()
.apply { .apply {
if (isEmpty()) { if (isEmpty()) {
throw IllegalArgumentException( throw IllegalArgumentException(
@ -93,7 +97,7 @@ class MergedSource : HttpSource() {
val mangaInfoReference = mangaReferences.firstOrNull { it.isInfoManga } val mangaInfoReference = mangaReferences.firstOrNull { it.isInfoManga }
?: mangaReferences.firstOrNull { it.mangaId != it.mergeId } ?: mangaReferences.firstOrNull { it.mangaId != it.mergeId }
val dbManga = mangaInfoReference?.run { val dbManga = mangaInfoReference?.run {
db.getManga(mangaUrl, mangaSourceId).executeAsBlocking()?.toMangaInfo() getManga.await(mangaUrl, mangaSourceId)?.toMangaInfo()
} }
(dbManga ?: mergedManga.toMangaInfo()).copy( (dbManga ?: mergedManga.toMangaInfo()).copy(
key = manga.key, key = manga.key,
@ -102,8 +106,8 @@ class MergedSource : HttpSource() {
} }
// TODO more chapter dedupe // TODO more chapter dedupe
fun transformMergedChapters(mangaId: Long, chapterList: List<DomainChapter>, editScanlators: Boolean, dedupe: Boolean): List<DomainChapter> { suspend fun transformMergedChapters(mangaId: Long, chapterList: List<DomainChapter>, editScanlators: Boolean, dedupe: Boolean): List<DomainChapter> {
val mangaReferences = db.getMergedMangaReferences(mangaId).executeAsBlocking() val mangaReferences = getMergedReferencesById.await(mangaId)
val chapters = if (editScanlators) { val chapters = if (editScanlators) {
val sources = mangaReferences.map { sourceManager.getOrStub(it.mangaSourceId) to it.mangaId } val sources = mangaReferences.map { sourceManager.getOrStub(it.mangaSourceId) to it.mangaId }
chapterList.map { chapter -> chapterList.map { chapter ->
@ -127,7 +131,11 @@ class MergedSource : HttpSource() {
} }
fun getChaptersAsBlocking(mangaId: Long, editScanlators: Boolean = false, dedupe: Boolean = true): List<DomainChapter> { 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> { 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>> { suspend fun fetchChaptersAndSync(manga: DomainManga, downloadChapters: Boolean = true): Pair<List<DomainChapter>, List<DomainChapter>> {
val syncChaptersWithSource = Injekt.get<SyncChaptersWithSource>() val syncChaptersWithSource = Injekt.get<SyncChaptersWithSource>()
val mangaReferences = db.getMergedMangaReferences(manga.id).executeAsBlocking() val mangaReferences = getMergedReferencesById.await(manga.id)
if (mangaReferences.isEmpty()) { if (mangaReferences.isEmpty()) {
throw IllegalArgumentException("Manga references are empty, chapters unavailable, merge is likely corrupted") throw IllegalArgumentException("Manga references are empty, chapters unavailable, merge is likely corrupted")
} }
@ -183,15 +191,15 @@ class MergedSource : HttpSource() {
values.map { values.map {
try { try {
val (source, loadedManga, reference) = val (source, loadedManga, reference) =
it.load(db, sourceManager) it.load(sourceManager, getManga, insertManga)
if (loadedManga != null && reference.getChapterUpdates) { if (loadedManga != null && reference.getChapterUpdates) {
val chapterList = source.getChapterList(loadedManga.toMangaInfo()) val chapterList = source.getChapterList(loadedManga.toMangaInfo())
.map(ChapterInfo::toSChapter) .map(ChapterInfo::toSChapter)
val results = val results =
syncChaptersWithSource.await(chapterList, loadedManga.toDomainManga()!!, source) syncChaptersWithSource.await(chapterList, loadedManga, source)
if (ifDownloadNewChapters && reference.downloadChapters) { if (ifDownloadNewChapters && reference.downloadChapters) {
downloadManager.downloadChapters( downloadManager.downloadChapters(
loadedManga, loadedManga.toDbManga(),
results.first.map(DomainChapter::toDbChapter), results.first.map(DomainChapter::toDbChapter),
) )
} }
@ -218,26 +226,25 @@ class MergedSource : HttpSource() {
} }
} }
suspend fun MergedMangaReference.load(db: DatabaseHelper, sourceManager: SourceManager): LoadedMangaSource { suspend fun MergedMangaReference.load(sourceManager: SourceManager, getManga: GetManga, insertManga: InsertManga): LoadedMangaSource {
var manga = db.getManga(mangaUrl, mangaSourceId).executeAsBlocking() var manga = getManga.await(mangaUrl, mangaSourceId)
val source = sourceManager.getOrStub(manga?.source ?: mangaSourceId) val source = sourceManager.getOrStub(manga?.source ?: mangaSourceId)
if (manga == null) { if (manga == null) {
manga = Manga.create(mangaSourceId).apply { val newManga = Manga.create(mangaSourceId).apply {
url = mangaUrl url = mangaUrl
} }
manga.copyFrom(source.getMangaDetails(manga.toMangaInfo()).toSManga()) newManga.copyFrom(source.getMangaDetails(newManga.toMangaInfo()).toSManga())
try { newManga.id = -1
manga.id = db.insertManga(manga).executeAsBlocking().insertedId() val result = run {
mangaId = manga.id val id = insertManga.await(newManga.toDomainManga()!!)
db.insertNewMergedMangaId(this).executeAsBlocking() getManga.await(id!!)
} catch (e: Exception) {
xLogW("Error inserting merged manga id", e)
} }
manga = result
} }
return LoadedMangaSource(source, manga, this) 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 lang = "all"
override val supportsLatest = false override val supportsLatest = false

View File

@ -4,8 +4,13 @@ import android.os.Bundle
import eu.kanade.data.DatabaseHandler import eu.kanade.data.DatabaseHandler
import eu.kanade.data.exh.feedSavedSearchMapper import eu.kanade.data.exh.feedSavedSearchMapper
import eu.kanade.data.exh.savedSearchMapper 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.Manga
import eu.kanade.tachiyomi.data.database.models.toDomainManga
import eu.kanade.tachiyomi.data.database.models.toMangaInfo import eu.kanade.tachiyomi.data.database.models.toMangaInfo
import eu.kanade.tachiyomi.data.preference.PreferencesHelper import eu.kanade.tachiyomi.data.preference.PreferencesHelper
import eu.kanade.tachiyomi.source.CatalogueSource import eu.kanade.tachiyomi.source.CatalogueSource
@ -23,6 +28,7 @@ import exh.savedsearches.models.FeedSavedSearch
import exh.savedsearches.models.SavedSearch import exh.savedsearches.models.SavedSearch
import kotlinx.coroutines.flow.launchIn import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.onEach import kotlinx.coroutines.flow.onEach
import kotlinx.coroutines.runBlocking
import kotlinx.serialization.decodeFromString import kotlinx.serialization.decodeFromString
import kotlinx.serialization.json.Json import kotlinx.serialization.json.Json
import logcat.LogPriority 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. * Function calls should be done from here. UI calls should be done from the controller.
* *
* @param sourceManager manages the different sources. * @param sourceManager manages the different sources.
* @param database manages the database calls. * @param handler manages the database calls.
* @param preferences manages the preference calls. * @param preferences manages the preference calls.
*/ */
open class FeedPresenter( open class FeedPresenter(
val sourceManager: SourceManager = Injekt.get(), val sourceManager: SourceManager = Injekt.get(),
val database: DatabaseHandler = Injekt.get(), val handler: DatabaseHandler = Injekt.get(),
val db: DatabaseHelper = Injekt.get(),
val preferences: PreferencesHelper = 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>() { ) : BasePresenter<FeedController>() {
/** /**
@ -68,7 +76,7 @@ open class FeedPresenter(
override fun onCreate(savedState: Bundle?) { override fun onCreate(savedState: Bundle?) {
super.onCreate(savedState) super.onCreate(savedState)
database.subscribeToList { feed_saved_searchQueries.selectAllGlobal() } handler.subscribeToList { feed_saved_searchQueries.selectAllGlobal() }
.onEach { .onEach {
getFeed() getFeed()
} }
@ -82,7 +90,7 @@ open class FeedPresenter(
} }
suspend fun hasTooManyFeeds(): Boolean { suspend fun hasTooManyFeeds(): Boolean {
return database.awaitOne { feed_saved_searchQueries.countGlobal() } > 10 return handler.awaitOne { feed_saved_searchQueries.countGlobal() } > 10
} }
fun getEnabledSources(): List<CatalogueSource> { fun getEnabledSources(): List<CatalogueSource> {
@ -97,12 +105,12 @@ open class FeedPresenter(
} }
suspend fun getSourceSavedSearches(source: CatalogueSource): List<SavedSearch> { 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?) { fun createFeed(source: CatalogueSource, savedSearch: SavedSearch?) {
launchIO { launchIO {
database.await { handler.await {
feed_saved_searchQueries.insertFeedSavedSearch( feed_saved_searchQueries.insertFeedSavedSearch(
_id = null, _id = null,
source = source.id, source = source.id,
@ -115,17 +123,17 @@ open class FeedPresenter(
fun deleteFeed(feed: FeedSavedSearch) { fun deleteFeed(feed: FeedSavedSearch) {
launchIO { launchIO {
database.await { handler.await {
feed_saved_searchQueries.deleteById(feed.id ?: return@await) feed_saved_searchQueries.deleteById(feed.id ?: return@await)
} }
} }
} }
private suspend fun getSourcesToGetFeed(): List<Pair<FeedSavedSearch, SavedSearch?>> { private suspend fun getSourcesToGetFeed(): List<Pair<FeedSavedSearch, SavedSearch?>> {
val savedSearches = database.awaitList { val savedSearches = handler.awaitList {
feed_saved_searchQueries.selectGlobalFeedSavedSearch(savedSearchMapper) feed_saved_searchQueries.selectGlobalFeedSavedSearch(savedSearchMapper)
}.associateBy { it.id } }.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] } .map { it to savedSearches[it.savedSearch] }
} }
@ -263,7 +271,7 @@ open class FeedPresenter(
val networkManga = source.getMangaDetails(manga.toMangaInfo()) val networkManga = source.getMangaDetails(manga.toMangaInfo())
manga.copyFrom(networkManga.toSManga()) manga.copyFrom(networkManga.toSManga())
manga.initialized = true manga.initialized = true
db.insertManga(manga).executeAsBlocking() updateManga.await(manga.toDomainManga()!!.toMangaUpdate())
manga manga
} }
.onErrorResumeNext { Observable.just(manga) } .onErrorResumeNext { Observable.just(manga) }
@ -276,15 +284,22 @@ open class FeedPresenter(
* @param sManga the manga from the source. * @param sManga the manga from the source.
* @return a manga from the database. * @return a manga from the database.
*/ */
protected open fun networkToLocalManga(sManga: SManga, sourceId: Long): Manga { 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) { if (localManga == null) {
val newManga = Manga.create(sManga.url, sManga.title, sourceId) val newManga = Manga.create(sManga.url, sManga.title, sourceId)
newManga.copyFrom(sManga) newManga.copyFrom(sManga)
val result = db.insertManga(newManga).executeAsBlocking() newManga.id = -1
newManga.id = result.insertedId() val result = runBlocking {
localManga = newManga 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 android.view.MenuInflater
import androidx.appcompat.widget.SearchView import androidx.appcompat.widget.SearchView
import androidx.core.os.bundleOf 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.R
import eu.kanade.tachiyomi.data.database.DatabaseHelper
import eu.kanade.tachiyomi.data.database.models.Manga import eu.kanade.tachiyomi.data.database.models.Manga
import eu.kanade.tachiyomi.source.CatalogueSource import eu.kanade.tachiyomi.source.CatalogueSource
import eu.kanade.tachiyomi.source.SourceManager 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.filter
import kotlinx.coroutines.flow.launchIn import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.onEach import kotlinx.coroutines.flow.onEach
import kotlinx.coroutines.runBlocking
import reactivecircus.flowbinding.appcompat.QueryTextEvent import reactivecircus.flowbinding.appcompat.QueryTextEvent
import reactivecircus.flowbinding.appcompat.queryTextEvents import reactivecircus.flowbinding.appcompat.queryTextEvents
import uy.kohesive.injekt.Injekt import uy.kohesive.injekt.Injekt
@ -34,7 +36,11 @@ class SearchController(
) { ) {
constructor(targetController: MigrationListController?, mangaId: Long, sources: LongArray) : constructor(targetController: MigrationListController?, mangaId: Long, sources: LongArray) :
this( this(
Injekt.get<DatabaseHelper>().getManga(mangaId).executeAsBlocking(), runBlocking {
Injekt.get<GetManga>()
.await(mangaId)
?.toDbManga()
},
sources.map { Injekt.get<SourceManager>().getOrStub(it) }.filterIsInstance<CatalogueSource>(), sources.map { Injekt.get<SourceManager>().getOrStub(it) }.filterIsInstance<CatalogueSource>(),
) { ) {
this.targetController = targetController 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.GetChapterByMangaId
import eu.kanade.domain.chapter.interactor.SyncChaptersWithTrackServiceTwoWay import eu.kanade.domain.chapter.interactor.SyncChaptersWithTrackServiceTwoWay
import eu.kanade.domain.manga.interactor.GetDuplicateLibraryManga 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.toDbManga
import eu.kanade.domain.manga.model.toMangaUpdate
import eu.kanade.domain.track.interactor.InsertTrack import eu.kanade.domain.track.interactor.InsertTrack
import eu.kanade.domain.track.model.toDomainTrack import eu.kanade.domain.track.model.toDomainTrack
import eu.kanade.tachiyomi.data.cache.CoverCache 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.Manga
import eu.kanade.tachiyomi.data.database.models.toDomainManga import eu.kanade.tachiyomi.data.database.models.toDomainManga
import eu.kanade.tachiyomi.data.database.models.toMangaInfo import eu.kanade.tachiyomi.data.database.models.toMangaInfo
@ -87,14 +90,16 @@ open class BrowseSourcePresenter(
private val savedSearch: Long? = null, private val savedSearch: Long? = null,
// SY <-- // SY <--
private val sourceManager: SourceManager = Injekt.get(), private val sourceManager: SourceManager = Injekt.get(),
private val db: DatabaseHelper = Injekt.get(),
private val database: DatabaseHandler = Injekt.get(), private val database: DatabaseHandler = Injekt.get(),
private val prefs: PreferencesHelper = Injekt.get(), private val prefs: PreferencesHelper = Injekt.get(),
private val coverCache: CoverCache = Injekt.get(), private val coverCache: CoverCache = Injekt.get(),
private val getManga: GetManga = Injekt.get(),
private val getDuplicateLibraryManga: GetDuplicateLibraryManga = Injekt.get(), private val getDuplicateLibraryManga: GetDuplicateLibraryManga = Injekt.get(),
private val getCategories: GetCategories = Injekt.get(), private val getCategories: GetCategories = Injekt.get(),
private val getChapterByMangaId: GetChapterByMangaId = Injekt.get(), private val getChapterByMangaId: GetChapterByMangaId = Injekt.get(),
private val setMangaCategories: SetMangaCategories = 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 insertTrack: InsertTrack = Injekt.get(),
private val syncChaptersWithTrackServiceTwoWay: SyncChaptersWithTrackServiceTwoWay = Injekt.get(), private val syncChaptersWithTrackServiceTwoWay: SyncChaptersWithTrackServiceTwoWay = Injekt.get(),
) : BasePresenter<BrowseSourceController>() { ) : BasePresenter<BrowseSourceController>() {
@ -278,19 +283,22 @@ open class BrowseSourcePresenter(
* @return a manga from the database. * @return a manga from the database.
*/ */
private fun networkToLocalManga(sManga: SManga, sourceId: Long): Manga { 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) { if (localManga == null) {
val newManga = Manga.create(sManga.url, sManga.title, sourceId) val newManga = Manga.create(sManga.url, sManga.title, sourceId)
newManga.copyFrom(sManga) newManga.copyFrom(sManga)
val result = db.insertManga(newManga).executeAsBlocking() newManga.id = -1
newManga.id = result.insertedId() val result = runBlocking {
localManga = newManga val id = insertManga.await(newManga.toDomainManga()!!)
getManga.await(id!!)
}
localManga = result
} else if (!localManga.favorite) { } else if (!localManga.favorite) {
// if the manga isn't a favorite, set its display title from source // 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 // 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()) val networkManga = source.getMangaDetails(manga.toMangaInfo())
manga.copyFrom(networkManga.toSManga()) manga.copyFrom(networkManga.toSManga())
manga.initialized = true manga.initialized = true
db.insertManga(manga).executeAsBlocking() updateManga.await(
manga
.toDomainManga()
?.toMangaUpdate()!!,
)
} catch (e: Exception) { } catch (e: Exception) {
logcat(LogPriority.ERROR, e) logcat(LogPriority.ERROR, e)
} }
@ -352,7 +364,13 @@ open class BrowseSourcePresenter(
autoAddTrack(manga) autoAddTrack(manga)
} }
db.insertManga(manga).executeAsBlocking() runBlocking {
updateManga.await(
manga
.toDomainManga()
?.toMangaUpdate()!!,
)
}
} }
private fun autoAddTrack(manga: Manga) { 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.DatabaseHandler
import eu.kanade.data.exh.feedSavedSearchMapper import eu.kanade.data.exh.feedSavedSearchMapper
import eu.kanade.data.exh.savedSearchMapper 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.Manga
import eu.kanade.tachiyomi.data.database.models.toDomainManga
import eu.kanade.tachiyomi.data.database.models.toMangaInfo import eu.kanade.tachiyomi.data.database.models.toMangaInfo
import eu.kanade.tachiyomi.data.preference.PreferencesHelper import eu.kanade.tachiyomi.data.preference.PreferencesHelper
import eu.kanade.tachiyomi.source.CatalogueSource import eu.kanade.tachiyomi.source.CatalogueSource
@ -28,6 +33,7 @@ import exh.savedsearches.models.SavedSearch
import kotlinx.coroutines.CancellationException import kotlinx.coroutines.CancellationException
import kotlinx.coroutines.flow.launchIn import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.onEach import kotlinx.coroutines.flow.onEach
import kotlinx.coroutines.runBlocking
import kotlinx.serialization.decodeFromString import kotlinx.serialization.decodeFromString
import kotlinx.serialization.json.Json import kotlinx.serialization.json.Json
import kotlinx.serialization.json.JsonArray import kotlinx.serialization.json.JsonArray
@ -40,7 +46,6 @@ import rx.subjects.PublishSubject
import uy.kohesive.injekt.Injekt import uy.kohesive.injekt.Injekt
import uy.kohesive.injekt.api.get import uy.kohesive.injekt.api.get
import xyz.nulldev.ts.api.http.serializer.FilterSerializer import xyz.nulldev.ts.api.http.serializer.FilterSerializer
import java.lang.RuntimeException
sealed class SourceFeed { sealed class SourceFeed {
object Latest : 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. * Function calls should be done from here. UI calls should be done from the controller.
* *
* @param source the source. * @param source the source.
* @param database manages the database calls. * @param handler manages the database calls.
* @param preferences manages the preference calls. * @param preferences manages the preference calls.
*/ */
open class SourceFeedPresenter( open class SourceFeedPresenter(
val source: CatalogueSource, val source: CatalogueSource,
val database: DatabaseHandler = Injekt.get(), val handler: DatabaseHandler = Injekt.get(),
val db: DatabaseHelper = Injekt.get(),
val preferences: PreferencesHelper = 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>() { ) : BasePresenter<SourceFeedController>() {
/** /**
@ -98,7 +105,7 @@ open class SourceFeedPresenter(
sourceFilters = source.getFilterList() sourceFilters = source.getFilterList()
database.subscribeToList { feed_saved_searchQueries.selectSourceFeedSavedSearch(source.id, savedSearchMapper) } handler.subscribeToList { feed_saved_searchQueries.selectSourceFeedSavedSearch(source.id, savedSearchMapper) }
.onEach { .onEach {
getFeed() getFeed()
} }
@ -113,19 +120,19 @@ open class SourceFeedPresenter(
suspend fun hasTooManyFeeds(): Boolean { suspend fun hasTooManyFeeds(): Boolean {
return withIOContext { return withIOContext {
database.awaitList { handler.awaitList {
feed_saved_searchQueries.selectSourceFeedSavedSearch(source.id) feed_saved_searchQueries.selectSourceFeedSavedSearch(source.id)
}.size > 10 }.size > 10
} }
} }
suspend fun getSourceSavedSearches(): List<SavedSearch> { 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) { fun createFeed(savedSearchId: Long) {
launchIO { launchIO {
database.await { handler.await {
feed_saved_searchQueries.insertFeedSavedSearch( feed_saved_searchQueries.insertFeedSavedSearch(
_id = null, _id = null,
source = source.id, source = source.id,
@ -138,12 +145,12 @@ open class SourceFeedPresenter(
fun deleteFeed(feed: FeedSavedSearch) { fun deleteFeed(feed: FeedSavedSearch) {
launchIO { 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> { 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!! } .associateBy { it.id!! }
return listOfNotNull( return listOfNotNull(
@ -151,7 +158,7 @@ open class SourceFeedPresenter(
SourceFeed.Latest SourceFeed.Latest
} else null, } else null,
SourceFeed.Browse, 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]!!) } .map { SourceFeed.SourceSavedSearch(it, savedSearches[it.savedSearch]!!) }
} }
@ -284,7 +291,7 @@ open class SourceFeedPresenter(
val networkManga = source.getMangaDetails(manga.toMangaInfo()) val networkManga = source.getMangaDetails(manga.toMangaInfo())
manga.copyFrom(networkManga.toSManga()) manga.copyFrom(networkManga.toSManga())
manga.initialized = true manga.initialized = true
db.insertManga(manga).executeAsBlocking() updateManga.await(manga.toDomainManga()!!.toMangaUpdate())
manga manga
} }
.onErrorResumeNext { Observable.just(manga) } .onErrorResumeNext { Observable.just(manga) }
@ -297,21 +304,28 @@ open class SourceFeedPresenter(
* @param sManga the manga from the source. * @param sManga the manga from the source.
* @return a manga from the database. * @return a manga from the database.
*/ */
protected open fun networkToLocalManga(sManga: SManga, sourceId: Long): Manga { 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) { if (localManga == null) {
val newManga = Manga.create(sManga.url, sManga.title, sourceId) val newManga = Manga.create(sManga.url, sManga.title, sourceId)
newManga.copyFrom(sManga) newManga.copyFrom(sManga)
val result = db.insertManga(newManga).executeAsBlocking() newManga.id = -1
newManga.id = result.insertedId() val result = runBlocking {
localManga = newManga 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? { suspend fun loadSearch(searchId: Long): EXHSavedSearch? {
return withIOContext { return withIOContext {
val search = database.awaitOneOrNull { val search = handler.awaitOneOrNull {
saved_searchQueries.selectById(searchId, savedSearchMapper) saved_searchQueries.selectById(searchId, savedSearchMapper)
} ?: return@withIOContext null } ?: return@withIOContext null
EXHSavedSearch( EXHSavedSearch(
@ -334,7 +348,7 @@ open class SourceFeedPresenter(
suspend fun loadSearches(): List<EXHSavedSearch> { suspend fun loadSearches(): List<EXHSavedSearch> {
return withIOContext { 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( val filtersJson = it.filtersJson ?: return@map EXHSavedSearch(
id = it.id!!, id = it.id!!,
name = it.name, name = it.name,

View File

@ -1,8 +1,13 @@
package eu.kanade.tachiyomi.ui.browse.source.globalsearch package eu.kanade.tachiyomi.ui.browse.source.globalsearch
import android.os.Bundle 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.Manga
import eu.kanade.tachiyomi.data.database.models.toDomainManga
import eu.kanade.tachiyomi.data.database.models.toMangaInfo import eu.kanade.tachiyomi.data.database.models.toMangaInfo
import eu.kanade.tachiyomi.data.preference.PreferencesHelper import eu.kanade.tachiyomi.data.preference.PreferencesHelper
import eu.kanade.tachiyomi.extension.ExtensionManager 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.ui.browse.source.browse.BrowseSourcePresenter
import eu.kanade.tachiyomi.util.lang.runAsObservable import eu.kanade.tachiyomi.util.lang.runAsObservable
import eu.kanade.tachiyomi.util.system.logcat import eu.kanade.tachiyomi.util.system.logcat
import kotlinx.coroutines.runBlocking
import logcat.LogPriority import logcat.LogPriority
import rx.Observable import rx.Observable
import rx.Subscription import rx.Subscription
@ -39,8 +45,10 @@ open class GlobalSearchPresenter(
private val initialExtensionFilter: String? = null, private val initialExtensionFilter: String? = null,
private val sourcesToUse: List<CatalogueSource>? = null, private val sourcesToUse: List<CatalogueSource>? = null,
val sourceManager: SourceManager = Injekt.get(), val sourceManager: SourceManager = Injekt.get(),
val db: DatabaseHelper = Injekt.get(),
val preferences: PreferencesHelper = 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>() { ) : BasePresenter<GlobalSearchController>() {
/** /**
@ -250,7 +258,7 @@ open class GlobalSearchPresenter(
val networkManga = source.getMangaDetails(manga.toMangaInfo()) val networkManga = source.getMangaDetails(manga.toMangaInfo())
manga.copyFrom(networkManga.toSManga()) manga.copyFrom(networkManga.toSManga())
manga.initialized = true manga.initialized = true
db.insertManga(manga).executeAsBlocking() runBlocking { updateManga.await(manga.toDomainManga()!!.toMangaUpdate()) }
return manga return manga
} }
@ -262,18 +270,21 @@ open class GlobalSearchPresenter(
* @return a manga from the database. * @return a manga from the database.
*/ */
protected open fun networkToLocalManga(sManga: SManga, sourceId: Long): Manga { 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) { if (localManga == null) {
val newManga = Manga.create(sManga.url, sManga.title, sourceId) val newManga = Manga.create(sManga.url, sManga.title, sourceId)
newManga.copyFrom(sManga) newManga.copyFrom(sManga)
val result = db.insertManga(newManga).executeAsBlocking() newManga.id = -1
newManga.id = result.insertedId() val result = runBlocking {
localManga = newManga val id = insertManga.await(newManga.toDomainManga()!!)
getManga.await(id!!)
}
localManga = result
} else if (!localManga.favorite) { } else if (!localManga.favorite) {
// if the manga isn't a favorite, set its display title from source // 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 // 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 package eu.kanade.tachiyomi.ui.category.sources
import android.os.Bundle import android.os.Bundle
import eu.kanade.tachiyomi.data.database.DatabaseHelper
import eu.kanade.tachiyomi.data.preference.PreferencesHelper import eu.kanade.tachiyomi.data.preference.PreferencesHelper
import eu.kanade.tachiyomi.ui.base.presenter.BasePresenter import eu.kanade.tachiyomi.ui.base.presenter.BasePresenter
import kotlinx.coroutines.flow.launchIn 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. * Presenter of [SourceCategoryController]. Used to manage the categories of the library.
*/ */
class SourceCategoryPresenter( class SourceCategoryPresenter : BasePresenter<SourceCategoryController>() {
private val db: DatabaseHelper = Injekt.get(),
) : BasePresenter<SourceCategoryController>() {
/** /**
* List containing categories. * List containing categories.

View File

@ -3,7 +3,6 @@ package eu.kanade.tachiyomi.ui.library
import android.os.Bundle import android.os.Bundle
import com.jakewharton.rxrelay.BehaviorRelay import com.jakewharton.rxrelay.BehaviorRelay
import eu.kanade.core.util.asObservable import eu.kanade.core.util.asObservable
import eu.kanade.data.AndroidDatabaseHandler
import eu.kanade.data.DatabaseHandler import eu.kanade.data.DatabaseHandler
import eu.kanade.domain.category.interactor.GetCategories import eu.kanade.domain.category.interactor.GetCategories
import eu.kanade.domain.category.interactor.SetMangaCategories 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.interactor.UpdateChapter
import eu.kanade.domain.chapter.model.ChapterUpdate import eu.kanade.domain.chapter.model.ChapterUpdate
import eu.kanade.domain.chapter.model.toDbChapter 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.GetMergedMangaById
import eu.kanade.domain.manga.interactor.UpdateManga import eu.kanade.domain.manga.interactor.UpdateManga
import eu.kanade.domain.manga.model.Manga import eu.kanade.domain.manga.model.Manga
@ -74,6 +74,7 @@ typealias LibraryMap = Map<Long, List<LibraryItem>>
*/ */
class LibraryPresenter( class LibraryPresenter(
private val handler: DatabaseHandler = Injekt.get(), private val handler: DatabaseHandler = Injekt.get(),
private val getLibraryManga: GetLibraryManga = Injekt.get(),
private val getTracks: GetTracks = Injekt.get(), private val getTracks: GetTracks = Injekt.get(),
private val getCategories: GetCategories = Injekt.get(), private val getCategories: GetCategories = Injekt.get(),
private val getChapterByMangaId: GetChapterByMangaId = Injekt.get(), private val getChapterByMangaId: GetChapterByMangaId = Injekt.get(),
@ -561,39 +562,7 @@ class LibraryPresenter(
val defaultLibraryDisplayMode = preferences.libraryDisplayMode() val defaultLibraryDisplayMode = preferences.libraryDisplayMode()
val shouldSetFromCategory = preferences.categorizedDisplaySettings() val shouldSetFromCategory = preferences.categorizedDisplaySettings()
// TODO: Move this to domain/data layer return getLibraryManga.subscribe().asObservable()
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()
.map { list -> .map { list ->
list.map { libraryManga -> list.map { libraryManga ->
// Display mode based on user preference: take it from global library setting or category // 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() { private fun openMergedSettingsDialog() {
EditMergedSettingsDialog( EditMergedSettingsDialog(
this, this,
presenter.manga!!.toDbManga(), presenter.manga ?: return,
).showDialog(router) ).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.interactor.UpdateChapter
import eu.kanade.domain.chapter.model.ChapterUpdate import eu.kanade.domain.chapter.model.ChapterUpdate
import eu.kanade.domain.chapter.model.toDbChapter 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.GetDuplicateLibraryManga
import eu.kanade.domain.manga.interactor.GetFlatMetadataById import eu.kanade.domain.manga.interactor.GetFlatMetadataById
import eu.kanade.domain.manga.interactor.GetManga import eu.kanade.domain.manga.interactor.GetManga
import eu.kanade.domain.manga.interactor.GetMangaWithChapters import eu.kanade.domain.manga.interactor.GetMangaWithChapters
import eu.kanade.domain.manga.interactor.GetMergedMangaById import eu.kanade.domain.manga.interactor.GetMergedMangaById
import eu.kanade.domain.manga.interactor.GetMergedReferencesById 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.SetMangaChapterFlags
import eu.kanade.domain.manga.interactor.SetMangaFilteredScanlators import eu.kanade.domain.manga.interactor.SetMangaFilteredScanlators
import eu.kanade.domain.manga.interactor.UpdateManga 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.MangaUpdate
import eu.kanade.domain.manga.model.MergeMangaSettingsUpdate
import eu.kanade.domain.manga.model.TriStateFilter import eu.kanade.domain.manga.model.TriStateFilter
import eu.kanade.domain.manga.model.isLocal import eu.kanade.domain.manga.model.isLocal
import eu.kanade.domain.manga.model.toDbManga 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.toDbTrack
import eu.kanade.domain.track.model.toDomainTrack import eu.kanade.domain.track.model.toDomainTrack
import eu.kanade.tachiyomi.R 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.Chapter
import eu.kanade.tachiyomi.data.database.models.Track import eu.kanade.tachiyomi.data.database.models.Track
import eu.kanade.tachiyomi.data.database.models.toDomainChapter import eu.kanade.tachiyomi.data.database.models.toDomainChapter
@ -127,6 +132,11 @@ class MangaPresenter(
private val getMergedChapterByMangaId: GetMergedChapterByMangaId = Injekt.get(), private val getMergedChapterByMangaId: GetMergedChapterByMangaId = Injekt.get(),
private val getMergedMangaById: GetMergedMangaById = Injekt.get(), private val getMergedMangaById: GetMergedMangaById = Injekt.get(),
private val getMergedReferencesById: GetMergedReferencesById = 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(), private val getFlatMetadata: GetFlatMetadataById = Injekt.get(),
// SY <-- // SY <--
private val getDuplicateLibraryManga: GetDuplicateLibraryManga = Injekt.get(), private val getDuplicateLibraryManga: GetDuplicateLibraryManga = Injekt.get(),
@ -223,7 +233,7 @@ class MangaPresenter(
presenterScope.launchIO { presenterScope.launchIO {
if (!getMangaAndChapters.awaitManga(mangaId).favorite) { if (!getMangaAndChapters.awaitManga(mangaId).favorite) {
ChapterSettingsHelper.applySettingDefaults(mangaId, setMangaChapterFlags) ChapterSettingsHelper.applySettingDefaults(mangaId)
} }
getMangaAndChapters.subscribe(mangaId) getMangaAndChapters.subscribe(mangaId)
@ -431,8 +441,6 @@ class MangaPresenter(
} }
suspend fun smartSearchMerge(context: Context, manga: DomainManga, originalMangaId: Long): DomainManga { suspend fun smartSearchMerge(context: Context, manga: DomainManga, originalMangaId: Long): DomainManga {
val db = Injekt.get<DatabaseHelper>()
val originalManga = getManga.await(originalMangaId) val originalManga = getManga.await(originalMangaId)
?: throw IllegalArgumentException(context.getString(R.string.merge_unknown_manga, originalMangaId)) ?: throw IllegalArgumentException(context.getString(R.string.merge_unknown_manga, originalMangaId))
if (originalManga.source == MERGED_SOURCE_ID) { if (originalManga.source == MERGED_SOURCE_ID) {
@ -474,7 +482,7 @@ class MangaPresenter(
} }
// todo // todo
db.insertMergedMangas(mangaReferences).executeAsBlocking() insertMergedReference.awaitAll(mangaReferences)
return originalManga return originalManga
} else { } else {
@ -494,8 +502,10 @@ class MangaPresenter(
throw IllegalArgumentException(context.getString(R.string.merge_duplicate)) throw IllegalArgumentException(context.getString(R.string.merge_duplicate))
} else if (!existingManga.favorite) { } else if (!existingManga.favorite) {
withContext(NonCancellable) { withContext(NonCancellable) {
db.deleteManga(existingManga!!.toDbManga()).executeAsBlocking() existingManga?.id?.let {
db.deleteMangaForMergedManga(existingManga!!.id).executeAsBlocking() deleteByMergeId.await(it)
deleteMangaById.await(it)
}
} }
} }
existingManga = getManga.await(mergedManga.url, mergedManga.source) existingManga = getManga.await(mergedManga.url, mergedManga.source)
@ -503,13 +513,13 @@ class MangaPresenter(
// Reload chapters immediately // Reload chapters immediately
mergedManga.initialized = false mergedManga.initialized = false
mergedManga.id = -1
val newId = db.insertManga(mergedManga).executeAsBlocking().insertedId() val newId = insertManga.await(mergedManga.toDomainManga()!!)
if (newId != null) mergedManga.id = newId mergedManga.id = newId ?: throw NullPointerException("Invalid new manga id")
getCategories.await(originalMangaId) getCategories.await(originalMangaId)
.let { .let {
setMangaCategories.await(mergedManga.id!!, it.map { it.id }) setMangaCategories.await(newId, it.map { it.id })
} }
val originalMangaReference = MergedMangaReference( val originalMangaReference = MergedMangaReference(
@ -554,7 +564,7 @@ class MangaPresenter(
mangaSourceId = MERGED_SOURCE_ID, mangaSourceId = MERGED_SOURCE_ID,
) )
db.insertMergedMangas(listOf(originalMangaReference, newMangaReference, mergedMangaReference)).executeAsBlocking() insertMergedReference.awaitAll(listOf(originalMangaReference, newMangaReference, mergedMangaReference))
return mergedManga.toDomainManga()!! 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 // 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 { launchIO {
// todo if (mergedMangaReferences.isNotEmpty()) {
val db = Injekt.get<DatabaseHelper>() updateMergedSettings.awaitAll(
mergeReference?.let { mergedMangaReferences.map {
db.updateMergeMangaSettings(it).executeAsBlocking() 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.FlexibleAdapter
import eu.davidea.flexibleadapter.items.AbstractFlexibleItem import eu.davidea.flexibleadapter.items.AbstractFlexibleItem
import eu.davidea.flexibleadapter.items.IFlexible import eu.davidea.flexibleadapter.items.IFlexible
import eu.kanade.domain.manga.model.Manga
import eu.kanade.tachiyomi.R import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.data.database.models.Manga
import eu.kanade.tachiyomi.databinding.EditMergedSettingsItemBinding import eu.kanade.tachiyomi.databinding.EditMergedSettingsItemBinding
import exh.merged.sql.models.MergedMangaReference 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.ConcatAdapter
import androidx.recyclerview.widget.LinearLayoutManager import androidx.recyclerview.widget.LinearLayoutManager
import com.google.android.material.dialog.MaterialAlertDialogBuilder 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.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.databinding.EditMergedSettingsDialogBinding
import eu.kanade.tachiyomi.ui.base.controller.DialogController import eu.kanade.tachiyomi.ui.base.controller.DialogController
import eu.kanade.tachiyomi.ui.manga.MangaController import eu.kanade.tachiyomi.ui.manga.MangaController
import eu.kanade.tachiyomi.util.lang.launchIO
import eu.kanade.tachiyomi.util.system.toast import eu.kanade.tachiyomi.util.system.toast
import exh.merged.sql.models.MergedMangaReference import exh.merged.sql.models.MergedMangaReference
import exh.source.MERGED_SOURCE_ID 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 import uy.kohesive.injekt.injectLazy
class EditMergedSettingsDialog : DialogController, EditMergedMangaAdapter.EditMergedMangaItemListener { class EditMergedSettingsDialog : DialogController, EditMergedMangaAdapter.EditMergedMangaItemListener {
@ -27,13 +34,15 @@ class EditMergedSettingsDialog : DialogController, EditMergedMangaAdapter.EditMe
lateinit var binding: EditMergedSettingsDialogBinding 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 private val mangaController
get() = targetController as MangaController get() = targetController as MangaController
constructor(target: MangaController, manga: Manga) : super( constructor(target: MangaController, manga: Manga) : super(
bundleOf(KEY_MANGA to manga.id!!), bundleOf(KEY_MANGA to manga.id),
) { ) {
targetController = target targetController = target
this.manga = manga this.manga = manga
@ -41,8 +50,7 @@ class EditMergedSettingsDialog : DialogController, EditMergedMangaAdapter.EditMe
@Suppress("unused") @Suppress("unused")
constructor(bundle: Bundle) : super(bundle) { constructor(bundle: Bundle) : super(bundle) {
manga = db.getManga(bundle.getLong(KEY_MANGA)) manga = runBlocking { Injekt.get<GetManga>().await(bundle.getLong(KEY_MANGA))!! }
.executeAsBlocking()!!
} }
private var mergedHeaderAdapter: EditMergedSettingsHeaderAdapter? = null private var mergedHeaderAdapter: EditMergedSettingsHeaderAdapter? = null
@ -62,8 +70,8 @@ class EditMergedSettingsDialog : DialogController, EditMergedMangaAdapter.EditMe
} }
fun onViewCreated() { fun onViewCreated() {
val mergedManga = db.getMergedMangas(manga.id!!).executeAsBlocking() val mergedManga = runBlocking { getMergedMangaById.await(manga.id) }
val mergedReferences = db.getMergedMangaReferences(manga.id!!).executeAsBlocking() val mergedReferences = runBlocking { getMergedReferencesById.await(manga.id) }
if (mergedReferences.isEmpty() || mergedReferences.size == 1) { if (mergedReferences.isEmpty() || mergedReferences.size == 1) {
activity?.toast(R.string.merged_references_invalid) activity?.toast(R.string.merged_references_invalid)
router.popCurrentController() router.popCurrentController()
@ -85,7 +93,7 @@ class EditMergedSettingsDialog : DialogController, EditMergedMangaAdapter.EditMe
} }
private fun onPositiveButtonClick() { 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) { override fun onItemReleased(position: Int) {
@ -105,7 +113,9 @@ class EditMergedSettingsDialog : DialogController, EditMergedMangaAdapter.EditMe
.setTitle(R.string.delete_merged_manga) .setTitle(R.string.delete_merged_manga)
.setMessage(R.string.delete_merged_manga_desc) .setMessage(R.string.delete_merged_manga_desc)
.setPositiveButton(android.R.string.ok) { _, _ -> .setPositiveButton(android.R.string.ok) { _, _ ->
db.deleteMergedManga(mergeMangaReference).executeAsBlocking() launchIO {
deleteMergeById.await(mergeMangaReference.id!!)
}
dialog?.dismiss() dialog?.dismiss()
mangaController.router.popController(mangaController) 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.history.model.HistoryUpdate
import eu.kanade.domain.manga.interactor.GetFlatMetadataById import eu.kanade.domain.manga.interactor.GetFlatMetadataById
import eu.kanade.domain.manga.interactor.GetManga 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.isLocal
import eu.kanade.domain.manga.model.toDbManga import eu.kanade.domain.manga.model.toDbManga
import eu.kanade.domain.track.interactor.GetTracks import eu.kanade.domain.track.interactor.GetTracks
import eu.kanade.domain.track.interactor.InsertTrack import eu.kanade.domain.track.interactor.InsertTrack
import eu.kanade.domain.track.model.toDbTrack 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.Chapter
import eu.kanade.tachiyomi.data.database.models.Manga import eu.kanade.tachiyomi.data.database.models.Manga
import eu.kanade.tachiyomi.data.database.models.toDomainManga 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. * Presenter used by the activity to perform background operations.
*/ */
class ReaderPresenter( class ReaderPresenter(
private val db: DatabaseHelper = Injekt.get(),
private val sourceManager: SourceManager = Injekt.get(), private val sourceManager: SourceManager = Injekt.get(),
private val downloadManager: DownloadManager = Injekt.get(), private val downloadManager: DownloadManager = Injekt.get(),
private val preferences: PreferencesHelper = Injekt.get(), private val preferences: PreferencesHelper = Injekt.get(),
@ -97,8 +98,11 @@ class ReaderPresenter(
private val insertTrack: InsertTrack = Injekt.get(), private val insertTrack: InsertTrack = Injekt.get(),
private val upsertHistory: UpsertHistory = Injekt.get(), private val upsertHistory: UpsertHistory = Injekt.get(),
private val updateChapter: UpdateChapter = Injekt.get(), private val updateChapter: UpdateChapter = Injekt.get(),
private val setMangaViewerFlags: SetMangaViewerFlags = Injekt.get(),
// SY --> // SY -->
private val getFlatMetadataById: GetFlatMetadataById, private val getFlatMetadataById: GetFlatMetadataById = Injekt.get(),
private val getMergedManga: GetMergedManga = Injekt.get(),
private val getMergedReferencesById: GetMergedReferencesById = Injekt.get(),
// SY <-- // SY <--
) : BasePresenter<ReaderActivity>() { ) : BasePresenter<ReaderActivity>() {
@ -314,8 +318,8 @@ class ReaderPresenter(
val context = Injekt.get<Application>() val context = Injekt.get<Application>()
val source = sourceManager.getOrStub(manga.source) val source = sourceManager.getOrStub(manga.source)
val mergedReferences = if (source is MergedSource) db.getMergedMangaReferences(manga.id!!).executeAsBlocking() else emptyList() val mergedReferences = if (source is MergedSource) runBlocking { getMergedReferencesById.await(manga.id!!) } else emptyList()
mergedManga = if (source is MergedSource) db.getMergedMangas(manga.id!!).executeAsBlocking().associateBy { it.id!! } else emptyMap() 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()) loader = ChapterLoader(context, downloadManager, manga, source, sourceManager, mergedReferences, mergedManga ?: emptyMap())
Observable.just(manga).subscribeLatestCache(ReaderActivity::setManga) Observable.just(manga).subscribeLatestCache(ReaderActivity::setManga)
@ -648,7 +652,14 @@ class ReaderPresenter(
fun toggleBookmark(chapterId: Long, bookmarked: Boolean) { fun toggleBookmark(chapterId: Long, bookmarked: Boolean) {
val chapter = chapterList.find { it.chapter.id == chapterId }?.chapter ?: return val chapter = chapterList.find { it.chapter.id == chapterId }?.chapter ?: return
chapter.bookmark = bookmarked chapter.bookmark = bookmarked
db.updateChapterProgress(chapter).executeAsBlocking() launchIO {
updateChapter.await(
ChapterUpdate(
id = chapter.id!!.toLong(),
bookmark = bookmarked,
),
)
}
} }
// SY <-- // SY <--
@ -677,7 +688,9 @@ class ReaderPresenter(
fun setMangaReadingMode(readingModeType: Int) { fun setMangaReadingMode(readingModeType: Int) {
val manga = manga ?: return val manga = manga ?: return
manga.readingModeType = readingModeType manga.readingModeType = readingModeType
db.updateViewerFlags(manga).executeAsBlocking() runBlocking {
setMangaViewerFlags.awaitSetMangaReadingMode(manga.id!!.toLong(), readingModeType.toLong())
}
Observable.timer(250, TimeUnit.MILLISECONDS, AndroidSchedulers.mainThread()) Observable.timer(250, TimeUnit.MILLISECONDS, AndroidSchedulers.mainThread())
.subscribeFirst({ view, _ -> .subscribeFirst({ view, _ ->
@ -712,7 +725,9 @@ class ReaderPresenter(
fun setMangaOrientationType(rotationType: Int) { fun setMangaOrientationType(rotationType: Int) {
val manga = manga ?: return val manga = manga ?: return
manga.orientationType = rotationType manga.orientationType = rotationType
db.updateViewerFlags(manga).executeAsBlocking() runBlocking {
setMangaViewerFlags.awaitSetOrientationType(manga.id!!.toLong(), rotationType.toLong())
}
logcat(LogPriority.INFO) { "Manga orientation is ${manga.orientationType}" } logcat(LogPriority.INFO) { "Manga orientation is ${manga.orientationType}" }

View File

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

View File

@ -2,21 +2,21 @@
package exh package exh
import android.content.Context
import androidx.core.content.edit import androidx.core.content.edit
import androidx.preference.PreferenceManager import androidx.preference.PreferenceManager
import com.pushtorefresh.storio.sqlite.queries.Query
import eu.kanade.data.DatabaseHandler 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.GetManga
import eu.kanade.domain.manga.interactor.GetMangaBySource 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.interactor.UpdateManga
import eu.kanade.domain.manga.model.MangaUpdate import eu.kanade.domain.manga.model.MangaUpdate
import eu.kanade.tachiyomi.BuildConfig import eu.kanade.tachiyomi.BuildConfig
import eu.kanade.tachiyomi.data.backup.BackupCreatorJob 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.models.Manga
import eu.kanade.tachiyomi.data.database.tables.ChapterTable
import eu.kanade.tachiyomi.data.library.LibraryUpdateJob import eu.kanade.tachiyomi.data.library.LibraryUpdateJob
import eu.kanade.tachiyomi.data.preference.MANGA_NON_COMPLETED import eu.kanade.tachiyomi.data.preference.MANGA_NON_COMPLETED
import eu.kanade.tachiyomi.data.preference.PreferenceKeys 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 eu.kanade.tachiyomi.util.system.DeviceUtil
import exh.eh.EHentaiUpdateWorker import exh.eh.EHentaiUpdateWorker
import exh.log.xLogE import exh.log.xLogE
import exh.log.xLogW
import exh.merged.sql.models.MergedMangaReference import exh.merged.sql.models.MergedMangaReference
import exh.source.BlacklistedSources import exh.source.BlacklistedSources
import exh.source.EH_SOURCE_ID import exh.source.EH_SOURCE_ID
@ -69,12 +68,14 @@ import java.net.URISyntaxException
import eu.kanade.domain.manga.model.Manga as DomainManga import eu.kanade.domain.manga.model.Manga as DomainManga
object EXHMigrations { object EXHMigrations {
private val db: DatabaseHelper by injectLazy()
private val handler: DatabaseHandler by injectLazy() private val handler: DatabaseHandler by injectLazy()
private val sourceManager: SourceManager by injectLazy() private val sourceManager: SourceManager by injectLazy()
private val getManga: GetManga by injectLazy() private val getManga: GetManga by injectLazy()
private val getMangaBySource: GetMangaBySource by injectLazy() private val getMangaBySource: GetMangaBySource by injectLazy()
private val updateManga: UpdateManga 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. * Performs a migration when the application is updated.
@ -125,89 +126,73 @@ object EXHMigrations {
updateSourceId(NHentai.otherId, 6907) updateSourceId(NHentai.otherId, 6907)
} }
if (oldVersion under 7) { 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()) { if (mergedMangas.isNotEmpty()) {
val mangaConfigs = mergedMangas.mapNotNull { mergedManga -> readMangaConfig(mergedManga)?.let { mergedManga to it } } val mangaConfigs = mergedMangas.mapNotNull { mergedManga -> readMangaConfig(mergedManga)?.let { mergedManga to it } }
if (mangaConfigs.isNotEmpty()) { if (mangaConfigs.isNotEmpty()) {
val mangaToUpdate = mutableListOf<MangaUpdate>() val mangaToUpdate = mutableListOf<MangaUpdate>()
val mergedMangaReferences = mutableListOf<MergedMangaReference>() val mergedMangaReferences = mutableListOf<MergedMangaReference>()
mangaConfigs.onEach { mergedManga -> mangaConfigs.onEach { mergedManga ->
val newFirst = mergedManga.second.children.firstOrNull()?.url?.let { val newFirst = mergedManga.second.children.firstOrNull()?.url?.let {
if (runBlocking { getManga.await(it, MERGED_SOURCE_ID) } != null) return@onEach if (runBlocking { getManga.await(it, MERGED_SOURCE_ID) } != null) return@onEach
mangaToUpdate += MangaUpdate(id = mergedManga.first.id, url = it) mangaToUpdate += MangaUpdate(id = mergedManga.first.id, url = it)
mergedManga.first.copy(url = it) mergedManga.first.copy(url = it)
} ?: mergedManga.first } ?: 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( mergedMangaReferences += MergedMangaReference(
id = null, id = null,
isInfoManga = false, isInfoManga = index == 0,
getChapterUpdates = false, getChapterUpdates = true,
chapterSortMode = 0, chapterSortMode = 0,
chapterPriority = 0, chapterPriority = 0,
downloadChapters = false, downloadChapters = true,
mergeId = mergedManga.first.id, mergeId = newFirst.id,
mergeUrl = mergedManga.first.url, mergeUrl = newFirst.url,
mangaId = mergedManga.first.id, mangaId = load.manga.id,
mangaUrl = mergedManga.first.url, mangaUrl = load.manga.url,
mangaSourceId = MERGED_SOURCE_ID, 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) runBlocking {
} updateManga.awaitAll(mangaToUpdate)
db.insertMergedMangas(mergedMangaReferences).executeAsBlocking() insertMergedReference.awaitAll(mergedMangaReferences)
}
val loadedMangaList = mangaConfigs.map { it.second.children }.flatten().mapNotNull { it.load(db, sourceManager) }.distinct() val loadedMangaList = mangaConfigs.map { it.second.children }.flatten().mapNotNull { it.load() }.distinct()
val chapters = db.db.get() val chapters = runBlocking { handler.awaitList { ehQueries.getChaptersByMangaIds(mergedMangas.map { it.id }, chapterMapper) } }
.listOfObjects(Chapter::class.java) val mergedMangaChapters = runBlocking { handler.awaitList { ehQueries.getChaptersByMangaIds(loadedMangaList.map { it.manga.id }, chapterMapper) } }
.withQuery(
Query.builder() val mergedMangaChaptersMatched = mergedMangaChapters.mapNotNull { chapter -> loadedMangaList.firstOrNull { it.manga.id == chapter.id }?.let { it to chapter } }
.table(ChapterTable.TABLE) val parsedChapters = chapters.filter { it.read || it.lastPageRead != 0L }.mapNotNull { chapter -> readUrlConfig(chapter.url)?.let { chapter to it } }
.where("${ChapterTable.COL_MANGA_ID} IN (${mergedMangas.joinToString { it.id.toString() }})") val chaptersToUpdate = mutableListOf<ChapterUpdate>()
.build(), 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 { private fun getUrlWithoutDomain(orig: String): String {
return try { return try {
val uri = URI(orig) val uri = URI(orig)
@ -536,8 +509,8 @@ object EXHMigrations {
@SerialName("u") @SerialName("u")
val url: String, val url: String,
) { ) {
fun load(db: DatabaseHelper, sourceManager: SourceManager): LoadedMangaSource? { fun load(): LoadedMangaSource? {
val manga = db.getManga(url, source).executeAsBlocking() ?: return null val manga = runBlocking { getManga.await(url, source) } ?: return null
val source = sourceManager.getOrStub(source) val source = sourceManager.getOrStub(source)
return LoadedMangaSource(source, manga) 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) { private fun updateSourceId(newId: Long, oldId: Long) {
runBlocking { runBlocking {

View File

@ -2,7 +2,6 @@ package exh.debug
import android.app.Application import android.app.Application
import androidx.work.WorkManager import androidx.work.WorkManager
import eu.kanade.data.AndroidDatabaseHandler
import eu.kanade.data.DatabaseHandler import eu.kanade.data.DatabaseHandler
import eu.kanade.domain.manga.interactor.GetAllManga import eu.kanade.domain.manga.interactor.GetAllManga
import eu.kanade.domain.manga.interactor.GetExhFavoriteMangaWithMetadata 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.InsertFlatMetadata
import eu.kanade.domain.manga.interactor.UpdateManga import eu.kanade.domain.manga.interactor.UpdateManga
import eu.kanade.domain.manga.model.toMangaInfo 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.data.preference.PreferencesHelper
import eu.kanade.tachiyomi.source.SourceManager import eu.kanade.tachiyomi.source.SourceManager
import eu.kanade.tachiyomi.source.online.all.NHentai import eu.kanade.tachiyomi.source.online.all.NHentai
@ -110,16 +108,7 @@ object DebugFunctions {
} }
fun addAllMangaInDatabaseToLibrary() { fun addAllMangaInDatabaseToLibrary() {
(handler as AndroidDatabaseHandler).rawQuery { runBlocking { handler.await { ehQueries.addAllMangaInDatabaseToLibrary() } }
it.execute(
null,
"""
UPDATE ${MangaTable.TABLE}
SET ${MangaTable.COL_FAVORITE} = 1
""".trimIndent(),
0,
)
}
} }
fun countMangaInDatabaseInLibrary() = runBlocking { getFavorites.await().size } fun countMangaInDatabaseInLibrary() = runBlocking { getFavorites.await().size }
@ -200,16 +189,8 @@ object DebugFunctions {
fun cancelAllScheduledJobs() = app.jobScheduler.cancelAll() fun cancelAllScheduledJobs() = app.jobScheduler.cancelAll()
private fun convertSources(from: Long, to: Long) { private fun convertSources(from: Long, to: Long) {
(handler as AndroidDatabaseHandler).rawQuery { runBlocking {
it.execute( handler.await { ehQueries.migrateSource(to, from) }
null,
"""
UPDATE ${MangaTable.TABLE}
SET ${MangaTable.COL_SOURCE} = $to
WHERE ${MangaTable.COL_SOURCE} = $from
""".trimIndent(),
0,
)
} }
} }
@ -292,60 +273,20 @@ object DebugFunctions {
}*/ }*/
fun fixReaderViewerBackupBug() { fun fixReaderViewerBackupBug() {
(handler as AndroidDatabaseHandler).rawQuery { runBlocking { handler.await { ehQueries.fixReaderViewerBackupBug() } }
it.execute(
null,
"""
UPDATE ${MangaTable.TABLE}
SET ${MangaTable.COL_VIEWER} = 0
WHERE ${MangaTable.COL_VIEWER} = -1
""".trimIndent(),
0,
)
}
} }
fun resetReaderViewerForAllManga() { fun resetReaderViewerForAllManga() {
(handler as AndroidDatabaseHandler).rawQuery { runBlocking { handler.await { ehQueries.resetReaderViewerForAllManga() } }
it.execute(
null,
"""
UPDATE ${MangaTable.TABLE}
SET ${MangaTable.COL_VIEWER} = 0
""".trimIndent(),
0,
)
}
} }
fun migrateAllNhentaiToOtherLang() { fun migrateAllNhentaiToOtherLang() {
val sources = nHentaiSourceIds.toMutableList() val sources = nHentaiSourceIds - NHentai.otherId
.also { it.remove(NHentai.otherId) }
.joinToString(separator = ",")
(handler as AndroidDatabaseHandler).rawQuery { runBlocking { handler.await { ehQueries.migrateAllNhentaiToOtherLang(NHentai.otherId, sources) } }
it.execute(
null,
"""
UPDATE ${MangaTable.TABLE}
SET ${MangaTable.COL_SOURCE} = ${NHentai.otherId}
WHERE ${MangaTable.COL_FAVORITE} = 1 AND ${MangaTable.COL_SOURCE} in ($sources)
""".trimIndent(),
0,
)
}
} }
fun resetFilteredScanlatorsForAllManga() { fun resetFilteredScanlatorsForAllManga() {
(handler as AndroidDatabaseHandler).rawQuery { runBlocking { handler.await { ehQueries.resetFilteredScanlatorsForAllManga() } }
it.execute(
null,
"""
UPDATE ${MangaTable.TABLE}
SET ${MangaTable.COL_FILTERED_SCANLATORS} = NULL
""".trimIndent(),
0,
)
}
} }
} }

View File

@ -12,11 +12,13 @@ import androidx.work.WorkerParameters
import com.elvishew.xlog.Logger import com.elvishew.xlog.Logger
import com.elvishew.xlog.XLog import com.elvishew.xlog.XLog
import eu.kanade.data.DatabaseHandler import eu.kanade.data.DatabaseHandler
import eu.kanade.data.manga.mangaMapper
import eu.kanade.domain.chapter.interactor.GetChapterByMangaId import eu.kanade.domain.chapter.interactor.GetChapterByMangaId
import eu.kanade.domain.chapter.interactor.SyncChaptersWithSource import eu.kanade.domain.chapter.interactor.SyncChaptersWithSource
import eu.kanade.domain.chapter.model.Chapter import eu.kanade.domain.chapter.model.Chapter
import eu.kanade.domain.chapter.model.toDbChapter 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.interactor.UpdateManga
import eu.kanade.domain.manga.model.Manga import eu.kanade.domain.manga.model.Manga
import eu.kanade.domain.manga.model.toDbManga import eu.kanade.domain.manga.model.toDbManga
@ -33,11 +35,6 @@ import exh.debug.DebugToggles
import exh.eh.EHentaiUpdateWorkerConstants.UPDATES_PER_ITERATION import exh.eh.EHentaiUpdateWorkerConstants.UPDATES_PER_ITERATION
import exh.log.xLog import exh.log.xLog
import exh.metadata.metadata.EHentaiSearchMetadata 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 exh.util.cancellable
import kotlinx.coroutines.flow.asFlow import kotlinx.coroutines.flow.asFlow
import kotlinx.coroutines.flow.mapNotNull import kotlinx.coroutines.flow.mapNotNull
@ -53,7 +50,6 @@ import kotlin.time.Duration.Companion.days
class EHentaiUpdateWorker(private val context: Context, workerParams: WorkerParameters) : class EHentaiUpdateWorker(private val context: Context, workerParams: WorkerParameters) :
CoroutineWorker(context, workerParams) { CoroutineWorker(context, workerParams) {
private val handler: DatabaseHandler by injectLazy()
private val prefs: PreferencesHelper by injectLazy() private val prefs: PreferencesHelper by injectLazy()
private val sourceManager: SourceManager by injectLazy() private val sourceManager: SourceManager by injectLazy()
private val updateHelper: EHentaiUpdateHelper 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 updateManga: UpdateManga by injectLazy()
private val syncChaptersWithSource: SyncChaptersWithSource by injectLazy() private val syncChaptersWithSource: SyncChaptersWithSource by injectLazy()
private val getChapterByMangaId: GetChapterByMangaId 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) } private val updateNotifier by lazy { LibraryUpdateNotifier(context) }
@ -84,16 +83,12 @@ class EHentaiUpdateWorker(private val context: Context, workerParams: WorkerPara
val startTime = System.currentTimeMillis() val startTime = System.currentTimeMillis()
logger.d("Finding manga with metadata...") 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...") logger.d("Filtering manga and raising metadata...")
val curTime = System.currentTimeMillis() val curTime = System.currentTimeMillis()
val allMeta = metadataManga.asFlow().cancellable().mapNotNull { manga -> val allMeta = metadataManga.asFlow().cancellable().mapNotNull { manga ->
if (!manga.isEhBasedManga()) { val meta = getFlatMetadataById.await(manga.id)
return@mapNotNull null
}
val meta = handler.awaitFlatMetadataForManga(manga.id)
?: return@mapNotNull null ?: return@mapNotNull null
val raisedMeta = meta.raise<EHentaiSearchMetadata>() val raisedMeta = meta.raise<EHentaiSearchMetadata>()
@ -221,12 +216,12 @@ class EHentaiUpdateWorker(private val context: Context, workerParams: WorkerPara
return new to getChapterByMangaId.await(manga.id) return new to getChapterByMangaId.await(manga.id)
} catch (t: Throwable) { } catch (t: Throwable) {
if (t is EHentai.GalleryNotFoundException) { if (t is EHentai.GalleryNotFoundException) {
val meta = handler.awaitFlatMetadataForManga(manga.id)?.raise<EHentaiSearchMetadata>() val meta = getFlatMetadataById.await(manga.id)?.raise<EHentaiSearchMetadata>()
if (meta != null) { if (meta != null) {
// Age dead galleries // Age dead galleries
logger.d("Aged %s - notfound", manga.id) logger.d("Aged %s - notfound", manga.id)
meta.aged = true meta.aged = true
handler.awaitInsertFlatMetadata(meta.flatten()) insertFlatMetadata.await(meta)
} }
throw GalleryNotUpdatedException(false, t) 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 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.SearchMetadata
import exh.metadata.sql.models.SearchTag import exh.metadata.sql.models.SearchTag
import exh.metadata.sql.models.SearchTitle import exh.metadata.sql.models.SearchTitle
@ -27,35 +23,3 @@ data class FlatMetadata(
fillBaseFields(this@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 package exh.smartsearch
import eu.kanade.data.DatabaseHandler import eu.kanade.domain.manga.interactor.GetManga
import eu.kanade.tachiyomi.data.database.DatabaseHelper 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.Manga
import eu.kanade.tachiyomi.data.database.models.toDomainManga
import eu.kanade.tachiyomi.source.CatalogueSource import eu.kanade.tachiyomi.source.CatalogueSource
import eu.kanade.tachiyomi.source.model.FilterList import eu.kanade.tachiyomi.source.model.FilterList
import eu.kanade.tachiyomi.source.model.SManga import eu.kanade.tachiyomi.source.model.SManga
import eu.kanade.tachiyomi.util.lang.awaitSingle import eu.kanade.tachiyomi.util.lang.awaitSingle
import exh.util.executeOnIO
import info.debatty.java.stringsimilarity.NormalizedLevenshtein import info.debatty.java.stringsimilarity.NormalizedLevenshtein
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.async import kotlinx.coroutines.async
@ -18,8 +19,8 @@ import java.util.Locale
class SmartSearchEngine( class SmartSearchEngine(
private val extraSearchParams: String? = null, private val extraSearchParams: String? = null,
) { ) {
private val db: DatabaseHelper by injectLazy() private val getManga: GetManga by injectLazy()
private val handler: DatabaseHandler by injectLazy() private val insertManga: InsertManga by injectLazy()
private val normalizedLevenshtein = NormalizedLevenshtein() private val normalizedLevenshtein = NormalizedLevenshtein()
@ -172,15 +173,22 @@ class SmartSearchEngine(
* @return a manga from the database. * @return a manga from the database.
*/ */
suspend fun networkToLocalManga(sManga: SManga, sourceId: Long): Manga { 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) { if (localManga == null) {
val newManga = Manga.create(sManga.url, sManga.title, sourceId) val newManga = Manga.create(sManga.url, sManga.title, sourceId)
newManga.copyFrom(sManga) newManga.copyFrom(sManga)
val result = db.insertManga(newManga).executeOnIO() newManga.id = -1
newManga.id = result.insertedId() val result = run {
localManga = newManga 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 { companion object {

View File

@ -1,14 +1,13 @@
package exh.ui.metadata package exh.ui.metadata
import android.os.Bundle 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.domain.manga.model.Manga
import eu.kanade.tachiyomi.data.preference.PreferencesHelper import eu.kanade.tachiyomi.data.preference.PreferencesHelper
import eu.kanade.tachiyomi.source.Source import eu.kanade.tachiyomi.source.Source
import eu.kanade.tachiyomi.source.online.MetadataSource import eu.kanade.tachiyomi.source.online.MetadataSource
import eu.kanade.tachiyomi.util.lang.launchIO import eu.kanade.tachiyomi.util.lang.launchIO
import exh.metadata.metadata.base.RaisedSearchMetadata import exh.metadata.metadata.base.RaisedSearchMetadata
import exh.metadata.metadata.base.awaitFlatMetadataForManga
import exh.source.getMainSource import exh.source.getMainSource
import exh.ui.base.CoroutinePresenter import exh.ui.base.CoroutinePresenter
import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.MutableStateFlow
@ -19,7 +18,7 @@ class MetadataViewPresenter(
val manga: Manga, val manga: Manga,
val source: Source, val source: Source,
val preferences: PreferencesHelper = Injekt.get(), val preferences: PreferencesHelper = Injekt.get(),
private val db: DatabaseHandler = Injekt.get(), private val getFlatMetadataById: GetFlatMetadataById = Injekt.get(),
) : CoroutinePresenter<MetadataViewController>() { ) : CoroutinePresenter<MetadataViewController>() {
val meta = MutableStateFlow<RaisedSearchMetadata?>(null) val meta = MutableStateFlow<RaisedSearchMetadata?>(null)
@ -28,7 +27,7 @@ class MetadataViewPresenter(
super.onCreate(savedState) super.onCreate(savedState)
launchIO { launchIO {
val flatMetadata = db.awaitFlatMetadataForManga(manga.id) ?: return@launchIO val flatMetadata = getFlatMetadataById.await(manga.id) ?: return@launchIO
val mainSource = source.getMainSource<MetadataSource<*, *>>() val mainSource = source.getMainSource<MetadataSource<*, *>>()
if (mainSource != null) { if (mainSource != null) {
meta.value = flatMetadata.raise(mainSource.metaClass) 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

@ -5,3 +5,28 @@ migrateSource:
UPDATE mangas UPDATE mangas
SET source = :newId 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 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:
UPDATE mangas SET UPDATE mangas SET
source = coalesce(:source, source), source = coalesce(:source, source),
@ -208,6 +246,9 @@ SELECT * FROM mangas WHERE source = :sourceId;
getAll: getAll:
SELECT * FROM mangas; SELECT * FROM mangas;
deleteById:
DELETE FROM mangas WHERE _id = :id;
selectLastInsertRow: selectLastInsertRow:
SELECT * SELECT *
FROM mangas FROM mangas

View File

@ -73,9 +73,31 @@ FROM (
JOIN chapters JOIN chapters
ON chapters.manga_id = M.manga_id; ON chapters.manga_id = M.manga_id;
insertMerged: insert:
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) INSERT INTO merged(
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?); 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: updateSettingsById:
UPDATE merged UPDATE merged
@ -89,5 +111,8 @@ WHERE _id = :id;
deleteById: deleteById:
DELETE FROM merged WHERE _id = ?; DELETE FROM merged WHERE _id = ?;
deleteBy: deleteAll:
DELETE FROM merged; DELETE FROM merged;
selectLastInsertedRowId:
SELECT last_insert_rowid();