Use coroutine job for fetching next source page

(cherry picked from commit e6f3cd03bbc8b9e0ea2203f981cda94062d5186c)

# Conflicts:
#	app/src/main/java/eu/kanade/tachiyomi/ui/browse/source/browse/BrowseSourcePresenter.kt
This commit is contained in:
arkon 2021-06-05 18:02:59 -04:00 committed by Jobobby04
parent 708c4b6905
commit 216065fb38
8 changed files with 61 additions and 109 deletions

View File

@ -41,6 +41,7 @@ import eu.kanade.tachiyomi.util.lang.withUIContext
import eu.kanade.tachiyomi.util.removeCovers import eu.kanade.tachiyomi.util.removeCovers
import exh.savedsearches.EXHSavedSearch import exh.savedsearches.EXHSavedSearch
import exh.savedsearches.JsonSavedSearch import exh.savedsearches.JsonSavedSearch
import kotlinx.coroutines.Job
import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.asFlow import kotlinx.coroutines.flow.asFlow
import kotlinx.coroutines.flow.catch import kotlinx.coroutines.flow.catch
@ -52,7 +53,6 @@ import kotlinx.serialization.decodeFromString
import kotlinx.serialization.encodeToString import kotlinx.serialization.encodeToString
import kotlinx.serialization.json.Json import kotlinx.serialization.json.Json
import kotlinx.serialization.json.JsonArray import kotlinx.serialization.json.JsonArray
import rx.Observable
import rx.Subscription import rx.Subscription
import rx.android.schedulers.AndroidSchedulers import rx.android.schedulers.AndroidSchedulers
import rx.schedulers.Schedulers import rx.schedulers.Schedulers
@ -117,7 +117,7 @@ open class BrowseSourcePresenter(
/** /**
* Subscription for one request from the pager. * Subscription for one request from the pager.
*/ */
private var pageSubscription: Subscription? = null private var nextPageJob: Job? = null
private val loggedServices by lazy { Injekt.get<TrackManager>().services.filter { it.isLogged } } private val loggedServices by lazy { Injekt.get<TrackManager>().services.filter { it.isLogged } }
@ -204,14 +204,14 @@ open class BrowseSourcePresenter(
fun requestNext() { fun requestNext() {
if (!hasNextPage()) return if (!hasNextPage()) return
pageSubscription?.let { remove(it) } nextPageJob?.cancel()
pageSubscription = Observable.defer { pager.requestNext() } nextPageJob = launchIO {
.subscribeFirst( try {
{ _, _ -> pager.requestNextPage()
// Nothing to do when onNext is emitted. } catch (e: Throwable) {
}, view?.onAddPageError(e)
BrowseSourceController::onAddPageError }
) }
} }
/** /**

View File

@ -21,7 +21,7 @@ abstract class Pager(var currentPage: Int = 1) {
return results.asObservable() return results.asObservable()
} }
abstract fun requestNext(): Observable<MangasPage> abstract suspend fun requestNextPage()
fun onPageReceived(mangasPage: MangasPage) { fun onPageReceived(mangasPage: MangasPage) {
val page = currentPage val page = currentPage

View File

@ -2,14 +2,11 @@ package eu.kanade.tachiyomi.ui.browse.source.browse
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.MangasPage import eu.kanade.tachiyomi.util.lang.awaitSingle
import rx.Observable
import rx.android.schedulers.AndroidSchedulers
import rx.schedulers.Schedulers
open class SourcePager(val source: CatalogueSource, val query: String, val filters: FilterList) : Pager() { open class SourcePager(val source: CatalogueSource, val query: String, val filters: FilterList) : Pager() {
override fun requestNext(): Observable<MangasPage> { override suspend fun requestNextPage() {
val page = currentPage val page = currentPage
val observable = if (query.isBlank() && filters.isEmpty()) { val observable = if (query.isBlank() && filters.isEmpty()) {
@ -18,15 +15,12 @@ open class SourcePager(val source: CatalogueSource, val query: String, val filte
source.fetchSearchManga(page, query, filters) source.fetchSearchManga(page, query, filters)
} }
return observable val mangasPage = observable.awaitSingle()
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread()) if (mangasPage.mangas.isNotEmpty()) {
.doOnNext { onPageReceived(mangasPage)
if (it.mangas.isNotEmpty()) {
onPageReceived(it)
} else { } else {
throw NoResultsException() throw NoResultsException()
} }
} }
}
} }

View File

@ -1,21 +1,13 @@
package eu.kanade.tachiyomi.ui.browse.source.latest package eu.kanade.tachiyomi.ui.browse.source.latest
import eu.kanade.tachiyomi.source.CatalogueSource import eu.kanade.tachiyomi.source.CatalogueSource
import eu.kanade.tachiyomi.source.model.MangasPage
import eu.kanade.tachiyomi.ui.browse.source.browse.Pager import eu.kanade.tachiyomi.ui.browse.source.browse.Pager
import rx.Observable import eu.kanade.tachiyomi.util.lang.awaitSingle
import rx.android.schedulers.AndroidSchedulers
import rx.schedulers.Schedulers
/**
* LatestUpdatesPager inherited from the general Pager.
*/
class LatestUpdatesPager(val source: CatalogueSource) : Pager() { class LatestUpdatesPager(val source: CatalogueSource) : Pager() {
override fun requestNext(): Observable<MangasPage> { override suspend fun requestNextPage() {
return source.fetchLatestUpdates(currentPage) val mangasPage = source.fetchLatestUpdates(currentPage).awaitSingle()
.subscribeOn(Schedulers.io()) onPageReceived(mangasPage)
.observeOn(AndroidSchedulers.mainThread())
.doOnNext { onPageReceived(it) }
} }
} }

View File

@ -4,9 +4,6 @@ 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
/**
* Presenter of [LatestUpdatesController]. Inherit BrowseCataloguePresenter.
*/
class LatestUpdatesPresenter(sourceId: Long) : BrowseSourcePresenter(sourceId) { class LatestUpdatesPresenter(sourceId: Long) : BrowseSourcePresenter(sourceId) {
override fun createPager(query: String, filters: FilterList): Pager { override fun createPager(query: String, filters: FilterList): Pager {

View File

@ -1,22 +1,14 @@
package exh.md.follows package exh.md.follows
import eu.kanade.tachiyomi.source.model.MangasPage
import eu.kanade.tachiyomi.source.online.all.MangaDex import eu.kanade.tachiyomi.source.online.all.MangaDex
import eu.kanade.tachiyomi.ui.browse.source.browse.Pager import eu.kanade.tachiyomi.ui.browse.source.browse.Pager
import eu.kanade.tachiyomi.util.lang.runAsObservable
import rx.Observable
import rx.android.schedulers.AndroidSchedulers
import rx.schedulers.Schedulers
/** /**
* LatestUpdatesPager inherited from the general Pager. * LatestUpdatesPager inherited from the general Pager.
*/ */
class MangaDexFollowsPager(val source: MangaDex) : Pager() { class MangaDexFollowsPager(val source: MangaDex) : Pager() {
override fun requestNext(): Observable<MangasPage> { override suspend fun requestNextPage() {
return runAsObservable({ source.fetchFollows(currentPage) }) onPageReceived(source.fetchFollows(currentPage))
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.doOnNext { onPageReceived(it) }
} }
} }

View File

@ -2,30 +2,22 @@ package exh.md.similar
import eu.kanade.tachiyomi.data.database.models.Manga import eu.kanade.tachiyomi.data.database.models.Manga
import eu.kanade.tachiyomi.data.database.models.toMangaInfo import eu.kanade.tachiyomi.data.database.models.toMangaInfo
import eu.kanade.tachiyomi.source.model.MangasPage
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
import eu.kanade.tachiyomi.ui.browse.source.browse.Pager import eu.kanade.tachiyomi.ui.browse.source.browse.Pager
import eu.kanade.tachiyomi.util.lang.runAsObservable
import rx.Observable
import rx.android.schedulers.AndroidSchedulers
import rx.schedulers.Schedulers
/** /**
* MangaDexSimilarPager inherited from the general Pager. * MangaDexSimilarPager inherited from the general Pager.
*/ */
class MangaDexSimilarPager(val manga: Manga, val source: MangaDex) : Pager() { class MangaDexSimilarPager(val manga: Manga, val source: MangaDex) : Pager() {
override fun requestNext(): Observable<MangasPage> { override suspend fun requestNextPage() {
return runAsObservable({ source.getMangaSimilar(manga.toMangaInfo()) }) val mangasPage = source.getMangaSimilar(manga.toMangaInfo())
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread()) if (mangasPage.mangas.isNotEmpty()) {
.doOnNext { onPageReceived(mangasPage)
if (it.mangas.isNotEmpty()) {
onPageReceived(it)
} else { } else {
throw NoResultsException() throw NoResultsException()
} }
} }
}
} }

View File

@ -5,16 +5,13 @@ import eu.kanade.tachiyomi.network.GET
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.model.MangasPage import eu.kanade.tachiyomi.source.model.MangasPage
import eu.kanade.tachiyomi.source.model.SMangaImpl import eu.kanade.tachiyomi.source.model.SManga
import eu.kanade.tachiyomi.ui.browse.source.browse.NoResultsException import eu.kanade.tachiyomi.ui.browse.source.browse.NoResultsException
import eu.kanade.tachiyomi.ui.browse.source.browse.Pager import eu.kanade.tachiyomi.ui.browse.source.browse.Pager
import eu.kanade.tachiyomi.util.lang.runAsObservable
import eu.kanade.tachiyomi.util.lang.withIOContext import eu.kanade.tachiyomi.util.lang.withIOContext
import exh.log.maybeInjectEHLogger import exh.log.maybeInjectEHLogger
import exh.util.MangaType import exh.util.MangaType
import exh.util.mangaType import exh.util.mangaType
import kotlinx.coroutines.Dispatchers
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
@ -29,9 +26,6 @@ import okhttp3.HttpUrl.Companion.toHttpUrlOrNull
import okhttp3.MediaType.Companion.toMediaTypeOrNull import okhttp3.MediaType.Companion.toMediaTypeOrNull
import okhttp3.OkHttpClient import okhttp3.OkHttpClient
import okhttp3.RequestBody.Companion.toRequestBody import okhttp3.RequestBody.Companion.toRequestBody
import rx.Observable
import rx.android.schedulers.AndroidSchedulers
import rx.schedulers.Schedulers
import timber.log.Timber import timber.log.Timber
abstract class API(_endpoint: String) { abstract class API(_endpoint: String) {
@ -40,11 +34,11 @@ abstract class API(_endpoint: String) {
.maybeInjectEHLogger() .maybeInjectEHLogger()
.build() .build()
abstract suspend fun getRecsBySearch(search: String): List<SMangaImpl> abstract suspend fun getRecsBySearch(search: String): List<SManga>
} }
class MyAnimeList : API("https://api.jikan.moe/v3/") { class MyAnimeList : API("https://api.jikan.moe/v3/") {
private suspend fun getRecsById(id: String): List<SMangaImpl> { private suspend fun getRecsById(id: String): List<SManga> {
val httpUrl = endpoint.toHttpUrlOrNull() ?: throw Exception("Could not convert endpoint url") val httpUrl = endpoint.toHttpUrlOrNull() ?: throw Exception("Could not convert endpoint url")
val apiUrl = httpUrl.newBuilder() val apiUrl = httpUrl.newBuilder()
.addPathSegment("manga") .addPathSegment("manga")
@ -59,7 +53,7 @@ class MyAnimeList : API("https://api.jikan.moe/v3/") {
val recommendations = data["recommendations"] as? JsonArray val recommendations = data["recommendations"] as? JsonArray
return recommendations?.filterIsInstance<JsonObject>()?.map { rec -> return recommendations?.filterIsInstance<JsonObject>()?.map { rec ->
Timber.tag("RECOMMENDATIONS").d("MYANIMELIST > RECOMMENDATION: %s", rec["title"]?.jsonPrimitive?.content.orEmpty()) Timber.tag("RECOMMENDATIONS").d("MYANIMELIST > RECOMMENDATION: %s", rec["title"]?.jsonPrimitive?.content.orEmpty())
SMangaImpl().apply { SManga.create().apply {
title = rec["title"]!!.jsonPrimitive.content title = rec["title"]!!.jsonPrimitive.content
thumbnail_url = rec["image_url"]!!.jsonPrimitive.content thumbnail_url = rec["image_url"]!!.jsonPrimitive.content
initialized = true initialized = true
@ -68,7 +62,7 @@ class MyAnimeList : API("https://api.jikan.moe/v3/") {
}.orEmpty() }.orEmpty()
} }
override suspend fun getRecsBySearch(search: String): List<SMangaImpl> { override suspend fun getRecsBySearch(search: String): List<SManga> {
val httpUrl = endpoint.toHttpUrlOrNull() ?: throw Exception("Could not convert endpoint url") val httpUrl = endpoint.toHttpUrlOrNull() ?: throw Exception("Could not convert endpoint url")
val url = httpUrl.newBuilder() val url = httpUrl.newBuilder()
.addPathSegment("search") .addPathSegment("search")
@ -121,7 +115,7 @@ class Anilist : API("https://graphql.anilist.co/") {
} }
} }
override suspend fun getRecsBySearch(search: String): List<SMangaImpl> { override suspend fun getRecsBySearch(search: String): List<SManga> {
val query = val query =
""" """
|query Recommendations(${'$'}search: String!) { |query Recommendations(${'$'}search: String!) {
@ -186,7 +180,7 @@ class Anilist : API("https://graphql.anilist.co/") {
val rec = it.jsonObject["node"]!!.jsonObject["mediaRecommendation"]!!.jsonObject val rec = it.jsonObject["node"]!!.jsonObject["mediaRecommendation"]!!.jsonObject
val recTitle = getTitle(rec) val recTitle = getTitle(rec)
Timber.tag("RECOMMENDATIONS").d("ANILIST > RECOMMENDATION: %s", recTitle) Timber.tag("RECOMMENDATIONS").d("ANILIST > RECOMMENDATION: %s", recTitle)
SMangaImpl().apply { SManga.create().apply {
title = recTitle title = recTitle
thumbnail_url = rec["coverImage"]!!.jsonObject["large"]!!.jsonPrimitive.content thumbnail_url = rec["coverImage"]!!.jsonObject["large"]!!.jsonPrimitive.content
initialized = true initialized = true
@ -201,39 +195,30 @@ open class RecommendsPager(
private val smart: Boolean = true, private val smart: Boolean = true,
private var preferredApi: API = API.MYANIMELIST private var preferredApi: API = API.MYANIMELIST
) : Pager() { ) : Pager() {
override fun requestNext(): Observable<MangasPage> { override suspend fun requestNextPage() {
return runAsObservable({
if (smart) preferredApi = if (manga.mangaType() != MangaType.TYPE_MANGA) API.ANILIST else preferredApi if (smart) preferredApi = if (manga.mangaType() != MangaType.TYPE_MANGA) API.ANILIST else preferredApi
val apiList = API_MAP.toList().sortedByDescending { it.first == preferredApi } val apiList = API_MAP.toList().sortedByDescending { it.first == preferredApi }
val recs = apiList val recs = apiList.firstNotNullOfOrNull { (key, api) ->
.asSequence()
.map { (key, api) ->
try { try {
val recs = runBlocking(Dispatchers.IO) { api.getRecsBySearch(manga.originalTitle) } val recs = api.getRecsBySearch(manga.originalTitle)
Timber.tag("RECOMMENDATIONS").d("%s > Results: %s", key, recs.count()) Timber.tag("RECOMMENDATIONS").d("%s > Results: %s", key, recs.count())
recs recs
} catch (e: Exception) { } catch (e: Exception) {
Timber.tag("RECOMMENDATIONS").e("%s > Error: %s", key, e.message) Timber.tag("RECOMMENDATIONS").e("%s > Error: %s", key, e.message)
listOf<SMangaImpl>() null
} }
} }.orEmpty()
.firstOrNull { it.isNotEmpty() }
.orEmpty()
MangasPage(recs, false) val mangasPage = MangasPage(recs, false)
})
.subscribeOn(Schedulers.io()) if (mangasPage.mangas.isNotEmpty()) {
.observeOn(AndroidSchedulers.mainThread()) onPageReceived(mangasPage)
.doOnNext {
if (it.mangas.isNotEmpty()) {
onPageReceived(it)
} else { } else {
throw NoResultsException() throw NoResultsException()
} }
} }
}
companion object { companion object {
val API_MAP = mapOf( val API_MAP = mapOf(