Use SQLDelight for most SY specific things

This commit is contained in:
Jobobby04 2022-07-02 23:52:03 -04:00
parent 3cf4c3128f
commit 664f9b1484
21 changed files with 480 additions and 393 deletions

View File

@ -0,0 +1,14 @@
package eu.kanade.data.exh
import exh.favorites.sql.models.FavoriteEntry
val favoriteEntryMapper: (Long, String, String, String, Long) -> FavoriteEntry =
{ id, title, gid, token, category ->
FavoriteEntry(
id = id,
title = title,
gid = gid,
token = token,
category = category.toInt(),
)
}

View File

@ -10,4 +10,10 @@ class RemoveHistoryById(
suspend fun await(history: HistoryWithRelations) { suspend fun await(history: HistoryWithRelations) {
repository.resetHistory(history.id) repository.resetHistory(history.id)
} }
// SY -->
suspend fun await(historyId: Long) {
repository.resetHistory(historyId)
}
// SY <--
} }

View File

@ -44,7 +44,6 @@ 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.awaitFlatMetadataForManga
import exh.metadata.metadata.base.awaitInsertFlatMetadata import exh.metadata.metadata.base.awaitInsertFlatMetadata
import exh.metadata.metadata.base.getFlatMetadataForManga
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
@ -198,7 +197,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.getFlatMetadataForManga(manga.id)?.let { flatMetadata -> handler.awaitFlatMetadataForManga(manga.id)?.let { flatMetadata ->
mangaObject.flatMetadata = BackupFlatMetadata.copyFrom(flatMetadata) mangaObject.flatMetadata = BackupFlatMetadata.copyFrom(flatMetadata)
} }
} }

View File

@ -1,6 +1,8 @@
package eu.kanade.tachiyomi.source.online package eu.kanade.tachiyomi.source.online
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import eu.kanade.data.DatabaseHandler
import eu.kanade.domain.manga.interactor.GetMangaByUrlAndSource
import eu.kanade.tachiyomi.data.database.DatabaseHelper 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
@ -12,8 +14,8 @@ import eu.kanade.tachiyomi.ui.manga.MangaScreenState
import eu.kanade.tachiyomi.util.lang.awaitSingle import eu.kanade.tachiyomi.util.lang.awaitSingle
import eu.kanade.tachiyomi.util.lang.runAsObservable import eu.kanade.tachiyomi.util.lang.runAsObservable
import exh.metadata.metadata.base.RaisedSearchMetadata import exh.metadata.metadata.base.RaisedSearchMetadata
import exh.metadata.metadata.base.getFlatMetadataForManga import exh.metadata.metadata.base.awaitFlatMetadataForManga
import exh.metadata.metadata.base.insertFlatMetadata import exh.metadata.metadata.base.awaitInsertFlatMetadata
import rx.Completable import rx.Completable
import rx.Single import rx.Single
import tachiyomi.source.model.MangaInfo import tachiyomi.source.model.MangaInfo
@ -26,6 +28,8 @@ import kotlin.reflect.KClass
*/ */
interface MetadataSource<M : RaisedSearchMetadata, I> : CatalogueSource { interface MetadataSource<M : RaisedSearchMetadata, I> : CatalogueSource {
val db: DatabaseHelper get() = Injekt.get() val db: DatabaseHelper get() = Injekt.get()
val handler: DatabaseHandler get() = Injekt.get()
val getMangaByUrlAndSource: GetMangaByUrlAndSource get() = Injekt.get()
/** /**
* The class of the metadata used by this source * The class of the metadata used by this source
@ -59,14 +63,14 @@ interface MetadataSource<M : RaisedSearchMetadata, I> : CatalogueSource {
suspend fun parseToManga(manga: MangaInfo, input: I): MangaInfo { suspend fun parseToManga(manga: MangaInfo, input: I): MangaInfo {
val mangaId = manga.id() val mangaId = manga.id()
val metadata = if (mangaId != null) { val metadata = if (mangaId != null) {
val flatMetadata = db.getFlatMetadataForManga(mangaId).executeAsBlocking() val flatMetadata = handler.awaitFlatMetadataForManga(mangaId)
flatMetadata?.raise(metaClass) ?: newMetaInstance() flatMetadata?.raise(metaClass) ?: newMetaInstance()
} else newMetaInstance() } else newMetaInstance()
parseIntoMetadata(metadata, input) parseIntoMetadata(metadata, input)
if (mangaId != null) { if (mangaId != null) {
metadata.mangaId = mangaId metadata.mangaId = mangaId
db.insertFlatMetadata(metadata.flatten()) handler.awaitInsertFlatMetadata(metadata.flatten())
} }
return metadata.createMangaInfo(manga) return metadata.createMangaInfo(manga)
@ -95,7 +99,7 @@ interface MetadataSource<M : RaisedSearchMetadata, I> : CatalogueSource {
*/ */
suspend fun fetchOrLoadMetadata(mangaId: Long?, inputProducer: suspend () -> I): M { suspend fun fetchOrLoadMetadata(mangaId: Long?, inputProducer: suspend () -> I): M {
val meta = if (mangaId != null) { val meta = if (mangaId != null) {
val flatMetadata = db.getFlatMetadataForManga(mangaId).executeAsBlocking() val flatMetadata = handler.awaitFlatMetadataForManga(mangaId)
flatMetadata?.raise(metaClass) flatMetadata?.raise(metaClass)
} else { } else {
null null
@ -106,15 +110,16 @@ interface MetadataSource<M : RaisedSearchMetadata, I> : CatalogueSource {
parseIntoMetadata(newMeta, input) parseIntoMetadata(newMeta, input)
if (mangaId != null) { if (mangaId != null) {
newMeta.mangaId = mangaId newMeta.mangaId = mangaId
db.insertFlatMetadata(newMeta.flatten()).let { newMeta } handler.awaitInsertFlatMetadata(newMeta.flatten())
} else newMeta }
newMeta
} }
} }
@Composable @Composable
fun DescriptionComposable(state: MangaScreenState.Success, openMetadataViewer: () -> Unit, search: (String) -> Unit) fun DescriptionComposable(state: MangaScreenState.Success, openMetadataViewer: () -> Unit, search: (String) -> Unit)
fun MangaInfo.id() = db.getManga(key, id).executeAsBlocking()?.id suspend fun MangaInfo.id() = getMangaByUrlAndSource.await(key, id)?.id
val SManga.id get() = (this as? Manga)?.id val SManga.id get() = (this as? Manga)?.id
val SChapter.mangaId get() = (this as? Chapter)?.manga_id val SChapter.mangaId get() = (this as? Chapter)?.manga_id
} }

View File

@ -807,7 +807,7 @@ class LibraryController(
?.setMessage(activity!!.getString(R.string.favorites_sync_bad_library_state, status.message)) ?.setMessage(activity!!.getString(R.string.favorites_sync_bad_library_state, status.message))
?.setCancelable(false) ?.setCancelable(false)
?.setPositiveButton(R.string.show_gallery) { _, _ -> ?.setPositiveButton(R.string.show_gallery) { _, _ ->
openManga(status.manga) openManga(status.manga.toDbManga())
presenter.favoritesSync.status.value = FavoritesSyncStatus.Idle(activity!!) presenter.favoritesSync.status.value = FavoritesSyncStatus.Idle(activity!!)
} }
?.setNegativeButton(android.R.string.ok) { _, _ -> ?.setNegativeButton(android.R.string.ok) { _, _ ->

View File

@ -239,7 +239,7 @@ class MangaPresenter(
if (chapters.isNotEmpty() && manga.isEhBasedManga() && DebugToggles.ENABLE_EXH_ROOT_REDIRECT.enabled) { if (chapters.isNotEmpty() && manga.isEhBasedManga() && DebugToggles.ENABLE_EXH_ROOT_REDIRECT.enabled) {
// Check for gallery in library and accept manga with lowest id // Check for gallery in library and accept manga with lowest id
// Find chapters sharing same root // Find chapters sharing same root
updateHelper.findAcceptedRootAndDiscardOthers(manga.source, chapters.map { it.toDbChapter() }) updateHelper.findAcceptedRootAndDiscardOthers(manga.source, chapters)
.onEach { (acceptedChain, _) -> .onEach { (acceptedChain, _) ->
// Redirect if we are not the accepted root // Redirect if we are not the accepted root
if (manga.id != acceptedChain.manga.id && acceptedChain.manga.favorite) { if (manga.id != acceptedChain.manga.id && acceptedChain.manga.favorite) {
@ -250,7 +250,7 @@ class MangaPresenter(
val update = (ourChapterUrls - acceptedChapterUrls).isNotEmpty() val update = (ourChapterUrls - acceptedChapterUrls).isNotEmpty()
redirectFlow.emit( redirectFlow.emit(
EXHRedirect( EXHRedirect(
acceptedChain.manga.id!!, acceptedChain.manga.id,
update, update,
), ),
) )

View File

@ -2,33 +2,38 @@ package exh
import android.content.Context import android.content.Context
import androidx.core.net.toUri import androidx.core.net.toUri
import eu.kanade.data.DatabaseHandler
import eu.kanade.data.chapter.chapterMapper
import eu.kanade.data.manga.mangaMapper
import eu.kanade.domain.chapter.interactor.SyncChaptersWithSource
import eu.kanade.domain.chapter.model.Chapter
import eu.kanade.domain.manga.interactor.GetMangaById
import eu.kanade.domain.manga.interactor.GetMangaByUrlAndSource
import eu.kanade.domain.manga.interactor.UpdateManga
import eu.kanade.domain.manga.model.Manga
import eu.kanade.domain.manga.model.toMangaInfo
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.Manga
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.SourceManager import eu.kanade.tachiyomi.source.SourceManager
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.online.UrlImportableSource import eu.kanade.tachiyomi.source.online.UrlImportableSource
import eu.kanade.tachiyomi.source.online.all.EHentai import eu.kanade.tachiyomi.source.online.all.EHentai
import eu.kanade.tachiyomi.util.chapter.syncChaptersWithSource
import exh.log.xLogStack import exh.log.xLogStack
import exh.source.getMainSource import exh.source.getMainSource
import uy.kohesive.injekt.Injekt import uy.kohesive.injekt.Injekt
import uy.kohesive.injekt.api.get import uy.kohesive.injekt.api.get
import uy.kohesive.injekt.injectLazy
class GalleryAdder { class GalleryAdder(
private val handler: DatabaseHandler = Injekt.get(),
private val getMangaByUrlAndSource: GetMangaByUrlAndSource = Injekt.get(),
private val getMangaById: GetMangaById = Injekt.get(),
private val updateManga: UpdateManga = Injekt.get(),
private val syncChaptersWithSource: SyncChaptersWithSource = Injekt.get(),
private val sourceManager: SourceManager = Injekt.get(),
) {
private val db: DatabaseHelper by injectLazy() private val filters: Pair<Set<String>, Set<Long>> = Injekt.get<PreferencesHelper>().run {
enabledLanguages().get() to disabledSources().get().map { it.toLong() }.toSet()
private val sourceManager: SourceManager by injectLazy()
private val filters: Pair<Set<String>, Set<Long>> = run {
val preferences = Injekt.get<PreferencesHelper>()
preferences.enabledLanguages().get() to preferences.disabledSources().get().map { it.toLong() }.toSet()
} }
private val logger = xLogStack() private val logger = xLogStack()
@ -115,33 +120,28 @@ class GalleryAdder {
} ?: return GalleryAddEvent.Fail.UnknownType(url, context) } ?: return GalleryAddEvent.Fail.UnknownType(url, context)
// Use manga in DB if possible, otherwise, make a new manga // Use manga in DB if possible, otherwise, make a new manga
val manga = db.getManga(cleanedMangaUrl, source.id).executeAsBlocking() var manga = getMangaByUrlAndSource.await(cleanedMangaUrl, source.id)
?: Manga.create(source.id).apply { ?: handler.awaitOne(true) {
this.url = cleanedMangaUrl
title = realMangaUrl
}
// Insert created manga if not in DB before fetching details // Insert created manga if not in DB before fetching details
// This allows us to keep the metadata when fetching details // This allows us to keep the metadata when fetching details
if (manga.id == null) { mangasQueries.insertEmpty(
db.insertManga(manga).executeAsBlocking().insertedId()?.let { source = source.id,
manga.id = it url = cleanedMangaUrl,
} title = realMangaUrl,
)
mangasQueries.selectLastInsertRow(mangaMapper)
} }
// Fetch and copy details // Fetch and copy details
val newManga = source.getMangaDetails(manga.toMangaInfo()) val newManga = source.getMangaDetails(manga.toMangaInfo())
updateManga.awaitUpdateFromSource(manga, newManga, false, Injekt.get())
manga.copyFrom(newManga.toSManga()) manga = getMangaById.await(manga.id)!!
manga.initialized = true
if (fav) { if (fav) {
manga.favorite = true updateManga.awaitUpdateFavorite(manga.id, true)
manga.date_added = System.currentTimeMillis() manga = manga.copy(favorite = true)
} }
db.insertManga(manga).executeAsBlocking()
// Fetch and copy chapters // Fetch and copy chapters
try { try {
val chapterList = if (source is EHentai) { val chapterList = if (source is EHentai) {
@ -151,7 +151,7 @@ class GalleryAdder {
}.map { it.toSChapter() } }.map { it.toSChapter() }
if (chapterList.isNotEmpty()) { if (chapterList.isNotEmpty()) {
syncChaptersWithSource(chapterList, manga, source) syncChaptersWithSource.await(chapterList, manga, source)
} }
} catch (e: Exception) { } catch (e: Exception) {
logger.w(context.getString(R.string.gallery_adder_chapter_fetch_error, manga.title), e) logger.w(context.getString(R.string.gallery_adder_chapter_fetch_error, manga.title), e)
@ -159,7 +159,7 @@ class GalleryAdder {
} }
return if (cleanedChapterUrl != null) { return if (cleanedChapterUrl != null) {
val chapter = db.getChapter(cleanedChapterUrl, manga.id!!).executeAsBlocking() val chapter = handler.awaitOneOrNull { chaptersQueries.getChapterByUrlAndMangaId(cleanedChapterUrl, manga.id, chapterMapper) }
if (chapter != null) { if (chapter != null) {
GalleryAddEvent.Success(url, manga, context, chapter) GalleryAddEvent.Success(url, manga, context, chapter)
} else { } else {

View File

@ -4,19 +4,21 @@ import android.app.Application
import androidx.work.WorkManager import androidx.work.WorkManager
import com.pushtorefresh.storio.sqlite.queries.RawQuery import com.pushtorefresh.storio.sqlite.queries.RawQuery
import eu.kanade.data.DatabaseHandler import eu.kanade.data.DatabaseHandler
import eu.kanade.data.manga.mangaMapper
import eu.kanade.domain.manga.interactor.GetFavorites
import eu.kanade.domain.manga.interactor.UpdateManga
import eu.kanade.domain.manga.model.toMangaInfo
import eu.kanade.tachiyomi.data.database.DatabaseHelper import eu.kanade.tachiyomi.data.database.DatabaseHelper
import eu.kanade.tachiyomi.data.database.models.toMangaInfo
import eu.kanade.tachiyomi.data.database.tables.MangaTable 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.model.toSManga
import eu.kanade.tachiyomi.source.online.all.NHentai import eu.kanade.tachiyomi.source.online.all.NHentai
import exh.EXHMigrations import exh.EXHMigrations
import exh.eh.EHentaiThrottleManager import exh.eh.EHentaiThrottleManager
import exh.eh.EHentaiUpdateWorker import exh.eh.EHentaiUpdateWorker
import exh.metadata.metadata.EHentaiSearchMetadata import exh.metadata.metadata.EHentaiSearchMetadata
import exh.metadata.metadata.base.getFlatMetadataForManga import exh.metadata.metadata.base.awaitFlatMetadataForManga
import exh.metadata.metadata.base.insertFlatMetadataAsync import exh.metadata.metadata.base.awaitInsertFlatMetadata
import exh.source.EH_SOURCE_ID import exh.source.EH_SOURCE_ID
import exh.source.EXH_SOURCE_ID import exh.source.EXH_SOURCE_ID
import exh.source.isEhBasedManga import exh.source.isEhBasedManga
@ -35,9 +37,11 @@ import java.util.UUID
object DebugFunctions { object DebugFunctions {
val app: Application by injectLazy() val app: Application by injectLazy()
val db: DatabaseHelper by injectLazy() val db: DatabaseHelper by injectLazy()
val database: DatabaseHandler by injectLazy() val handler: DatabaseHandler by injectLazy()
val prefs: PreferencesHelper by injectLazy() val prefs: PreferencesHelper by injectLazy()
val sourceManager: SourceManager by injectLazy() val sourceManager: SourceManager by injectLazy()
val updateManga: UpdateManga by injectLazy()
val getFavorites: GetFavorites by injectLazy()
fun forceUpgradeMigration() { fun forceUpgradeMigration() {
prefs.ehLastVersionCode().set(1) prefs.ehLastVersionCode().set(1)
@ -59,10 +63,10 @@ object DebugFunctions {
}.toList() }.toList()
allManga.forEach { manga -> allManga.forEach { manga ->
val meta = db.getFlatMetadataForManga(manga.id!!).executeOnIO()?.raise<EHentaiSearchMetadata>() ?: return@forEach val meta = handler.awaitFlatMetadataForManga(manga.id!!)?.raise<EHentaiSearchMetadata>() ?: return@forEach
// remove age flag // remove age flag
meta.aged = false meta.aged = false
db.insertFlatMetadataAsync(meta.flatten()).await() handler.awaitInsertFlatMetadata(meta.flatten())
} }
} }
} }
@ -73,70 +77,50 @@ object DebugFunctions {
fun resetEHGalleriesForUpdater() { fun resetEHGalleriesForUpdater() {
throttleManager.resetThrottle() throttleManager.resetThrottle()
runBlocking { runBlocking {
val metadataManga = db.getFavoriteMangaWithMetadata().executeOnIO() val allManga = handler
.awaitList { mangasQueries.getEhMangaWithMetadata(EH_SOURCE_ID, EXH_SOURCE_ID, mangaMapper) }
val allManga = metadataManga.asFlow().cancellable().mapNotNull { manga ->
if (manga.isEhBasedManga()) manga
else null
}.toList()
val eh = sourceManager.get(EH_SOURCE_ID) val eh = sourceManager.get(EH_SOURCE_ID)
val ex = sourceManager.get(EXH_SOURCE_ID) val ex = sourceManager.get(EXH_SOURCE_ID)
allManga.forEach { manga -> allManga.forEach { manga ->
throttleManager.throttle() throttleManager.throttle()
(
when (manga.source) { val networkManga = when (manga.source) {
EH_SOURCE_ID -> eh EH_SOURCE_ID -> eh
EXH_SOURCE_ID -> ex EXH_SOURCE_ID -> ex
else -> return@forEach else -> return@forEach
} }?.getMangaDetails(manga.toMangaInfo()) ?: return@forEach
)?.getMangaDetails(manga.toMangaInfo())?.let { networkManga ->
manga.copyFrom(networkManga.toSManga()) updateManga.awaitUpdateFromSource(manga, networkManga, true)
manga.initialized = true
db.insertManga(manga).executeOnIO()
}
} }
} }
} }
fun getEHMangaListWithAgedFlagInfo(): String { fun getEHMangaListWithAgedFlagInfo(): String {
val galleries = mutableListOf(String()) return runBlocking {
runBlocking { val allManga = handler
val metadataManga = db.getFavoriteMangaWithMetadata().executeOnIO() .awaitList { mangasQueries.getEhMangaWithMetadata(EH_SOURCE_ID, EXH_SOURCE_ID, mangaMapper) }
val allManga = metadataManga.asFlow().cancellable().mapNotNull { manga -> allManga.map { manga ->
if (manga.isEhBasedManga()) manga val meta = handler.awaitFlatMetadataForManga(manga.id)?.raise<EHentaiSearchMetadata>() ?: return@map
else null "Aged: ${meta.aged}\t Title: ${manga.title}"
}.toList()
allManga.forEach { manga ->
val meta = db.getFlatMetadataForManga(manga.id!!).executeOnIO()?.raise<EHentaiSearchMetadata>() ?: return@forEach
galleries += "Aged: ${meta.aged}\t Title: ${manga.title}"
} }
} }.joinToString(",\n")
return galleries.joinToString(",\n")
} }
fun countAgedFlagInEXHManga(): Int { fun countAgedFlagInEXHManga(): Int {
var agedAmount = 0 return runBlocking {
runBlocking { handler
val metadataManga = db.getFavoriteMangaWithMetadata().executeOnIO() .awaitList { mangasQueries.getEhMangaWithMetadata(EH_SOURCE_ID, EXH_SOURCE_ID, mangaMapper) }
.count { manga ->
val allManga = metadataManga.asFlow().cancellable().mapNotNull { manga -> val meta = handler.awaitFlatMetadataForManga(manga.id)
if (manga.isEhBasedManga()) manga ?.raise<EHentaiSearchMetadata>()
else null ?: return@count false
}.toList() meta.aged
allManga.forEach { manga ->
val meta = db.getFlatMetadataForManga(manga.id!!).executeOnIO()?.raise<EHentaiSearchMetadata>() ?: return@forEach
if (meta.aged) {
// remove age flag
agedAmount++
} }
} }
} }
return agedAmount
}
fun addAllMangaInDatabaseToLibrary() { fun addAllMangaInDatabaseToLibrary() {
db.inTransaction { db.inTransaction {
@ -154,7 +138,7 @@ object DebugFunctions {
} }
} }
fun countMangaInDatabaseInLibrary() = db.getMangas().executeAsBlocking().count { it.favorite } fun countMangaInDatabaseInLibrary() = runBlocking { getFavorites.await().size }
fun countMangaInDatabaseNotInLibrary() = db.getMangas().executeAsBlocking().count { !it.favorite } fun countMangaInDatabaseNotInLibrary() = db.getMangas().executeAsBlocking().count { !it.favorite }
@ -166,7 +150,7 @@ object DebugFunctions {
it.favorite && db.getSearchMetadataForManga(it.id!!).executeAsBlocking() == null it.favorite && db.getSearchMetadataForManga(it.id!!).executeAsBlocking() == null
} }
fun clearSavedSearches() = runBlocking { database.await { saved_searchQueries.deleteAll() } } fun clearSavedSearches() = runBlocking { handler.await { saved_searchQueries.deleteAll() } }
fun listAllSources() = sourceManager.getCatalogueSources().joinToString("\n") { fun listAllSources() = sourceManager.getCatalogueSources().joinToString("\n") {
"${it.id}: ${it.name} (${it.lang.uppercase()})" "${it.id}: ${it.name} (${it.lang.uppercase()})"

View File

@ -1,12 +1,23 @@
package exh.eh package exh.eh
import android.content.Context import android.content.Context
import eu.kanade.tachiyomi.data.database.DatabaseHelper import eu.kanade.data.DatabaseHandler
import eu.kanade.tachiyomi.data.database.models.Chapter import eu.kanade.data.chapter.chapterMapper
import eu.kanade.tachiyomi.data.database.models.History import eu.kanade.data.history.historyMapper
import eu.kanade.tachiyomi.data.database.models.Manga import eu.kanade.domain.category.interactor.GetCategories
import eu.kanade.tachiyomi.data.database.models.MangaCategory import eu.kanade.domain.category.interactor.SetMangaCategories
import exh.util.executeOnIO import eu.kanade.domain.chapter.interactor.GetChapterByMangaId
import eu.kanade.domain.chapter.model.Chapter
import eu.kanade.domain.chapter.model.ChapterUpdate
import eu.kanade.domain.chapter.repository.ChapterRepository
import eu.kanade.domain.history.interactor.RemoveHistoryById
import eu.kanade.domain.history.interactor.UpsertHistory
import eu.kanade.domain.history.model.History
import eu.kanade.domain.history.model.HistoryUpdate
import eu.kanade.domain.manga.interactor.GetMangaById
import eu.kanade.domain.manga.interactor.UpdateManga
import eu.kanade.domain.manga.model.Manga
import eu.kanade.domain.manga.model.MangaUpdate
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.async import kotlinx.coroutines.async
import kotlinx.coroutines.coroutineScope import kotlinx.coroutines.coroutineScope
@ -24,7 +35,15 @@ class EHentaiUpdateHelper(context: Context) {
File(context.filesDir, "exh-plt.maftable"), File(context.filesDir, "exh-plt.maftable"),
GalleryEntry.Serializer(), GalleryEntry.Serializer(),
) )
private val db: DatabaseHelper by injectLazy() private val handler: DatabaseHandler by injectLazy()
private val getChapterByMangaId: GetChapterByMangaId by injectLazy()
private val getMangaById: GetMangaById by injectLazy()
private val updateManga: UpdateManga by injectLazy()
private val setMangaCategories: SetMangaCategories by injectLazy()
private val getCategories: GetCategories by injectLazy()
private val chapterRepository: ChapterRepository by injectLazy()
private val upsertHistory: UpsertHistory by injectLazy()
private val removeHistoryById: RemoveHistoryById by injectLazy()
/** /**
* @param chapters Cannot be an empty list! * @param chapters Cannot be an empty list!
@ -36,7 +55,8 @@ class EHentaiUpdateHelper(context: Context) {
val chainsFlow = flowOf(chapters) val chainsFlow = flowOf(chapters)
.map { chapterList -> .map { chapterList ->
chapterList.flatMap { chapter -> chapterList.flatMap { chapter ->
db.getChapters(chapter.url).executeOnIO().mapNotNull { it.manga_id } handler.awaitList { chaptersQueries.getChapterByUrl(chapter.url, chapterMapper) }
.map { it.mangaId }
}.distinct() }.distinct()
} }
.map { mangaIds -> .map { mangaIds ->
@ -44,13 +64,13 @@ class EHentaiUpdateHelper(context: Context) {
.mapNotNull { mangaId -> .mapNotNull { mangaId ->
coroutineScope { coroutineScope {
val manga = async(Dispatchers.IO) { val manga = async(Dispatchers.IO) {
db.getManga(mangaId).executeAsBlocking() getMangaById.await(mangaId)
} }
val chapterList = async(Dispatchers.IO) { val chapterList = async(Dispatchers.IO) {
db.getChapters(mangaId).executeAsBlocking() getChapterByMangaId.await(mangaId)
} }
val history = async(Dispatchers.IO) { val history = async(Dispatchers.IO) {
db.getHistoryByMangaId(mangaId).executeAsBlocking() handler.awaitList { historyQueries.getHistoryByMangaId(mangaId, historyMapper) }
} }
ChapterChain( ChapterChain(
manga.await() ?: return@coroutineScope null, manga.await() ?: return@coroutineScope null,
@ -64,66 +84,66 @@ class EHentaiUpdateHelper(context: Context) {
// Accept oldest chain // Accept oldest chain
val chainsWithAccepted = chainsFlow.map { chains -> val chainsWithAccepted = chainsFlow.map { chains ->
val acceptedChain = chains.minByOrNull { it.manga.id!! }!! val acceptedChain = chains.minBy { it.manga.id }
acceptedChain to chains acceptedChain to chains
} }
return chainsWithAccepted.map { (accepted, chains) -> return chainsWithAccepted.map { (accepted, chains) ->
val toDiscard = chains.filter { it.manga.favorite && it.manga.id != accepted.manga.id } val toDiscard = chains.filter { it.manga.favorite && it.manga.id != accepted.manga.id }
val mangaUpdates = mutableListOf<MangaUpdate>()
val chainsAsChapters = chains.flatMap { it.chapters } val chainsAsChapters = chains.flatMap { it.chapters }
val chainsAsHistory = chains.flatMap { it.history } val chainsAsHistory = chains.flatMap { it.history }
if (toDiscard.isNotEmpty()) { if (toDiscard.isNotEmpty()) {
// Copy chain chapters to curChapters // Copy chain chapters to curChapters
val (newChapters, new) = getChapterList(accepted, toDiscard, chainsAsChapters) val (chapterUpdates, newChapters, new) = getChapterList(accepted, toDiscard, chainsAsChapters)
val (history, urlHistory, deleteHistory) = getHistory(newChapters, chainsAsChapters, chainsAsHistory)
toDiscard.forEach { toDiscard.forEach {
it.manga.favorite = false mangaUpdates += MangaUpdate(
it.manga.date_added = 0 id = it.manga.id,
favorite = false,
dateAdded = 0,
)
} }
if (!accepted.manga.favorite) { if (!accepted.manga.favorite) {
accepted.manga.favorite = true mangaUpdates += MangaUpdate(
accepted.manga.date_added = System.currentTimeMillis() id = accepted.manga.id,
favorite = true,
dateAdded = System.currentTimeMillis(),
)
} }
val newAccepted = ChapterChain(accepted.manga, newChapters, history + urlHistory.map { it.second }) val newAccepted = ChapterChain(accepted.manga, newChapters, emptyList())
val rootsToMutate = toDiscard + newAccepted val rootsToMutate = toDiscard + newAccepted
db.inTransaction {
// Apply changes to all manga // Apply changes to all manga
db.insertMangas(rootsToMutate.map { it.manga }).executeAsBlocking() updateManga.awaitAll(mangaUpdates)
// Insert new chapters for accepted manga // Insert new chapters for accepted manga
val chapterPutResults = db.insertChapters(newAccepted.chapters).executeAsBlocking().results() chapterRepository.updateAll(chapterUpdates)
chapterRepository.addAll(newChapters)
val (newHistory, deleteHistory) = getHistory(getChapterByMangaId.await(accepted.manga.id), chainsAsChapters, chainsAsHistory)
// Delete the duplicate history first // Delete the duplicate history first
if (deleteHistory.isNotEmpty()) { if (deleteHistory.isNotEmpty()) {
db.deleteHistoryIds(deleteHistory).executeAsBlocking() deleteHistory.forEach {
removeHistoryById.await(it)
} }
// Get a updated history list
val newHistory = urlHistory.mapNotNull { (url, history) ->
val result = chapterPutResults.firstNotNullOfOrNull { (chapter, result) ->
if (chapter.url == url) {
result.insertedId()
} else null
} }
if (result != null) { // Insert new history
history.chapter_id = result newHistory.forEach {
history upsertHistory.await(it)
} else null }
} + history
// Copy the new history chapter ids
db.updateHistoryChapterIds(newHistory).executeAsBlocking()
// Copy categories from all chains to accepted manga // Copy categories from all chains to accepted manga
val newCategories = rootsToMutate.flatMap { val newCategories = rootsToMutate.flatMap {
db.getCategoriesForManga(it.manga).executeAsBlocking() getCategories.await(it.manga.id).map { it.id }
}.distinctBy { it.id }.map { }.distinct()
MangaCategory.create(newAccepted.manga, it) rootsToMutate.forEach {
} setMangaCategories.await(it.manga.id, newCategories)
db.setMangaCategories(newCategories, rootsToMutate.map { it.manga })
} }
Triple(newAccepted, toDiscard, new) Triple(newAccepted, toDiscard, new)
@ -140,105 +160,105 @@ class EHentaiUpdateHelper(context: Context) {
} }
} }
data class HistoryUpdates( fun getHistory(
val history: List<History>, currentChapters: List<Chapter>,
val urlHistory: List<Pair<String, History>>,
val historyToDelete: List<Long>,
)
private fun getHistory(
newChapters: List<Chapter>,
chainsAsChapters: List<Chapter>, chainsAsChapters: List<Chapter>,
chainsAsHistory: List<History>, chainsAsHistory: List<History>,
): HistoryUpdates { ): Pair<List<HistoryUpdate>, List<Long>> {
val historyMap = chainsAsHistory val history = chainsAsHistory.groupBy { history -> chainsAsChapters.find { it.id == history.chapterId }?.url }
.groupBy { history -> val newHistory = currentChapters.mapNotNull { chapter ->
chainsAsChapters.find { it.id == history.chapter_id }?.url.orEmpty() val newHistory = history[chapter.url]
?.maxByOrNull {
it.readAt?.time ?: 0
} }
.filterKeys { it.isNotBlank() } ?.takeIf { it.chapterId != chapter.id && it.readAt != null }
val latestHistory = historyMap.mapValues { entry -> if (newHistory != null) {
entry.value.maxByOrNull { HistoryUpdate(chapter.id, newHistory.readAt!!, newHistory.readDuration)
it.time_read } else null
}!!
} }
val oldHistory = historyMap.flatMap { entry -> val currentChapterIds = currentChapters.map { it.id }
val topEntry = entry.value.maxByOrNull { val historyToDelete = chainsAsHistory.filterNot { it.chapterId in currentChapterIds }
it.time_read .map { it.id }
}!! return newHistory to historyToDelete
entry.value - topEntry
}.mapNotNull { it.id }
return HistoryUpdates(
latestHistory.filter { (_, history) ->
val oldChapter = chainsAsChapters.find { it.id == history.chapter_id }
val newChapter = newChapters.find { it.url == oldChapter?.url }
if (oldChapter != newChapter && newChapter?.id != null) {
history.chapter_id = newChapter.id!!
true
} else false
}.mapNotNull { it.value },
latestHistory.mapNotNull { (url, history) ->
val oldChapter = chainsAsChapters.find { it.id == history.chapter_id }
val newChapter = newChapters.find { it.url == oldChapter?.url }
if (oldChapter != newChapter && newChapter?.id == null) {
url to history
} else {
null
}
},
oldHistory,
)
} }
private fun getChapterList( private fun getChapterList(
accepted: ChapterChain, accepted: ChapterChain,
toDiscard: List<ChapterChain>, toDiscard: List<ChapterChain>,
chainsAsChapters: List<Chapter>, chainsAsChapters: List<Chapter>,
): Pair<List<Chapter>, Boolean> { ): Triple<List<ChapterUpdate>, List<Chapter>, Boolean> {
var new = false var new = false
return toDiscard return toDiscard
.flatMap { chain -> .flatMap { chain ->
chain.chapters chain.chapters
} }
.fold(accepted.chapters) { curChapters, chapter -> .fold(accepted.chapters) { curChapters, chapter ->
val existing = curChapters.find { it.url == chapter.url } val newLastPageRead = chainsAsChapters.maxOfOrNull { it.lastPageRead }
val newLastPageRead = chainsAsChapters.maxOfOrNull { it.last_page_read } if (curChapters.any { it.url == chapter.url }) {
curChapters.map {
if (existing != null) { if (it.url == chapter.url) {
existing.read = existing.read || chapter.read val read = it.read || chapter.read
existing.last_page_read = existing.last_page_read.coerceAtLeast(chapter.last_page_read) var lastPageRead = it.lastPageRead.coerceAtLeast(chapter.lastPageRead)
if (newLastPageRead != null && existing.last_page_read <= 0) { if (newLastPageRead != null && lastPageRead <= 0) {
existing.last_page_read = newLastPageRead lastPageRead = newLastPageRead
}
val bookmark = it.bookmark || chapter.bookmark
it.copy(
read = read,
lastPageRead = lastPageRead,
bookmark = bookmark,
)
} else it
} }
existing.bookmark = existing.bookmark || chapter.bookmark
curChapters
} else { } else {
new = true new = true
curChapters + Chapter.create().apply { curChapters + Chapter(
manga_id = accepted.manga.id id = -1,
url = chapter.url mangaId = accepted.manga.id,
name = chapter.name url = chapter.url,
read = chapter.read name = chapter.name,
bookmark = chapter.bookmark read = chapter.read,
bookmark = chapter.bookmark,
last_page_read = chapter.last_page_read lastPageRead = if (newLastPageRead != null && chapter.lastPageRead <= 0) {
if (newLastPageRead != null && last_page_read <= 0) { newLastPageRead
last_page_read = newLastPageRead } else chapter.lastPageRead,
} dateFetch = chapter.dateFetch,
dateUpload = chapter.dateUpload,
date_fetch = chapter.date_fetch chapterNumber = -1F,
date_upload = chapter.date_upload scanlator = null,
sourceOrder = -1,
)
} }
} }
} .sortedBy { it.dateUpload }
.sortedBy { it.date_upload }
.let { chapters -> .let { chapters ->
chapters.onEachIndexed { index, chapter -> val updates = mutableListOf<ChapterUpdate>()
chapter.name = "v${index + 1}: " + chapter.name.substringAfter(" ") val newChapters = mutableListOf<Chapter>()
chapter.chapter_number = index + 1f chapters.mapIndexed { index, chapter ->
chapter.source_order = chapters.lastIndex - index val name = "v${index + 1}: " + chapter.name.substringAfter(" ")
val chapterNumber = index + 1f
val sourceOrder = chapters.lastIndex - index.toLong()
when (chapter.id) {
-1L -> newChapters.add(
chapter.copy(
name = name,
chapterNumber = chapterNumber,
sourceOrder = sourceOrder,
),
)
else -> updates.add(
ChapterUpdate(
id = chapter.id,
name = name.takeUnless { chapter.name == it },
chapterNumber = chapterNumber.takeUnless { chapter.chapterNumber == it },
sourceOrder = sourceOrder.takeUnless { chapter.sourceOrder == it },
),
)
}
}
Triple(updates.toList(), newChapters.toList(), new)
} }
} to new
} }
} }

View File

@ -11,29 +11,35 @@ import androidx.work.WorkManager
import androidx.work.WorkerParameters 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.manga.mangaMapper
import eu.kanade.domain.chapter.interactor.GetChapterByMangaId
import eu.kanade.domain.chapter.interactor.SyncChaptersWithSource
import eu.kanade.domain.chapter.model.Chapter
import eu.kanade.domain.chapter.model.toDbChapter
import eu.kanade.domain.manga.interactor.UpdateManga
import eu.kanade.domain.manga.model.Manga
import eu.kanade.domain.manga.model.toDbManga
import eu.kanade.domain.manga.model.toMangaInfo
import eu.kanade.tachiyomi.data.database.DatabaseHelper import eu.kanade.tachiyomi.data.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.toMangaInfo
import eu.kanade.tachiyomi.data.library.LibraryUpdateNotifier import eu.kanade.tachiyomi.data.library.LibraryUpdateNotifier
import eu.kanade.tachiyomi.data.preference.DEVICE_CHARGING import eu.kanade.tachiyomi.data.preference.DEVICE_CHARGING
import eu.kanade.tachiyomi.data.preference.DEVICE_ONLY_ON_WIFI import eu.kanade.tachiyomi.data.preference.DEVICE_ONLY_ON_WIFI
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.model.toSChapter import eu.kanade.tachiyomi.source.model.toSChapter
import eu.kanade.tachiyomi.source.model.toSManga
import eu.kanade.tachiyomi.source.online.all.EHentai import eu.kanade.tachiyomi.source.online.all.EHentai
import eu.kanade.tachiyomi.util.chapter.syncChaptersWithSource
import eu.kanade.tachiyomi.util.system.isConnectedToWifi import eu.kanade.tachiyomi.util.system.isConnectedToWifi
import exh.debug.DebugToggles 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.getFlatMetadataForManga import exh.metadata.metadata.base.awaitFlatMetadataForManga
import exh.metadata.metadata.base.insertFlatMetadataAsync import exh.metadata.metadata.base.insertFlatMetadataAsync
import exh.source.EH_SOURCE_ID
import exh.source.EXH_SOURCE_ID
import exh.source.isEhBasedManga import exh.source.isEhBasedManga
import exh.util.cancellable import exh.util.cancellable
import exh.util.executeOnIO
import kotlinx.coroutines.flow.asFlow import kotlinx.coroutines.flow.asFlow
import kotlinx.coroutines.flow.mapNotNull import kotlinx.coroutines.flow.mapNotNull
import kotlinx.coroutines.flow.single import kotlinx.coroutines.flow.single
@ -49,10 +55,14 @@ 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 db: DatabaseHelper by injectLazy() private val db: DatabaseHelper by injectLazy()
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()
private val logger: Logger = xLog() private val logger: Logger = xLog()
private val updateManga: UpdateManga by injectLazy()
private val syncChaptersWithSource: SyncChaptersWithSource by injectLazy()
private val getChapterByMangaId: GetChapterByMangaId by injectLazy()
private val updateNotifier by lazy { LibraryUpdateNotifier(context) } private val updateNotifier by lazy { LibraryUpdateNotifier(context) }
@ -76,7 +86,7 @@ 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 = db.getFavoriteMangaWithMetadata().executeOnIO() val metadataManga = handler.awaitList { mangasQueries.getEhMangaWithMetadata(EH_SOURCE_ID, EXH_SOURCE_ID, mangaMapper) }
logger.d("Filtering manga and raising metadata...") logger.d("Filtering manga and raising metadata...")
val curTime = System.currentTimeMillis() val curTime = System.currentTimeMillis()
@ -85,7 +95,7 @@ class EHentaiUpdateWorker(private val context: Context, workerParams: WorkerPara
return@mapNotNull null return@mapNotNull null
} }
val meta = db.getFlatMetadataForManga(manga.id!!).executeOnIO() val meta = handler.awaitFlatMetadataForManga(manga.id)
?: return@mapNotNull null ?: return@mapNotNull null
val raisedMeta = meta.raise<EHentaiSearchMetadata>() val raisedMeta = meta.raise<EHentaiSearchMetadata>()
@ -95,8 +105,8 @@ class EHentaiUpdateWorker(private val context: Context, workerParams: WorkerPara
return@mapNotNull null return@mapNotNull null
} }
val chapter = db.getChapters(manga.id!!).executeOnIO().minByOrNull { val chapter = getChapterByMangaId.await(manga.id).minByOrNull {
it.date_upload it.dateUpload
} }
UpdateEntry(manga, raisedMeta, chapter) UpdateEntry(manga, raisedMeta, chapter)
@ -176,8 +186,8 @@ class EHentaiUpdateWorker(private val context: Context, workerParams: WorkerPara
updatedManga += acceptedRoot.manga to new.toTypedArray() updatedManga += acceptedRoot.manga to new.toTypedArray()
} }
modifiedThisIteration += acceptedRoot.manga.id!! modifiedThisIteration += acceptedRoot.manga.id
modifiedThisIteration += discardedRoots.map { it.manga.id!! } modifiedThisIteration += discardedRoots.map { it.manga.id }
updatedThisIteration++ updatedThisIteration++
} }
} finally { } finally {
@ -192,7 +202,7 @@ class EHentaiUpdateWorker(private val context: Context, workerParams: WorkerPara
) )
if (updatedManga.isNotEmpty()) { if (updatedManga.isNotEmpty()) {
updateNotifier.showUpdateNotifications(updatedManga) updateNotifier.showUpdateNotifications(updatedManga.map { it.first.toDbManga() to it.second.map { it.toDbChapter() }.toTypedArray() })
} }
} }
} }
@ -204,17 +214,16 @@ class EHentaiUpdateWorker(private val context: Context, workerParams: WorkerPara
try { try {
val updatedManga = source.getMangaDetails(manga.toMangaInfo()) val updatedManga = source.getMangaDetails(manga.toMangaInfo())
manga.copyFrom(updatedManga.toSManga()) updateManga.awaitUpdateFromSource(manga, updatedManga, false)
db.insertManga(manga).executeOnIO()
val newChapters = source.getChapterList(manga.toMangaInfo()) val newChapters = source.getChapterList(manga.toMangaInfo())
.map { it.toSChapter() } .map { it.toSChapter() }
val (new, _) = syncChaptersWithSource(newChapters, manga, source) // Not suspending, but does block, maybe fix this? val (new, _) = syncChaptersWithSource.await(newChapters, manga, source)
return new to db.getChapters(manga).executeOnIO() 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 = db.getFlatMetadataForManga(manga.id!!).executeOnIO()?.raise<EHentaiSearchMetadata>() val meta = handler.awaitFlatMetadataForManga(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)

View File

@ -3,18 +3,23 @@ package exh.favorites
import android.content.Context import android.content.Context
import android.net.wifi.WifiManager import android.net.wifi.WifiManager
import android.os.PowerManager import android.os.PowerManager
import eu.kanade.data.AndroidDatabaseHandler
import eu.kanade.data.DatabaseHandler
import eu.kanade.domain.category.interactor.GetCategories
import eu.kanade.domain.category.interactor.SetMangaCategories
import eu.kanade.domain.category.model.Category
import eu.kanade.domain.manga.interactor.GetMangaByUrlAndSource
import eu.kanade.domain.manga.interactor.UpdateManga
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.toDomainManga
import eu.kanade.tachiyomi.data.database.models.Category
import eu.kanade.tachiyomi.data.database.models.Manga
import eu.kanade.tachiyomi.data.database.models.MangaCategory
import eu.kanade.tachiyomi.data.preference.PreferencesHelper import eu.kanade.tachiyomi.data.preference.PreferencesHelper
import eu.kanade.tachiyomi.network.POST import eu.kanade.tachiyomi.network.POST
import eu.kanade.tachiyomi.network.await import eu.kanade.tachiyomi.network.await
import eu.kanade.tachiyomi.source.SourceManager import eu.kanade.tachiyomi.source.SourceManager
import eu.kanade.tachiyomi.source.online.all.EHentai import eu.kanade.tachiyomi.source.online.all.EHentai
import eu.kanade.tachiyomi.util.lang.launchUI
import eu.kanade.tachiyomi.util.lang.withIOContext import eu.kanade.tachiyomi.util.lang.withIOContext
import eu.kanade.tachiyomi.util.lang.withUIContext
import eu.kanade.tachiyomi.util.system.powerManager import eu.kanade.tachiyomi.util.system.powerManager
import eu.kanade.tachiyomi.util.system.toast import eu.kanade.tachiyomi.util.system.toast
import exh.GalleryAddEvent import exh.GalleryAddEvent
@ -29,13 +34,11 @@ import exh.source.isEhBasedManga
import exh.util.ignore import exh.util.ignore
import exh.util.wifiManager import exh.util.wifiManager
import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.DelicateCoroutinesApi
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.Job import kotlinx.coroutines.Job
import kotlinx.coroutines.cancel import kotlinx.coroutines.cancel
import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import kotlinx.coroutines.newSingleThreadContext
import okhttp3.FormBody import okhttp3.FormBody
import okhttp3.Request import okhttp3.Request
import uy.kohesive.injekt.Injekt import uy.kohesive.injekt.Injekt
@ -43,16 +46,18 @@ import uy.kohesive.injekt.api.get
import uy.kohesive.injekt.injectLazy import uy.kohesive.injekt.injectLazy
import kotlin.time.Duration.Companion.seconds import kotlin.time.Duration.Companion.seconds
// TODO only apply database changes after sync
class FavoritesSyncHelper(val context: Context) { class FavoritesSyncHelper(val context: Context) {
private val db: DatabaseHelper by injectLazy() private val handler: DatabaseHandler by injectLazy()
private val getCategories: GetCategories by injectLazy()
private val getMangaByUrlAndSource: GetMangaByUrlAndSource by injectLazy()
private val updateManga: UpdateManga by injectLazy()
private val setMangaCategories: SetMangaCategories by injectLazy()
private val prefs: PreferencesHelper by injectLazy() private val prefs: PreferencesHelper by injectLazy()
private val scope = CoroutineScope(Job() + Dispatchers.Main) private val scope = CoroutineScope(Job() + Dispatchers.Main)
@OptIn(DelicateCoroutinesApi::class)
private val dispatcher = newSingleThreadContext("Favorites-sync-worker")
private val exh by lazy { private val exh by lazy {
Injekt.get<SourceManager>().get(EXH_SOURCE_ID) as? EHentai Injekt.get<SourceManager>().get(EXH_SOURCE_ID) as? EHentai
?: EHentai(0, true, context) ?: EHentai(0, true, context)
@ -79,7 +84,7 @@ class FavoritesSyncHelper(val context: Context) {
status.value = FavoritesSyncStatus.Initializing(context) status.value = FavoritesSyncStatus.Initializing(context)
scope.launch(dispatcher) { beginSync() } scope.launch(Dispatchers.IO) { beginSync() }
} }
private suspend fun beginSync() { private suspend fun beginSync() {
@ -91,14 +96,14 @@ class FavoritesSyncHelper(val context: Context) {
// Validate library state // Validate library state
status.value = FavoritesSyncStatus.Processing(context.getString(R.string.favorites_sync_verifying_library), context = context) status.value = FavoritesSyncStatus.Processing(context.getString(R.string.favorites_sync_verifying_library), context = context)
val libraryManga = db.getLibraryMangas().executeAsBlocking() val libraryManga = handler.awaitList { (handler as AndroidDatabaseHandler).getLibraryQuery() }
val seenManga = HashSet<Long>(libraryManga.size) val seenManga = HashSet<Long>(libraryManga.size)
libraryManga.forEach { libraryManga.forEach {
if (!it.isEhBasedManga()) return@forEach if (!it.isEhBasedManga()) return@forEach
if (it.id in seenManga) { if (it.id in seenManga) {
val inCategories = db.getCategoriesForManga(it).executeAsBlocking() val inCategories = getCategories.await(it.id!!)
status.value = FavoritesSyncStatus.BadLibraryState.MangaInMultipleCategories(it, inCategories, context) status.value = FavoritesSyncStatus.BadLibraryState.MangaInMultipleCategories(it.toDomainManga()!!, inCategories, context)
logger.w(context.getString(R.string.favorites_sync_manga_multiple_categories_error, it.id)) logger.w(context.getString(R.string.favorites_sync_manga_multiple_categories_error, it.id))
return return
@ -139,7 +144,6 @@ class FavoritesSyncHelper(val context: Context) {
// Do not update galleries while syncing favorites // Do not update galleries while syncing favorites
EHentaiUpdateWorker.cancelBackground(context) EHentaiUpdateWorker.cancelBackground(context)
db.inTransaction {
status.value = FavoritesSyncStatus.Processing(context.getString(R.string.favorites_sync_calculating_remote_changes), context = context) status.value = FavoritesSyncStatus.Processing(context.getString(R.string.favorites_sync_calculating_remote_changes), context = context)
val remoteChanges = storage.getChangedRemoteEntries(favorites.first) val remoteChanges = storage.getChangedRemoteEntries(favorites.first)
val localChanges = if (prefs.exhReadOnlySync().get()) { val localChanges = if (prefs.exhReadOnlySync().get()) {
@ -161,10 +165,9 @@ class FavoritesSyncHelper(val context: Context) {
status.value = FavoritesSyncStatus.Processing(context.getString(R.string.favorites_sync_cleaning_up), context = context) status.value = FavoritesSyncStatus.Processing(context.getString(R.string.favorites_sync_cleaning_up), context = context)
storage.snapshotEntries() storage.snapshotEntries()
}
launchUI { withUIContext {
context.toast(context.getString(R.string.favorites_sync_complete)) context.toast(R.string.favorites_sync_complete)
} }
} catch (e: IgnoredException) { } catch (e: IgnoredException) {
// Do not display error as this error has already been reported // Do not display error as this error has already been reported
@ -196,46 +199,33 @@ class FavoritesSyncHelper(val context: Context) {
} }
} }
private fun applyRemoteCategories(categories: List<String>) { private suspend fun applyRemoteCategories(categories: List<String>) {
val localCategories = db.getCategories().executeAsBlocking() val localCategories = getCategories.await()
val newLocalCategories = localCategories.toMutableList() val newLocalCategories = localCategories.toMutableList()
var changed = false
categories.forEachIndexed { index, remote -> categories.forEachIndexed { index, remote ->
val local = localCategories.getOrElse(index) { val local = localCategories.getOrElse(index) {
changed = true val newCategoryId = handler.awaitOne(true) {
categoriesQueries.insert(remote, index.toLong(), 0L, emptyList())
Category.create(remote).apply { categoriesQueries.selectLastInsertedRowId()
order = index
// Going through categories list from front to back
// If category does not exist, list size <= category index
// Thus, we can just add it here and not worry about indexing
newLocalCategories += this
} }
Category(newCategoryId, remote, index.toLong(), 0L, emptyList())
.also { newLocalCategories += it }
} }
if (local.name != remote) { // Ensure consistent ordering and naming
changed = true if (local.name != remote || local.order != index.toLong()) {
handler.await {
local.name = remote categoriesQueries.update(
categoryId = local.id,
order = index.toLong().takeIf { it != local.order },
name = remote.takeIf { it != local.name },
flags = null,
mangaOrder = null,
)
} }
} }
// Ensure consistent ordering
newLocalCategories.forEachIndexed { index, category ->
if (category.order != index) {
changed = true
category.order = index
}
}
// Only insert categories if changed
if (changed) {
db.insertCategories(newLocalCategories).executeAsBlocking()
} }
} }
@ -339,27 +329,25 @@ class FavoritesSyncHelper(val context: Context) {
// Consider both EX and EH sources // Consider both EX and EH sources
listOf( listOf(
db.getManga(url, EXH_SOURCE_ID), EXH_SOURCE_ID,
db.getManga(url, EH_SOURCE_ID), EH_SOURCE_ID,
).forEach { ).forEach {
val manga = it.executeAsBlocking() val manga = getMangaByUrlAndSource.await(url, it)
if (manga?.favorite == true) { if (manga?.favorite == true) {
manga.favorite = false updateManga.awaitUpdateFavorite(manga.id, false)
manga.date_added = 0
db.updateMangaFavorite(manga).executeAsBlocking()
removedManga += manga removedManga += manga
} }
} }
} }
// Can't do too many DB OPs in one go // Can't do too many DB OPs in one go
removedManga.chunked(10).forEach { removedManga.forEach {
db.deleteOldMangasCategories(it).executeAsBlocking() setMangaCategories.await(it.id, emptyList())
} }
val insertedMangaCategories = mutableListOf<Pair<MangaCategory, Manga>>() val insertedMangaCategories = mutableListOf<Pair<Long, Manga>>()
val categories = db.getCategories().executeAsBlocking() val categories = getCategories.await()
// Apply additions // Apply additions
throttleManager.resetThrottle() throttleManager.resetThrottle()
@ -402,18 +390,13 @@ class FavoritesSyncHelper(val context: Context) {
throw IgnoredException() throw IgnoredException()
} }
} else if (result is GalleryAddEvent.Success) { } else if (result is GalleryAddEvent.Success) {
insertedMangaCategories += MangaCategory.create( insertedMangaCategories += categories[it.category].id to result.manga
result.manga,
categories[it.category],
) to result.manga
} }
} }
// Can't do too many DB OPs in one go // Can't do too many DB OPs in one go
insertedMangaCategories.chunked(10).map { mangaCategories -> insertedMangaCategories.forEach { (category, manga) ->
mangaCategories.map { it.first } to mangaCategories.map { it.second } setMangaCategories.await(manga.id, listOf(category))
}.forEach {
db.setMangaCategories(it.first, it.second)
} }
} }
@ -424,7 +407,6 @@ class FavoritesSyncHelper(val context: Context) {
fun onDestroy() { fun onDestroy() {
scope.cancel() scope.cancel()
dispatcher.close()
} }
companion object { companion object {

View File

@ -1,56 +1,77 @@
package exh.favorites package exh.favorites
import eu.kanade.tachiyomi.data.database.DatabaseHelper import eu.kanade.data.DatabaseHandler
import eu.kanade.tachiyomi.data.database.models.Manga import eu.kanade.data.exh.favoriteEntryMapper
import eu.kanade.domain.category.interactor.GetCategories
import eu.kanade.domain.manga.interactor.GetFavorites
import eu.kanade.domain.manga.model.Manga
import eu.kanade.tachiyomi.data.database.models.toDomainManga
import eu.kanade.tachiyomi.source.online.all.EHentai import eu.kanade.tachiyomi.source.online.all.EHentai
import exh.favorites.sql.models.FavoriteEntry import exh.favorites.sql.models.FavoriteEntry
import exh.metadata.metadata.EHentaiSearchMetadata import exh.metadata.metadata.EHentaiSearchMetadata
import exh.source.isEhBasedManga import exh.source.isEhBasedManga
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.asFlow
import kotlinx.coroutines.flow.filter
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.mapNotNull
import kotlinx.coroutines.flow.toList
import uy.kohesive.injekt.injectLazy import uy.kohesive.injekt.injectLazy
class LocalFavoritesStorage { class LocalFavoritesStorage {
private val db: DatabaseHelper by injectLazy() private val handler: DatabaseHandler by injectLazy()
private val getFavorites: GetFavorites by injectLazy()
private val getCategories: GetCategories by injectLazy()
fun getChangedDbEntries() = db.getFavoriteMangas() suspend fun getChangedDbEntries() = getFavorites.await()
.executeAsBlocking() .asFlow()
.asSequence()
.loadDbCategories() .loadDbCategories()
.parseToFavoriteEntries() .parseToFavoriteEntries()
.getChangedEntries() .getChangedEntries()
fun getChangedRemoteEntries(entries: List<EHentai.ParsedManga>) = entries suspend fun getChangedRemoteEntries(entries: List<EHentai.ParsedManga>) = entries
.asSequence() .asFlow()
.map { .map {
it.fav to it.manga.apply { it.fav to it.manga.apply {
id = -1
favorite = true favorite = true
date_added = System.currentTimeMillis() date_added = System.currentTimeMillis()
} }.toDomainManga()!!
} }
.parseToFavoriteEntries() .parseToFavoriteEntries()
.getChangedEntries() .getChangedEntries()
fun snapshotEntries() { suspend fun snapshotEntries() {
val dbMangas = db.getFavoriteMangas() val dbMangas = getFavorites.await()
.executeAsBlocking() .asFlow()
.asSequence()
.loadDbCategories() .loadDbCategories()
.parseToFavoriteEntries() .parseToFavoriteEntries()
// Delete old snapshot // Delete old snapshot
db.deleteAllFavoriteEntries().executeAsBlocking() handler.await { eh_favoritesQueries.deleteAll() }
// Insert new snapshots // Insert new snapshots
db.insertFavoriteEntries(dbMangas.toList()).executeAsBlocking() handler.await(true) {
dbMangas.toList().forEach {
eh_favoritesQueries.insertEhFavorites(
it.id,
it.title,
it.gid,
it.token,
it.category.toLong(),
)
}
}
} }
fun clearSnapshots() { suspend fun clearSnapshots() {
db.deleteAllFavoriteEntries().executeAsBlocking() handler.await { eh_favoritesQueries.deleteAll() }
} }
private fun Sequence<FavoriteEntry>.getChangedEntries(): ChangeSet { private suspend fun Flow<FavoriteEntry>.getChangedEntries(): ChangeSet {
val terminated = toList() val terminated = toList()
val databaseEntries = db.getFavoriteEntries().executeAsBlocking() val databaseEntries = handler.awaitList { eh_favoritesQueries.selectAll(favoriteEntryMapper) }
val added = terminated.filter { val added = terminated.filter {
queryListForEntry(databaseEntries, it) == null queryListForEntry(databaseEntries, it) == null
@ -74,11 +95,11 @@ class LocalFavoritesStorage {
it.category == entry.category it.category == entry.category
} }
private fun Sequence<Manga>.loadDbCategories(): Sequence<Pair<Int, Manga>> { private suspend fun Flow<Manga>.loadDbCategories(): Flow<Pair<Int, Manga>> {
val dbCategories = db.getCategories().executeAsBlocking() val dbCategories = getCategories.await()
return filter(::validateDbManga).mapNotNull { return filter(::validateDbManga).mapNotNull {
val category = db.getCategoriesForManga(it).executeAsBlocking() val category = getCategories.await(it.id)
dbCategories.indexOf( dbCategories.indexOf(
category.firstOrNull() category.firstOrNull()
@ -87,12 +108,12 @@ class LocalFavoritesStorage {
} }
} }
private fun Sequence<Pair<Int, Manga>>.parseToFavoriteEntries() = private fun Flow<Pair<Int, Manga>>.parseToFavoriteEntries() =
filter { (_, manga) -> filter { (_, manga) ->
validateDbManga(manga) validateDbManga(manga)
}.mapNotNull { (categoryId, manga) -> }.mapNotNull { (categoryId, manga) ->
FavoriteEntry( FavoriteEntry(
title = manga.originalTitle, title = manga.ogTitle,
gid = EHentaiSearchMetadata.galleryId(manga.url), gid = EHentaiSearchMetadata.galleryId(manga.url),
token = EHentaiSearchMetadata.galleryToken(manga.url), token = EHentaiSearchMetadata.galleryToken(manga.url),
category = categoryId, category = categoryId,

View File

@ -1,6 +1,7 @@
package exh.md.handlers package exh.md.handlers
import eu.kanade.tachiyomi.data.database.DatabaseHelper import eu.kanade.data.DatabaseHandler
import eu.kanade.domain.manga.interactor.GetMangaByUrlAndSource
import eu.kanade.tachiyomi.source.model.SManga import eu.kanade.tachiyomi.source.model.SManga
import exh.log.xLogE import exh.log.xLogE
import exh.md.dto.ChapterDataDto import exh.md.dto.ChapterDataDto
@ -12,8 +13,8 @@ import exh.md.utils.MdUtil
import exh.md.utils.asMdMap import exh.md.utils.asMdMap
import exh.metadata.metadata.MangaDexSearchMetadata import exh.metadata.metadata.MangaDexSearchMetadata
import exh.metadata.metadata.base.RaisedTag import exh.metadata.metadata.base.RaisedTag
import exh.metadata.metadata.base.getFlatMetadataForManga import exh.metadata.metadata.base.awaitFlatMetadataForManga
import exh.metadata.metadata.base.insertFlatMetadata import exh.metadata.metadata.base.awaitInsertFlatMetadata
import exh.util.capitalize import exh.util.capitalize
import exh.util.floor import exh.util.floor
import exh.util.nullIfEmpty import exh.util.nullIfEmpty
@ -25,7 +26,8 @@ import java.util.Locale
class ApiMangaParser( class ApiMangaParser(
private val lang: String, private val lang: String,
) { ) {
val db: DatabaseHelper by injectLazy() private val handler: DatabaseHandler by injectLazy()
private val getMangaByUrlAndSource: GetMangaByUrlAndSource by injectLazy()
val metaClass = MangaDexSearchMetadata::class val metaClass = MangaDexSearchMetadata::class
@ -37,23 +39,23 @@ class ApiMangaParser(
}?.call() }?.call()
?: error("Could not find no-args constructor for meta class: ${metaClass.qualifiedName}!") ?: error("Could not find no-args constructor for meta class: ${metaClass.qualifiedName}!")
fun parseToManga( suspend fun parseToManga(
manga: MangaInfo, manga: MangaInfo,
sourceId: Long, sourceId: Long,
input: MangaDto, input: MangaDto,
simpleChapters: List<String>, simpleChapters: List<String>,
statistics: StatisticsMangaDto?, statistics: StatisticsMangaDto?,
): MangaInfo { ): MangaInfo {
val mangaId = db.getManga(manga.key, sourceId).executeAsBlocking()?.id val mangaId = getMangaByUrlAndSource.await(manga.key, sourceId)?.id
val metadata = if (mangaId != null) { val metadata = if (mangaId != null) {
val flatMetadata = db.getFlatMetadataForManga(mangaId).executeAsBlocking() val flatMetadata = handler.awaitFlatMetadataForManga(mangaId)
flatMetadata?.raise(metaClass) ?: newMetaInstance() flatMetadata?.raise(metaClass) ?: newMetaInstance()
} else newMetaInstance() } else newMetaInstance()
parseIntoMetadata(metadata, input, simpleChapters, statistics) parseIntoMetadata(metadata, input, simpleChapters, statistics)
if (mangaId != null) { if (mangaId != null) {
metadata.mangaId = mangaId metadata.mangaId = mangaId
db.insertFlatMetadata(metadata.flatten()) handler.awaitInsertFlatMetadata(metadata.flatten())
} }
return metadata.createMangaInfo(manga) return metadata.createMangaInfo(manga)

View File

@ -1,7 +1,7 @@
package exh.md.similar package exh.md.similar
import eu.kanade.tachiyomi.data.database.models.Manga import eu.kanade.domain.manga.model.Manga
import eu.kanade.tachiyomi.data.database.models.toMangaInfo import eu.kanade.domain.manga.model.toMangaInfo
import eu.kanade.tachiyomi.source.model.MetadataMangasPage import eu.kanade.tachiyomi.source.model.MetadataMangasPage
import eu.kanade.tachiyomi.source.online.all.MangaDex import eu.kanade.tachiyomi.source.online.all.MangaDex
import eu.kanade.tachiyomi.ui.browse.source.browse.NoResultsException import eu.kanade.tachiyomi.ui.browse.source.browse.NoResultsException

View File

@ -1,26 +1,30 @@
package exh.md.similar package exh.md.similar
import eu.kanade.tachiyomi.data.database.DatabaseHelper import eu.kanade.domain.manga.interactor.GetMangaById
import eu.kanade.tachiyomi.data.database.models.Manga import eu.kanade.domain.manga.model.Manga
import eu.kanade.tachiyomi.source.model.FilterList import eu.kanade.tachiyomi.source.model.FilterList
import eu.kanade.tachiyomi.source.online.all.MangaDex import eu.kanade.tachiyomi.source.online.all.MangaDex
import eu.kanade.tachiyomi.ui.browse.source.browse.BrowseSourcePresenter import eu.kanade.tachiyomi.ui.browse.source.browse.BrowseSourcePresenter
import eu.kanade.tachiyomi.ui.browse.source.browse.Pager import eu.kanade.tachiyomi.ui.browse.source.browse.Pager
import exh.source.getMainSource import exh.source.getMainSource
import uy.kohesive.injekt.injectLazy import kotlinx.coroutines.runBlocking
import uy.kohesive.injekt.Injekt
import uy.kohesive.injekt.api.get
/** /**
* Presenter of [MangaDexSimilarController]. Inherit BrowseCataloguePresenter. * Presenter of [MangaDexSimilarController]. Inherit BrowseCataloguePresenter.
*/ */
class MangaDexSimilarPresenter(val mangaId: Long, sourceId: Long) : BrowseSourcePresenter(sourceId) { class MangaDexSimilarPresenter(
val mangaId: Long,
sourceId: Long,
private val getMangaById: GetMangaById = Injekt.get(),
) : BrowseSourcePresenter(sourceId) {
var manga: Manga? = null var manga: Manga? = null
val db: DatabaseHelper by injectLazy()
override fun createPager(query: String, filters: FilterList): Pager { override fun createPager(query: String, filters: FilterList): Pager {
val sourceAsMangaDex = source.getMainSource() as MangaDex val sourceAsMangaDex = source.getMainSource() as MangaDex
this.manga = db.getManga(mangaId).executeAsBlocking() this.manga = runBlocking { getMangaById.await(mangaId) }
return MangaDexSimilarPager(manga!!, sourceAsMangaDex) return MangaDexSimilarPager(manga!!, sourceAsMangaDex)
} }
} }

View File

@ -1,6 +1,6 @@
package exh.recs package exh.recs
import eu.kanade.tachiyomi.data.database.models.Manga import eu.kanade.domain.manga.model.Manga
import eu.kanade.tachiyomi.network.GET import eu.kanade.tachiyomi.network.GET
import eu.kanade.tachiyomi.network.NetworkHelper import eu.kanade.tachiyomi.network.NetworkHelper
import eu.kanade.tachiyomi.network.POST import eu.kanade.tachiyomi.network.POST
@ -203,7 +203,7 @@ open class RecommendsPager(
val recs = apiList.firstNotNullOfOrNull { (key, api) -> val recs = apiList.firstNotNullOfOrNull { (key, api) ->
try { try {
val recs = api.getRecsBySearch(manga.originalTitle) val recs = api.getRecsBySearch(manga.ogTitle)
logcat { key.toString() + " > Results: " + recs.count() } logcat { key.toString() + " > Results: " + recs.count() }
recs recs
} catch (e: Exception) { } catch (e: Exception) {

View File

@ -1,23 +1,27 @@
package exh.recs package exh.recs
import eu.kanade.tachiyomi.data.database.DatabaseHelper import eu.kanade.domain.manga.interactor.GetMangaById
import eu.kanade.tachiyomi.data.database.models.Manga import eu.kanade.domain.manga.model.Manga
import eu.kanade.tachiyomi.source.model.FilterList import eu.kanade.tachiyomi.source.model.FilterList
import eu.kanade.tachiyomi.ui.browse.source.browse.BrowseSourcePresenter import eu.kanade.tachiyomi.ui.browse.source.browse.BrowseSourcePresenter
import eu.kanade.tachiyomi.ui.browse.source.browse.Pager import eu.kanade.tachiyomi.ui.browse.source.browse.Pager
import uy.kohesive.injekt.injectLazy import kotlinx.coroutines.runBlocking
import uy.kohesive.injekt.Injekt
import uy.kohesive.injekt.api.get
/** /**
* Presenter of [RecommendsController]. Inherit BrowseCataloguePresenter. * Presenter of [RecommendsController]. Inherit BrowseCataloguePresenter.
*/ */
class RecommendsPresenter(val mangaId: Long, sourceId: Long) : BrowseSourcePresenter(sourceId) { class RecommendsPresenter(
val mangaId: Long,
sourceId: Long,
private val getMangaById: GetMangaById = Injekt.get(),
) : BrowseSourcePresenter(sourceId) {
var manga: Manga? = null var manga: Manga? = null
val db: DatabaseHelper by injectLazy()
override fun createPager(query: String, filters: FilterList): Pager { override fun createPager(query: String, filters: FilterList): Pager {
this.manga = db.getManga(mangaId).executeAsBlocking() this.manga = runBlocking { getMangaById.await(mangaId) }
return RecommendsPager(manga!!) return RecommendsPager(manga!!)
} }
} }

View File

@ -6,9 +6,9 @@ import android.view.MenuItem
import androidx.core.view.isVisible import androidx.core.view.isVisible
import androidx.lifecycle.lifecycleScope import androidx.lifecycle.lifecycleScope
import com.google.android.material.dialog.MaterialAlertDialogBuilder import com.google.android.material.dialog.MaterialAlertDialogBuilder
import eu.kanade.domain.chapter.model.Chapter
import eu.kanade.domain.manga.model.Manga
import eu.kanade.tachiyomi.R import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.data.database.models.Chapter
import eu.kanade.tachiyomi.data.database.models.Manga
import eu.kanade.tachiyomi.databinding.EhActivityInterceptBinding import eu.kanade.tachiyomi.databinding.EhActivityInterceptBinding
import eu.kanade.tachiyomi.source.online.UrlImportableSource import eu.kanade.tachiyomi.source.online.UrlImportableSource
import eu.kanade.tachiyomi.ui.base.activity.BaseActivity import eu.kanade.tachiyomi.ui.base.activity.BaseActivity
@ -70,7 +70,7 @@ class InterceptActivity : BaseActivity() {
onBackPressed() onBackPressed()
startActivity( startActivity(
if (it.chapter != null) { if (it.chapter != null) {
ReaderActivity.newIntent(this, it.manga.id!!, it.chapter.id!!) ReaderActivity.newIntent(this, it.manga.id, it.chapter.id)
} else { } else {
Intent(this, MainActivity::class.java) Intent(this, MainActivity::class.java)
.setAction(MainActivity.SHORTCUT_MANGA) .setAction(MainActivity.SHORTCUT_MANGA)
@ -133,9 +133,7 @@ class InterceptActivity : BaseActivity() {
val result = galleryAdder.addGallery(this@InterceptActivity, gallery, forceSource = source) val result = galleryAdder.addGallery(this@InterceptActivity, gallery, forceSource = source)
status.value = when (result) { status.value = when (result) {
is GalleryAddEvent.Success -> result.manga.id?.let { is GalleryAddEvent.Success -> InterceptResult.Success(result.manga.id, result.manga, result.chapter)
InterceptResult.Success(it, result.manga, result.chapter)
} ?: InterceptResult.Failure(getString(R.string.manga_id_is_null))
is GalleryAddEvent.Fail -> InterceptResult.Failure(result.logMessage) is GalleryAddEvent.Fail -> InterceptResult.Failure(result.logMessage)
} }
} }

View File

@ -8,6 +8,7 @@ import eu.kanade.tachiyomi.ui.reader.setting.ReadingModeType
import uy.kohesive.injekt.Injekt import uy.kohesive.injekt.Injekt
import uy.kohesive.injekt.api.get import uy.kohesive.injekt.api.get
import java.util.Locale import java.util.Locale
import eu.kanade.domain.manga.model.Manga as DomainManga
fun Manga.mangaType(context: Context): String { fun Manga.mangaType(context: Context): String {
return context.getString( return context.getString(
@ -48,6 +49,30 @@ fun Manga.mangaType(sourceName: String? = Injekt.get<SourceManager>().get(source
} }
} }
fun DomainManga.mangaType(sourceName: String? = Injekt.get<SourceManager>().get(source)?.name): MangaType {
val currentTags = genre.orEmpty()
return when {
currentTags.any { tag -> isMangaTag(tag) } -> {
MangaType.TYPE_MANGA
}
currentTags.any { tag -> isWebtoonTag(tag) } || sourceName?.let { isWebtoonSource(it) } == true -> {
MangaType.TYPE_WEBTOON
}
currentTags.any { tag -> isComicTag(tag) } || sourceName?.let { isComicSource(it) } == true -> {
MangaType.TYPE_COMIC
}
currentTags.any { tag -> isManhuaTag(tag) } || sourceName?.let { isManhuaSource(it) } == true -> {
MangaType.TYPE_MANHUA
}
currentTags.any { tag -> isManhwaTag(tag) } || sourceName?.let { isManhwaSource(it) } == true -> {
MangaType.TYPE_MANHWA
}
else -> {
MangaType.TYPE_MANGA
}
}
}
/** /**
* The type the reader should use. Different from manga type as certain manga has different * The type the reader should use. Different from manga type as certain manga has different
* read types * read types

View File

@ -24,7 +24,7 @@ fun UrlImportableSource.urlImportFetchSearchManga(context: Context, query: Strin
.map { res -> .map { res ->
MangasPage( MangasPage(
if (res is GalleryAddEvent.Success) { if (res is GalleryAddEvent.Success) {
listOf(res.manga) listOf(res.manga.toSManga())
} else { } else {
emptyList() emptyList()
}, },

View File

@ -30,6 +30,9 @@ insert:
INSERT INTO mangas(source,url,artist,author,description,genre,title,status,thumbnail_url,favorite,last_update,next_update,initialized,viewer,chapter_flags,cover_last_modified,date_added) 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,: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,:thumbnail_url,:favorite,:last_update,:next_update,:initialized,:viewer,:chapter_flags,:cover_last_modified,:date_added);
insertEmpty:
INSERT INTO mangas (source, url, title, artist, author, description, genre, status, thumbnail_url, favorite, last_update, next_update, initialized, viewer, chapter_flags, cover_last_modified, date_added, filtered_scanlators) VALUES (?, ?, ?, NULL, NULL, NULL, NULL, 0, NULL, 0, NULL, NULL, 0, 0, 0, 0, 0, NULL);
getMangaById: getMangaById:
SELECT * SELECT *
FROM mangas FROM mangas
@ -186,3 +189,14 @@ WHERE _id = :mangaId;
selectLastInsertedRowId: selectLastInsertedRowId:
SELECT last_insert_rowid(); SELECT last_insert_rowid();
getEhMangaWithMetadata:
SELECT mangas.* FROM mangas
INNER JOIN search_metadata
ON mangas._id = search_metadata.manga_id
WHERE mangas.favorite = 1 AND (mangas.source = :eh OR mangas.source = :exh);
selectLastInsertRow:
SELECT *
FROM mangas
WHERE _id = last_insert_rowid();