Add more replacement suspend functions for source APIs

These are basically 1-to-1 replacements for the existing RxJava APIs.
This will make the initial migration off of RxJava simpler. We'll
revisit the actual call flows in followup versions of the API.

(cherry picked from commit 26c5d761da4ba577481f41e63f03952b8a6c323f)

# Conflicts:
#	data/src/main/java/tachiyomi/data/source/SourcePagingSource.kt
#	source-api/src/commonMain/kotlin/eu/kanade/tachiyomi/source/online/HttpSource.kt
#	source-api/src/commonMain/kotlin/eu/kanade/tachiyomi/source/online/HttpSourceFetcher.kt
This commit is contained in:
arkon
2023-09-08 17:28:04 -04:00
committed by Jobobby04
parent e12f01ccdc
commit f4e92e4a56
26 changed files with 316 additions and 180 deletions
@@ -50,7 +50,6 @@ import nl.adaptivity.xmlutil.serialization.XML
import okhttp3.Response
import tachiyomi.core.metadata.comicinfo.COMIC_INFO_FILE
import tachiyomi.core.metadata.comicinfo.ComicInfo
import tachiyomi.core.util.lang.awaitSingle
import tachiyomi.core.util.lang.launchIO
import tachiyomi.core.util.lang.launchNow
import tachiyomi.core.util.lang.withIOContext
@@ -380,7 +379,7 @@ class Downloader(
if (page.imageUrl.isNullOrEmpty()) {
page.status = Page.State.LOAD_PAGE
try {
page.imageUrl = download.source.fetchImageUrl(page).awaitSingle()
page.imageUrl = download.source.getImageUrl(page)
} catch (e: Throwable) {
page.status = Page.State.ERROR
}
@@ -54,7 +54,7 @@ import exh.util.nullIfBlank
import exh.util.trimAll
import exh.util.trimOrNull
import exh.util.urlImportFetchSearchManga
import kotlinx.serialization.decodeFromString
import exh.util.urlImportFetchSearchMangaSuspend
import kotlinx.serialization.json.Json
import kotlinx.serialization.json.JsonObject
import kotlinx.serialization.json.add
@@ -456,27 +456,44 @@ class EHentai(
}
private fun <T : MangasPage> Observable<T>.checkValid(): Observable<MangasPage> = map {
if (exh && it.mangas.isEmpty() && preferences.igneousVal().get().equals("mystery", true)) {
throw Exception("Invalid igneous cookie, try re-logging or finding a correct one to input in the login menu")
} else {
it
}
it.checkValid()
}
private fun <T : MangasPage> T.checkValid(): MangasPage =
if (exh && mangas.isEmpty() && preferences.igneousVal().get().equals("mystery", true)) {
throw Exception("Invalid igneous cookie, try re-logging or finding a correct one to input in the login menu")
} else {
this
}
override fun fetchLatestUpdates(page: Int): Observable<MangasPage> {
return super.fetchLatestUpdates(page).checkValid()
return super<HttpSource>.fetchLatestUpdates(page).checkValid()
}
override suspend fun getLatestUpdates(page: Int): MangasPage {
return super<HttpSource>.getLatestUpdates(page).checkValid()
}
override fun fetchPopularManga(page: Int): Observable<MangasPage> {
return super.fetchPopularManga(page).checkValid()
return super<HttpSource>.fetchPopularManga(page).checkValid()
}
override suspend fun getPopularManga(page: Int): MangasPage {
return super<HttpSource>.getPopularManga(page).checkValid()
}
// Support direct URL importing
override fun fetchSearchManga(page: Int, query: String, filters: FilterList): Observable<MangasPage> =
urlImportFetchSearchManga(context, query) {
super.fetchSearchManga(page, query, filters).checkValid()
super<HttpSource>.fetchSearchManga(page, query, filters).checkValid()
}
override suspend fun getSearchManga(page: Int, query: String, filters: FilterList): MangasPage {
return urlImportFetchSearchMangaSuspend(context, query) {
super<HttpSource>.getSearchManga(page, query, filters).checkValid()
}
}
override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request {
val toplist = ToplistOption.values()[filters.firstNotNullOfOrNull { (it as? ToplistOptions)?.state } ?: 0]
if (toplist != ToplistOption.NONE) {
@@ -159,6 +159,7 @@ class MangaDex(delegate: HttpSource, val context: Context) :
return mangaHandler.getMangaFromChapterId(id)?.let { MdUtil.buildMangaUrl(it) }
}
@Deprecated("Use the non-RxJava API instead", replaceWith = ReplaceWith("getLatestUpdates"))
override fun fetchLatestUpdates(page: Int): Observable<MangasPage> {
val request = delegate.latestUpdatesRequest(page)
val url = request.url.newBuilder()
@@ -171,6 +172,16 @@ class MangaDex(delegate: HttpSource, val context: Context) :
}
}
override suspend fun getLatestUpdates(page: Int): MangasPage {
val request = delegate.latestUpdatesRequest(page)
val url = request.url.newBuilder()
.removeAllQueryParameters("includeFutureUpdates")
.build()
val response = client.newCall(request.newBuilder().url(url).build()).awaitSuccess()
return delegate.latestUpdatesParse(response)
}
@Deprecated("Use the 1.x API instead", replaceWith = ReplaceWith("getMangaDetails"))
override fun fetchMangaDetails(manga: SManga): Observable<SManga> {
return mangaHandler.fetchMangaDetailsObservable(manga, id)
@@ -198,22 +209,25 @@ class MangaDex(delegate: HttpSource, val context: Context) :
return pageHandler.fetchPageList(chapter, usePort443Only(), dataSaver(), delegate)
}
override fun fetchImage(page: Page): Observable<Response> {
val call = pageHandler.getImageCall(page)
return call?.asObservableSuccess() ?: super.fetchImage(page)
}
override suspend fun getImage(page: Page): Response {
val call = pageHandler.getImageCall(page)
return call?.awaitSuccess() ?: super.getImage(page)
}
@Deprecated("Use the non-RxJava API instead", replaceWith = ReplaceWith("getImageUrl"))
override fun fetchImageUrl(page: Page): Observable<String> {
return pageHandler.fetchImageUrl(page) {
@Suppress("DEPRECATION")
super.fetchImageUrl(it)
}
}
override suspend fun getImageUrl(page: Page): String {
return pageHandler.getImageUrl(page) {
super.getImageUrl(page)
}
}
// MetadataSource methods
override val metaClass: KClass<MangaDexSearchMetadata> = MangaDexSearchMetadata::class
@@ -61,15 +61,20 @@ class MergedSource : HttpSource() {
@Deprecated("Use the 1.x API instead", replaceWith = ReplaceWith("getChapterList"))
override fun fetchChapterList(manga: SManga) = throw UnsupportedOperationException()
override suspend fun getChapterList(manga: SManga) = throw UnsupportedOperationException()
override fun fetchImage(page: Page) = throw UnsupportedOperationException()
override suspend fun getImage(page: Page): Response = throw UnsupportedOperationException()
@Deprecated("Use the non-RxJava API instead", replaceWith = ReplaceWith("getImageUrl"))
override fun fetchImageUrl(page: Page) = throw UnsupportedOperationException()
override suspend fun getImageUrl(page: Page) = throw UnsupportedOperationException()
@Deprecated("Use the 1.x API instead", replaceWith = ReplaceWith("getPageList"))
override fun fetchPageList(chapter: SChapter) = throw UnsupportedOperationException()
override suspend fun getPageList(chapter: SChapter) = throw UnsupportedOperationException()
@Deprecated("Use the non-RxJava API instead", replaceWith = ReplaceWith("getLatestUpdates"))
override fun fetchLatestUpdates(page: Int) = throw UnsupportedOperationException()
override suspend fun getLatestUpdates(page: Int) = throw UnsupportedOperationException()
@Deprecated("Use the non-RxJava API instead", replaceWith = ReplaceWith("getPopularManga"))
override fun fetchPopularManga(page: Int) = throw UnsupportedOperationException()
override suspend fun getPopularManga(page: Int) = throw UnsupportedOperationException()
override suspend fun getMangaDetails(manga: SManga): SManga {
return withIOContext {
@@ -10,6 +10,7 @@ import eu.kanade.tachiyomi.source.PagePreviewInfo
import eu.kanade.tachiyomi.source.PagePreviewPage
import eu.kanade.tachiyomi.source.PagePreviewSource
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 eu.kanade.tachiyomi.source.online.HttpSource
@@ -22,9 +23,9 @@ import exh.metadata.metadata.base.RaisedTag
import exh.source.DelegatedHttpSource
import exh.util.trimOrNull
import exh.util.urlImportFetchSearchManga
import exh.util.urlImportFetchSearchMangaSuspend
import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable
import kotlinx.serialization.decodeFromString
import kotlinx.serialization.json.Json
import okhttp3.CacheControl
import okhttp3.Response
@@ -52,9 +53,15 @@ class NHentai(delegate: HttpSource, val context: Context) :
// Support direct URL importing
override fun fetchSearchManga(page: Int, query: String, filters: FilterList) =
urlImportFetchSearchManga(context, query) {
super.fetchSearchManga(page, query, filters)
super<DelegatedHttpSource>.fetchSearchManga(page, query, filters)
}
override suspend fun getSearchManga(page: Int, query: String, filters: FilterList): MangasPage {
return urlImportFetchSearchMangaSuspend(context, query) {
super<DelegatedHttpSource>.getSearchManga(page, query, filters,)
}
}
override suspend fun getMangaDetails(manga: SManga): SManga {
val response = client.newCall(mangaDetailsRequest(manga)).awaitSuccess()
return parseToManga(manga, response)
@@ -5,6 +5,7 @@ import android.net.Uri
import androidx.core.net.toUri
import eu.kanade.tachiyomi.network.awaitSuccess
import eu.kanade.tachiyomi.source.model.FilterList
import eu.kanade.tachiyomi.source.model.MangasPage
import eu.kanade.tachiyomi.source.model.SManga
import eu.kanade.tachiyomi.source.online.HttpSource
import eu.kanade.tachiyomi.source.online.MetadataSource
@@ -15,6 +16,7 @@ import exh.metadata.metadata.EightMusesSearchMetadata
import exh.metadata.metadata.base.RaisedTag
import exh.source.DelegatedHttpSource
import exh.util.urlImportFetchSearchManga
import exh.util.urlImportFetchSearchMangaSuspend
import org.jsoup.nodes.Document
import org.jsoup.nodes.Element
@@ -28,11 +30,18 @@ class EightMuses(delegate: HttpSource, val context: Context) :
override val lang = "en"
// Support direct URL importing
@Deprecated("Use the non-RxJava API instead", replaceWith = ReplaceWith("getSearchManga"))
override fun fetchSearchManga(page: Int, query: String, filters: FilterList) =
urlImportFetchSearchManga(context, query) {
super.fetchSearchManga(page, query, filters)
super<DelegatedHttpSource>.fetchSearchManga(page, query, filters)
}
override suspend fun getSearchManga(page: Int, query: String, filters: FilterList): MangasPage {
return urlImportFetchSearchMangaSuspend(context, query) {
super<DelegatedHttpSource>.getSearchManga(page, query, filters,)
}
}
override suspend fun getMangaDetails(manga: SManga): SManga {
val response = client.newCall(mangaDetailsRequest(manga)).awaitSuccess()
return parseToManga(manga, response.asJsoup())
@@ -4,6 +4,7 @@ import android.content.Context
import android.net.Uri
import eu.kanade.tachiyomi.network.awaitSuccess
import eu.kanade.tachiyomi.source.model.FilterList
import eu.kanade.tachiyomi.source.model.MangasPage
import eu.kanade.tachiyomi.source.model.SManga
import eu.kanade.tachiyomi.source.online.HttpSource
import eu.kanade.tachiyomi.source.online.MetadataSource
@@ -14,6 +15,7 @@ import exh.metadata.metadata.HBrowseSearchMetadata
import exh.metadata.metadata.base.RaisedTag
import exh.source.DelegatedHttpSource
import exh.util.urlImportFetchSearchManga
import exh.util.urlImportFetchSearchMangaSuspend
import org.jsoup.nodes.Document
import org.jsoup.nodes.Element
@@ -27,11 +29,18 @@ class HBrowse(delegate: HttpSource, val context: Context) :
override val lang = "en"
// Support direct URL importing
@Deprecated("Use the non-RxJava API instead", replaceWith = ReplaceWith("getSearchManga"))
override fun fetchSearchManga(page: Int, query: String, filters: FilterList) =
urlImportFetchSearchManga(context, query) {
super.fetchSearchManga(page, query, filters)
super<DelegatedHttpSource>.fetchSearchManga(page, query, filters)
}
override suspend fun getSearchManga(page: Int, query: String, filters: FilterList): MangasPage {
return urlImportFetchSearchMangaSuspend(context, query) {
super<DelegatedHttpSource>.getSearchManga(page, query, filters,)
}
}
override suspend fun getMangaDetails(manga: SManga): SManga {
val response = client.newCall(mangaDetailsRequest(manga)).awaitSuccess()
return parseToManga(manga, response.asJsoup())
@@ -19,6 +19,7 @@ import exh.source.DelegatedHttpSource
import exh.util.dropBlank
import exh.util.trimAll
import exh.util.urlImportFetchSearchManga
import exh.util.urlImportFetchSearchMangaSuspend
import org.jsoup.nodes.Document
import rx.Observable
@@ -39,6 +40,7 @@ class Pururin(delegate: HttpSource, val context: Context) :
override fun newMetaInstance() = PururinSearchMetadata()
// Support direct URL importing
@Deprecated("Use the non-RxJava API instead", replaceWith = ReplaceWith("getSearchManga"))
override fun fetchSearchManga(page: Int, query: String, filters: FilterList): Observable<MangasPage> {
val trimmedIdQuery = query.trim().removePrefix("id:")
val newQuery = if ((trimmedIdQuery.toIntOrNull() ?: -1) >= 0) {
@@ -48,7 +50,19 @@ class Pururin(delegate: HttpSource, val context: Context) :
}
return urlImportFetchSearchManga(context, newQuery) {
super.fetchSearchManga(page, query, filters)
super<DelegatedHttpSource>.fetchSearchManga(page, query, filters)
}
}
override suspend fun getSearchManga(page: Int, query: String, filters: FilterList): MangasPage {
val trimmedIdQuery = query.trim().removePrefix("id:")
val newQuery = if ((trimmedIdQuery.toIntOrNull() ?: -1) >= 0) {
"$baseUrl/gallery/$trimmedIdQuery/-"
} else {
query
}
return urlImportFetchSearchMangaSuspend(context, newQuery) {
super<DelegatedHttpSource>.getSearchManga(page, query, filters,)
}
}
@@ -19,6 +19,7 @@ import exh.source.DelegatedHttpSource
import exh.util.dropBlank
import exh.util.trimAll
import exh.util.urlImportFetchSearchManga
import exh.util.urlImportFetchSearchMangaSuspend
import org.jsoup.nodes.Document
import rx.Observable
import java.text.SimpleDateFormat
@@ -34,11 +35,18 @@ class Tsumino(delegate: HttpSource, val context: Context) :
override val lang = "en"
// Support direct URL importing
@Deprecated("Use the non-RxJava API instead", replaceWith = ReplaceWith("getSearchManga"))
override fun fetchSearchManga(page: Int, query: String, filters: FilterList): Observable<MangasPage> =
urlImportFetchSearchManga(context, query) {
super.fetchSearchManga(page, query, filters)
super<DelegatedHttpSource>.fetchSearchManga(page, query, filters)
}
override suspend fun getSearchManga(page: Int, query: String, filters: FilterList): MangasPage {
return urlImportFetchSearchMangaSuspend(context, query) {
super<DelegatedHttpSource>.getSearchManga(page, query, filters,)
}
}
override suspend fun mapUrlToMangaUrl(uri: Uri): String? {
val lcFirstPathSegment = uri.pathSegments.firstOrNull()?.lowercase(Locale.ROOT) ?: return null
if (lcFirstPathSegment != "read" && lcFirstPathSegment != "book" && lcFirstPathSegment != "entry") {
@@ -19,7 +19,6 @@ import kotlinx.coroutines.flow.update
import kotlinx.coroutines.isActive
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import tachiyomi.core.util.lang.awaitSingle
import tachiyomi.domain.manga.interactor.GetManga
import tachiyomi.domain.manga.interactor.NetworkToLocalManga
import tachiyomi.domain.manga.model.Manga
@@ -143,7 +142,7 @@ abstract class SearchScreenModel(
try {
val page = withContext(coroutineDispatcher) {
source.fetchSearchManga(1, query, source.getFilterList()).awaitSingle()
source.getSearchManga(1, query, source.getFilterList())
}
val titles = page.mangas.map {
@@ -20,7 +20,6 @@ import kotlinx.coroutines.flow.filter
import kotlinx.coroutines.flow.flow
import kotlinx.coroutines.runInterruptible
import kotlinx.coroutines.suspendCancellableCoroutine
import tachiyomi.core.util.lang.awaitSingle
import tachiyomi.core.util.lang.launchIO
import tachiyomi.core.util.lang.withIOContext
import uy.kohesive.injekt.Injekt
@@ -211,7 +210,7 @@ internal class HttpPageLoader(
try {
if (page.imageUrl.isNullOrEmpty()) {
page.status = Page.State.LOAD_PAGE
page.imageUrl = source.fetchImageUrl(page).awaitSingle()
page.imageUrl = source.getImageUrl(page)
}
val imageUrl = page.imageUrl!!
@@ -1,7 +1,6 @@
package exh.md.handlers
import eu.kanade.tachiyomi.network.POST
import eu.kanade.tachiyomi.network.asObservableSuccess
import eu.kanade.tachiyomi.network.awaitSuccess
import eu.kanade.tachiyomi.network.interceptor.rateLimit
import eu.kanade.tachiyomi.network.parseAs
@@ -22,6 +21,7 @@ import okhttp3.Request
import okhttp3.RequestBody.Companion.toRequestBody
import okhttp3.Response
import rx.Observable
import tachiyomi.core.util.lang.runAsObservable
import uy.kohesive.injekt.injectLazy
import java.util.concurrent.TimeUnit
@@ -154,12 +154,13 @@ class BilibiliHandler(currentClient: OkHttpClient) {
.mapIndexed { i, page -> Page(i, page.path, "") }
}
suspend fun getImageUrl(page: Page): String {
val response = client.newCall(imageUrlRequest(page)).awaitSuccess()
return imageUrlParse(response)
}
fun fetchImageUrl(page: Page): Observable<String> {
return client.newCall(imageUrlRequest(page))
.asObservableSuccess()
.map {
imageUrlParse(it)
}
return runAsObservable { getImageUrl(page) }
}
private fun imageUrlRequest(page: Page): Request {
@@ -134,4 +134,13 @@ class PageHandler(
else -> superMethod(page)
}
}
suspend fun getImageUrl(page: Page, superMethod: suspend (Page) -> String): String {
return when {
page.url.contains("/bfs/comic/") -> {
bilibiliHandler.getImageUrl(page)
}
else -> superMethod(page)
}
}
}
-10
View File
@@ -8,7 +8,6 @@ import eu.kanade.tachiyomi.source.Source
import eu.kanade.tachiyomi.source.model.Page
import eu.kanade.tachiyomi.source.online.HttpSource
import okhttp3.Response
import rx.Observable
import tachiyomi.core.preference.Preference
interface DataSaver {
@@ -22,15 +21,6 @@ interface DataSaver {
}
}
fun HttpSource.fetchImage(page: Page, dataSaver: DataSaver): Observable<Response> {
val imageUrl = page.imageUrl ?: return fetchImage(page)
page.imageUrl = dataSaver.compress(imageUrl)
return fetchImage(page)
.doOnNext {
page.imageUrl = imageUrl
}
}
suspend fun HttpSource.getImage(page: Page, dataSaver: DataSaver): Response {
val imageUrl = page.imageUrl ?: return getImage(page)
page.imageUrl = dataSaver.compress(imageUrl)
@@ -35,3 +35,29 @@ fun UrlImportableSource.urlImportFetchSearchManga(context: Context, query: Strin
}
else -> fail()
}
/**
* A version of fetchSearchManga that supports URL importing
*/
suspend fun UrlImportableSource.urlImportFetchSearchMangaSuspend(context: Context, query: String, fail: suspend () -> MangasPage): MangasPage =
when {
query.startsWith("http://") || query.startsWith("https://") -> {
val res = galleryAdder.addGallery(
context = context,
url = query,
fav = false,
forceSource = this
)
MangasPage(
if (res is GalleryAddEvent.Success) {
listOf(res.manga.toSManga())
} else {
emptyList()
},
false,
)
}
else -> fail()
}