From 6c530e4fcabd0eceffc62b613a9817cedcdd273e Mon Sep 17 00:00:00 2001 From: Cuong-Tran Date: Thu, 12 Jun 2025 15:39:09 +0700 Subject: [PATCH] fix(recommendation): Fix null RecommendationSource & refactoring (#1000) * Remove redundant condition & refactor class * Batch networkToLocalManga * Fix null source * Simplify by creating RecommendationSource to match with upstream * Add comment --- .../md/similar/MangaDexSimilarPagingSource.kt | 10 +-- .../java/exh/recs/BrowseRecommendsScreen.kt | 2 +- .../exh/recs/BrowseRecommendsScreenModel.kt | 9 +-- .../main/java/exh/recs/RecommendsScreen.kt | 1 + .../java/exh/recs/RecommendsScreenModel.kt | 7 +- .../recs/batch/RecommendationSearchHelper.kt | 19 +++--- .../exh/recs/sources/ComickPagingSource.kt | 9 +-- .../sources/RecommendationPagingSource.kt | 68 +++++++++++++------ .../data/source/EHentaiPagingSource.kt | 8 +-- .../data/source/SourcePagingSource.kt | 12 ++-- 10 files changed, 87 insertions(+), 58 deletions(-) diff --git a/app/src/main/java/exh/md/similar/MangaDexSimilarPagingSource.kt b/app/src/main/java/exh/md/similar/MangaDexSimilarPagingSource.kt index 87c207e2b..5c9e06969 100644 --- a/app/src/main/java/exh/md/similar/MangaDexSimilarPagingSource.kt +++ b/app/src/main/java/exh/md/similar/MangaDexSimilarPagingSource.kt @@ -11,7 +11,7 @@ import exh.md.service.MangaDexService import exh.md.service.SimilarService import exh.md.utils.MdLang import exh.recs.sources.RecommendationPagingSource -import exh.recs.sources.SourceCatalogue +import exh.recs.sources.RecommendationSource import kotlinx.coroutines.async import kotlinx.coroutines.coroutineScope import tachiyomi.data.source.NoResultsException @@ -26,12 +26,12 @@ import uy.kohesive.injekt.api.get internal class MangaDexSimilarPagingSource( manga: Manga, // KMK --> - private val sourceCatalogue: SourceCatalogue, + private val recommendationSource: RecommendationSource, // KMK <-- ) : RecommendationPagingSource( manga, // KMK --> - sourceCatalogue.source, + recommendationSource, // KMK <-- ) { @@ -43,12 +43,12 @@ internal class MangaDexSimilarPagingSource( override val associatedSourceId: Long // KMK --> - get() = sourceCatalogue.sourceId + get() = recommendationSource.id private val client by lazy { Injekt.get().client } private val mdLang by lazy { - sourceCatalogue.source?.lang?.let { lang -> + recommendationSource.lang.let { lang -> MdLang.fromExt(lang) } ?: MdLang.ENGLISH } diff --git a/app/src/main/java/exh/recs/BrowseRecommendsScreen.kt b/app/src/main/java/exh/recs/BrowseRecommendsScreen.kt index f7ac4af2f..a560a4da3 100644 --- a/app/src/main/java/exh/recs/BrowseRecommendsScreen.kt +++ b/app/src/main/java/exh/recs/BrowseRecommendsScreen.kt @@ -112,7 +112,7 @@ class BrowseRecommendsScreen( } else { // KMK <-- val title = remember { - val recSource = screenModel.recommendationSource + val recSource = screenModel.recommendationPagingSource "${recSource.name} (${recSource.category.getString(context)})" } diff --git a/app/src/main/java/exh/recs/BrowseRecommendsScreenModel.kt b/app/src/main/java/exh/recs/BrowseRecommendsScreenModel.kt index 830c68b94..e7863b3b0 100644 --- a/app/src/main/java/exh/recs/BrowseRecommendsScreenModel.kt +++ b/app/src/main/java/exh/recs/BrowseRecommendsScreenModel.kt @@ -4,8 +4,9 @@ import cafe.adriel.voyager.core.model.screenModelScope import eu.kanade.tachiyomi.source.model.FilterList import eu.kanade.tachiyomi.ui.browse.source.browse.BrowseSourceScreenModel import exh.metadata.metadata.RaisedSearchMetadata +import exh.recs.sources.RECOMMENDS_SOURCE import exh.recs.sources.RecommendationPagingSource -import exh.recs.sources.SourceCatalogue +import exh.recs.sources.RecommendationSource import exh.recs.sources.StaticResultPagingSource import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.flow.Flow @@ -40,20 +41,20 @@ class BrowseRecommendsScreenModel( } } - val recommendationSource: RecommendationPagingSource + val recommendationPagingSource: RecommendationPagingSource get() = when (args) { is BrowseRecommendsScreen.Args.MergedSourceMangas -> StaticResultPagingSource(args.results) is BrowseRecommendsScreen.Args.SingleSourceManga -> RecommendationPagingSource.createSources( manga ?: runBlocking(Dispatchers.IO) { getManga.await(args.mangaId)!! }, // KMK --> - SourceCatalogue(sourceId), + RecommendationSource(sourceId), // KMK <-- ).first { it::class.qualifiedName == args.recommendationSourceName } } - override fun createSourcePagingSource(query: String, filters: FilterList) = recommendationSource + override fun createSourcePagingSource(query: String, filters: FilterList) = recommendationPagingSource override fun Flow.combineMetadata(metadata: RaisedSearchMetadata?): Flow> { // Overridden to prevent our custom metadata from being replaced from a cache diff --git a/app/src/main/java/exh/recs/RecommendsScreen.kt b/app/src/main/java/exh/recs/RecommendsScreen.kt index 8de0ae29d..fc86af92c 100644 --- a/app/src/main/java/exh/recs/RecommendsScreen.kt +++ b/app/src/main/java/exh/recs/RecommendsScreen.kt @@ -20,6 +20,7 @@ import exh.recs.RecommendsScreen.Args.MergedSourceMangas import exh.recs.RecommendsScreen.Args.SingleSourceManga import exh.recs.batch.RankedSearchResults import exh.recs.components.RecommendsScreen +import exh.recs.sources.RECOMMENDS_SOURCE import exh.recs.sources.StaticResultPagingSource import tachiyomi.domain.manga.model.Manga import tachiyomi.i18n.sy.SYMR diff --git a/app/src/main/java/exh/recs/RecommendsScreenModel.kt b/app/src/main/java/exh/recs/RecommendsScreenModel.kt index 2eef74a94..62adc5ae6 100644 --- a/app/src/main/java/exh/recs/RecommendsScreenModel.kt +++ b/app/src/main/java/exh/recs/RecommendsScreenModel.kt @@ -5,8 +5,9 @@ import androidx.compose.runtime.Immutable import androidx.compose.runtime.produceState import cafe.adriel.voyager.core.model.StateScreenModel import eu.kanade.presentation.util.ioCoroutineScope +import exh.recs.sources.RECOMMENDS_SOURCE import exh.recs.sources.RecommendationPagingSource -import exh.recs.sources.SourceCatalogue +import exh.recs.sources.RecommendationSource import exh.recs.sources.StaticResultPagingSource import kotlinx.collections.immutable.PersistentMap import kotlinx.collections.immutable.mutate @@ -55,7 +56,7 @@ open class RecommendsScreenModel( RecommendationPagingSource.createSources( manga, // KMK --> - SourceCatalogue(args.sourceId), + RecommendationSource(args.sourceId), // KMK <-- ) } @@ -163,5 +164,3 @@ sealed interface RecommendationItemResult { return !onlyShowHasResults || (this is Success && !this.isEmpty) } } - -const val RECOMMENDS_SOURCE = -1L diff --git a/app/src/main/java/exh/recs/batch/RecommendationSearchHelper.kt b/app/src/main/java/exh/recs/batch/RecommendationSearchHelper.kt index ed17edd7e..cce2c7dbf 100644 --- a/app/src/main/java/exh/recs/batch/RecommendationSearchHelper.kt +++ b/app/src/main/java/exh/recs/batch/RecommendationSearchHelper.kt @@ -9,7 +9,7 @@ import eu.kanade.domain.manga.model.toSManga import eu.kanade.tachiyomi.source.model.SManga import exh.log.xLog import exh.recs.sources.RecommendationPagingSource -import exh.recs.sources.SourceCatalogue +import exh.recs.sources.RecommendationSource import exh.recs.sources.TrackerRecommendationPagingSource import exh.smartsearch.SmartLibrarySearchEngine import exh.util.ThrottleManager @@ -101,7 +101,7 @@ class RecommendationSearchHelper(val context: Context) { val jobs = RecommendationPagingSource.createSources( sourceManga, // KMK --> - SourceCatalogue(sourceManga.source), + RecommendationSource(sourceManga.source), // KMK <-- ).mapNotNull { source -> // Apply source filters @@ -198,13 +198,16 @@ class RecommendationSearchHelper(val context: Context) { return this } - return filterNot { manga -> - // Source recommendations can be directly resolved, if the recommendation is from the same source - recSource.associatedSourceId?.let { srcId -> - return@filterNot networkToLocalManga(manga.toDomainManga(srcId)) - .let { local -> libraryManga.any { it.id == local.id } } - } + // KMK --> + // Source recommendations can be directly resolved, if the recommendation is from the same source + recSource.associatedSourceId?.let { srcId -> + return networkToLocalManga(map { it.toDomainManga(srcId) }) + .filterNot { local -> libraryManga.any { it.id == local.id } } + .map { it.toSManga() } + } + // KMK <-- + return filterNot { manga -> // Tracker recommendations can be resolved by checking if the tracker is attached to the recommendation if (recSource is TrackerRecommendationPagingSource) { recSource.associatedTrackerId?.let { trackerId -> diff --git a/app/src/main/java/exh/recs/sources/ComickPagingSource.kt b/app/src/main/java/exh/recs/sources/ComickPagingSource.kt index b1b8bb4e4..f214f771e 100644 --- a/app/src/main/java/exh/recs/sources/ComickPagingSource.kt +++ b/app/src/main/java/exh/recs/sources/ComickPagingSource.kt @@ -5,7 +5,6 @@ import eu.kanade.tachiyomi.network.GET import eu.kanade.tachiyomi.network.NetworkHelper import eu.kanade.tachiyomi.network.awaitSuccess import eu.kanade.tachiyomi.network.parseAs -import eu.kanade.tachiyomi.source.CatalogueSource import eu.kanade.tachiyomi.source.model.MangasPage import eu.kanade.tachiyomi.source.model.SManga import kotlinx.coroutines.coroutineScope @@ -23,17 +22,15 @@ import uy.kohesive.injekt.Injekt import uy.kohesive.injekt.api.get import uy.kohesive.injekt.injectLazy -fun CatalogueSource.isComickSource() = name == "Comick" - internal class ComickPagingSource( manga: Manga, // KMK --> - private val sourceCatalogue: SourceCatalogue, + private val recommendationSource: RecommendationSource, // KMK <-- ) : RecommendationPagingSource( manga, // KMK --> - sourceCatalogue.source, + recommendationSource, // KMK <-- ) { @@ -45,7 +42,7 @@ internal class ComickPagingSource( override val associatedSourceId: Long // KMK --> - get() = sourceCatalogue.sourceId + get() = recommendationSource.id // KMK <-- private val client by lazy { Injekt.get().client } diff --git a/app/src/main/java/exh/recs/sources/RecommendationPagingSource.kt b/app/src/main/java/exh/recs/sources/RecommendationPagingSource.kt index 6e23b382e..24b515ac0 100644 --- a/app/src/main/java/exh/recs/sources/RecommendationPagingSource.kt +++ b/app/src/main/java/exh/recs/sources/RecommendationPagingSource.kt @@ -4,13 +4,13 @@ import dev.icerock.moko.resources.StringResource import eu.kanade.tachiyomi.data.track.TrackerManager import eu.kanade.tachiyomi.network.NetworkHelper import eu.kanade.tachiyomi.source.CatalogueSource +import eu.kanade.tachiyomi.source.model.FilterList import eu.kanade.tachiyomi.source.model.MangasPage +import eu.kanade.tachiyomi.source.model.SChapter import eu.kanade.tachiyomi.source.model.SManga import exh.md.similar.MangaDexSimilarPagingSource -import exh.pref.DelegateSourcePreferences import exh.source.COMICK_IDS import exh.source.MANGADEX_IDS -import exh.source.isMdBasedSource import kotlinx.serialization.json.Json import logcat.LogPriority import tachiyomi.core.common.util.system.logcat @@ -29,7 +29,9 @@ import uy.kohesive.injekt.injectLazy */ abstract class RecommendationPagingSource( protected val manga: Manga, - source: CatalogueSource? = null, + // KMK --> + source: RecommendationSource = RecommendationSource(), + // KMK <-- ) : BaseSourcePagingSource(source) { // Display name abstract val name: String @@ -49,12 +51,9 @@ abstract class RecommendationPagingSource( internal fun createSources( manga: Manga, // KMK --> - sourceCatalogue: SourceCatalogue, + recommendationSource: RecommendationSource, // KMK <-- ): List { - // KMK --> - val source = sourceCatalogue.source - // KMK <-- return buildList { add(AniListPagingSource(manga)) add(MangaUpdatesCommunityPagingSource(manga)) @@ -64,16 +63,14 @@ abstract class RecommendationPagingSource( // Only include MangaDex if the delegate sources are enabled and the source is MD-based if ( // KMK --> - sourceCatalogue.isMangaDexSource() || + recommendationSource.isMangaDexSource() // KMK <-- - source?.isMdBasedSource() /* KMK --> */ == true /* KMK <-- */ && - Injekt.get().delegateSources().get() ) { add( MangaDexSimilarPagingSource( manga, // KMK --> - sourceCatalogue, + recommendationSource, // KMK <-- ), ) @@ -82,15 +79,14 @@ abstract class RecommendationPagingSource( // Only include Comick if the source manga is from there if ( // KMK --> - sourceCatalogue.isComickSource() || + recommendationSource.isComickSource() // KMK <-- - source?.isComickSource() /* KMK --> */ == true /* KMK <-- */ ) { add( ComickPagingSource( manga, // KMK --> - sourceCatalogue, + recommendationSource, // KMK <-- ), ) @@ -151,15 +147,45 @@ abstract class TrackerRecommendationPagingSource( } // KMK --> -internal class SourceCatalogue( - internal val sourceId: Long, +class RecommendationSource( + override val id: Long = RECOMMENDS_SOURCE, sourceManager: SourceManager = Injekt.get(), -) { - val source = sourceManager.get(sourceId) - ?.let { it as CatalogueSource } +) : CatalogueSource { + private val delegate by lazy { + sourceManager.get(id) + ?.let { it as CatalogueSource } + } - fun isComickSource(): Boolean = sourceId in COMICK_IDS + fun isComickSource(): Boolean = id in COMICK_IDS + fun isMangaDexSource(): Boolean = id in MANGADEX_IDS - fun isMangaDexSource(): Boolean = sourceId in MANGADEX_IDS + override val name: String by lazy { delegate?.name ?: "Recommends Source" } + override val lang: String by lazy { delegate?.lang ?: "all" } + override val supportsLatest by lazy { delegate?.supportsLatest ?: false } + + override suspend fun getMangaDetails(manga: SManga) = + delegate?.getMangaDetails(manga) + ?: throw UnsupportedOperationException() + override suspend fun getChapterList(manga: SManga) = + delegate?.getChapterList(manga) + ?: throw UnsupportedOperationException() + override suspend fun getPageList(chapter: SChapter) = + delegate?.getPageList(chapter) + ?: throw UnsupportedOperationException() + + override suspend fun getPopularManga(page: Int) = + delegate?.getPopularManga(page) + ?: throw UnsupportedOperationException() + override suspend fun getLatestUpdates(page: Int) = + delegate?.getLatestUpdates(page) + ?: throw UnsupportedOperationException() + override suspend fun getSearchManga(page: Int, query: String, filters: FilterList) = + delegate?.getSearchManga(page, query, filters) + ?: throw UnsupportedOperationException() + override fun getFilterList() = + delegate?.getFilterList() + ?: throw UnsupportedOperationException() } + +const val RECOMMENDS_SOURCE = -1L // KMK <-- diff --git a/data/src/main/java/tachiyomi/data/source/EHentaiPagingSource.kt b/data/src/main/java/tachiyomi/data/source/EHentaiPagingSource.kt index 390250e59..1d4782374 100644 --- a/data/src/main/java/tachiyomi/data/source/EHentaiPagingSource.kt +++ b/data/src/main/java/tachiyomi/data/source/EHentaiPagingSource.kt @@ -21,7 +21,7 @@ abstract class EHentaiPagingSource( val metadata = mangasPage.mangasMetadata val manga = mangasPage.mangas - .mapIndexed { index, sManga -> sManga.toDomainManga(source!!.id) to metadata.getOrNull(index) } + .mapIndexed { index, sManga -> sManga.toDomainManga(source.id) to metadata.getOrNull(index) } .filter { seenManga.add(it.first.url) } // KMK --> .let { pairs -> networkToLocalManga(pairs.map { it.first }).zip(pairs.map { it.second }) } @@ -41,18 +41,18 @@ class EHentaiSearchPagingSource( val filters: FilterList, ) : EHentaiPagingSource(source) { override suspend fun requestNextPage(currentPage: Int): MangasPage { - return source!!.getSearchManga(currentPage, query.sanitize(), filters) + return source.getSearchManga(currentPage, query.sanitize(), filters) } } class EHentaiPopularPagingSource(source: CatalogueSource) : EHentaiPagingSource(source) { override suspend fun requestNextPage(currentPage: Int): MangasPage { - return source!!.getPopularManga(currentPage) + return source.getPopularManga(currentPage) } } class EHentaiLatestPagingSource(source: CatalogueSource) : EHentaiPagingSource(source) { override suspend fun requestNextPage(currentPage: Int): MangasPage { - return source!!.getLatestUpdates(currentPage) + return source.getLatestUpdates(currentPage) } } diff --git a/data/src/main/java/tachiyomi/data/source/SourcePagingSource.kt b/data/src/main/java/tachiyomi/data/source/SourcePagingSource.kt index 82b2c7cd3..eb2c18f93 100644 --- a/data/src/main/java/tachiyomi/data/source/SourcePagingSource.kt +++ b/data/src/main/java/tachiyomi/data/source/SourcePagingSource.kt @@ -5,6 +5,7 @@ import eu.kanade.tachiyomi.source.CatalogueSource import eu.kanade.tachiyomi.source.model.FilterList import eu.kanade.tachiyomi.source.model.MangasPage import eu.kanade.tachiyomi.source.model.MetadataMangasPage +import exh.log.xLogE import exh.metadata.metadata.RaisedSearchMetadata import mihon.domain.manga.model.toDomainManga import tachiyomi.core.common.util.QuerySanitizer.sanitize @@ -21,24 +22,24 @@ class SourceSearchPagingSource( private val filters: FilterList, ) : BaseSourcePagingSource(source) { override suspend fun requestNextPage(currentPage: Int): MangasPage { - return source!!.getSearchManga(currentPage, query.sanitize(), filters) + return source.getSearchManga(currentPage, query.sanitize(), filters) } } class SourcePopularPagingSource(source: CatalogueSource) : BaseSourcePagingSource(source) { override suspend fun requestNextPage(currentPage: Int): MangasPage { - return source!!.getPopularManga(currentPage) + return source.getPopularManga(currentPage) } } class SourceLatestPagingSource(source: CatalogueSource) : BaseSourcePagingSource(source) { override suspend fun requestNextPage(currentPage: Int): MangasPage { - return source!!.getLatestUpdates(currentPage) + return source.getLatestUpdates(currentPage) } } abstract class BaseSourcePagingSource( - protected val source: CatalogueSource?, + protected val source: CatalogueSource, protected val networkToLocalManga: NetworkToLocalManga = Injekt.get(), ) : SourcePagingSource() { @@ -62,6 +63,7 @@ abstract class BaseSourcePagingSource( getPageLoadResult(params, mangasPage) // SY <-- } catch (e: Exception) { + xLogE("${this::class.simpleName}: Failed to load paging source", e) LoadResult.Error(e) } } @@ -83,7 +85,7 @@ abstract class BaseSourcePagingSource( val manga = mangasPage.mangas // SY --> - .mapIndexed { index, sManga -> sManga.toDomainManga(source!!.id) to metadata.getOrNull(index) } + .mapIndexed { index, sManga -> sManga.toDomainManga(source.id) to metadata.getOrNull(index) } .filter { seenManga.add(it.first.url) } // KMK --> .let { pairs -> networkToLocalManga(pairs.map { it.first }).zip(pairs.map { it.second }) }