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:
parent
708c4b6905
commit
216065fb38
@ -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
|
}
|
||||||
)
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -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
|
||||||
|
@ -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()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
@ -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) }
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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 {
|
||||||
|
@ -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) }
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
@ -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(
|
||||||
|
Loading…
x
Reference in New Issue
Block a user