comick: improve performance (#16947)
* comick: improve performance for real this time * simplify logic Co-authored-by: stevenyomi <95685115+stevenyomi@users.noreply.github.com> * remove redundancy --------- Co-authored-by: stevenyomi <95685115+stevenyomi@users.noreply.github.com>
This commit is contained in:
parent
34483c9afe
commit
fcba0e4efe
|
@ -6,7 +6,7 @@ ext {
|
|||
extName = 'Comick'
|
||||
pkgNameSuffix = 'all.comickfun'
|
||||
extClass = '.ComickFunFactory'
|
||||
extVersionCode = 28
|
||||
extVersionCode = 29
|
||||
isNsfw = true
|
||||
}
|
||||
|
||||
|
|
|
@ -1,12 +1,8 @@
|
|||
package eu.kanade.tachiyomi.extension.all.comickfun
|
||||
|
||||
import android.app.Application
|
||||
import android.content.SharedPreferences
|
||||
import androidx.preference.ListPreference
|
||||
import androidx.preference.PreferenceScreen
|
||||
import eu.kanade.tachiyomi.network.GET
|
||||
import eu.kanade.tachiyomi.network.asObservableSuccess
|
||||
import eu.kanade.tachiyomi.network.interceptor.rateLimit
|
||||
import eu.kanade.tachiyomi.source.ConfigurableSource
|
||||
import eu.kanade.tachiyomi.source.model.FilterList
|
||||
import eu.kanade.tachiyomi.source.model.MangasPage
|
||||
import eu.kanade.tachiyomi.source.model.Page
|
||||
|
@ -17,20 +13,18 @@ import kotlinx.serialization.decodeFromString
|
|||
import kotlinx.serialization.json.Json
|
||||
import okhttp3.Headers
|
||||
import okhttp3.HttpUrl.Companion.toHttpUrl
|
||||
import okhttp3.OkHttpClient
|
||||
import okhttp3.Request
|
||||
import okhttp3.Response
|
||||
import rx.Observable
|
||||
import uy.kohesive.injekt.Injekt
|
||||
import uy.kohesive.injekt.api.get
|
||||
import java.text.SimpleDateFormat
|
||||
import java.util.Locale
|
||||
import java.util.TimeZone
|
||||
import kotlin.math.min
|
||||
|
||||
abstract class ComickFun(
|
||||
override val lang: String,
|
||||
private val comickFunLang: String,
|
||||
) : HttpSource(), ConfigurableSource {
|
||||
) : HttpSource() {
|
||||
|
||||
override val name = "Comick"
|
||||
|
||||
|
@ -47,62 +41,90 @@ abstract class ComickFun(
|
|||
explicitNulls = true
|
||||
}
|
||||
|
||||
private lateinit var searchResponse: List<SearchManga>
|
||||
|
||||
override fun headersBuilder() = Headers.Builder().apply {
|
||||
add("Referer", "$baseUrl/")
|
||||
add("User-Agent", "Tachiyomi ${System.getProperty("http.agent")}")
|
||||
}
|
||||
|
||||
override val client: OkHttpClient = network.client.newBuilder()
|
||||
.addNetworkInterceptor(::thumbnailIntercept)
|
||||
.rateLimit(4, 1)
|
||||
override val client = network.client.newBuilder()
|
||||
.addInterceptor(::thumbnailIntercept)
|
||||
.rateLimit(3, 1)
|
||||
.build()
|
||||
|
||||
private val preferences: SharedPreferences by lazy {
|
||||
Injekt.get<Application>().getSharedPreferences("source_$id", 0x0000)
|
||||
}
|
||||
|
||||
/** Popular Manga **/
|
||||
override fun popularMangaRequest(page: Int): Request {
|
||||
return searchMangaRequest(
|
||||
page = page,
|
||||
query = "",
|
||||
filters = FilterList(
|
||||
SortFilter("", getSortsList, defaultPopularSort),
|
||||
),
|
||||
)
|
||||
val url = "$apiUrl/v1.0/search?sort=follow&limit=$limit&page=$page&tachiyomi=true"
|
||||
return GET(url, headers)
|
||||
}
|
||||
|
||||
override fun popularMangaParse(response: Response) = searchMangaParse(response)
|
||||
override fun popularMangaParse(response: Response): MangasPage {
|
||||
val result = response.parseAs<List<SearchManga>>()
|
||||
return MangasPage(
|
||||
result.map(SearchManga::toSManga),
|
||||
hasNextPage = result.size >= limit,
|
||||
)
|
||||
}
|
||||
|
||||
/** Latest Manga **/
|
||||
override fun latestUpdatesRequest(page: Int): Request {
|
||||
return searchMangaRequest(
|
||||
page = page,
|
||||
query = "",
|
||||
filters = FilterList(
|
||||
SortFilter("", getSortsList, defaultLatestSort),
|
||||
),
|
||||
)
|
||||
val url = "$apiUrl/v1.0/search?sort=uploaded&limit=$limit&page=$page&tachiyomi=true"
|
||||
return GET(url, headers)
|
||||
}
|
||||
|
||||
override fun latestUpdatesParse(response: Response) = searchMangaParse(response)
|
||||
override fun latestUpdatesParse(response: Response) = popularMangaParse(response)
|
||||
|
||||
/** Manga Search **/
|
||||
override fun fetchSearchManga(page: Int, query: String, filters: FilterList): Observable<MangasPage> {
|
||||
if (!query.startsWith(SLUG_SEARCH_PREFIX)) {
|
||||
return super.fetchSearchManga(page, query, filters)
|
||||
}
|
||||
|
||||
return if (query.startsWith(SLUG_SEARCH_PREFIX)) {
|
||||
// url deep link
|
||||
val slugOrHid = query.substringAfter(SLUG_SEARCH_PREFIX)
|
||||
val manga = SManga.create().apply { this.url = "/comic/$slugOrHid#" }
|
||||
return fetchMangaDetails(manga).map {
|
||||
fetchMangaDetails(manga).map {
|
||||
MangasPage(listOf(it), false)
|
||||
}
|
||||
} else if (query.isEmpty()) {
|
||||
// regular filtering without text search
|
||||
client.newCall(searchMangaRequest(page, query, filters))
|
||||
.asObservableSuccess()
|
||||
.map(::searchMangaParse)
|
||||
} else {
|
||||
// text search, no pagination in api
|
||||
if (page == 1) {
|
||||
client.newCall(querySearchRequest(query))
|
||||
.asObservableSuccess()
|
||||
.map(::querySearchParse)
|
||||
} else {
|
||||
Observable.just(paginatedSearchPage(page))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun querySearchRequest(query: String): Request {
|
||||
val url = "$apiUrl/v1.0/search?limit=300&page=1&tachiyomi=true"
|
||||
.toHttpUrl().newBuilder()
|
||||
.addQueryParameter("q", query.trim())
|
||||
.build()
|
||||
|
||||
return GET(url, headers)
|
||||
}
|
||||
|
||||
private fun querySearchParse(response: Response): MangasPage {
|
||||
searchResponse = response.parseAs()
|
||||
|
||||
return paginatedSearchPage(1)
|
||||
}
|
||||
|
||||
private fun paginatedSearchPage(page: Int): MangasPage {
|
||||
val end = min(page * limit, searchResponse.size)
|
||||
val entries = searchResponse.subList((page - 1) * limit, end)
|
||||
.map(SearchManga::toSManga)
|
||||
return MangasPage(entries, end < searchResponse.size)
|
||||
}
|
||||
|
||||
override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request {
|
||||
val url = "$apiUrl/v1.0/search".toHttpUrl().newBuilder().apply {
|
||||
if (query.isEmpty()) {
|
||||
filters.forEach { it ->
|
||||
when (it) {
|
||||
is CompletedFilter -> {
|
||||
|
@ -174,30 +196,15 @@ abstract class ComickFun(
|
|||
else -> {}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
addQueryParameter("q", query)
|
||||
}
|
||||
addQueryParameter("tachiyomi", "true")
|
||||
addQueryParameter("limit", "50")
|
||||
addQueryParameter("limit", "$limit")
|
||||
addQueryParameter("page", "$page")
|
||||
}.build()
|
||||
|
||||
return GET(url, headers)
|
||||
}
|
||||
|
||||
override fun searchMangaParse(response: Response): MangasPage {
|
||||
val isQueryPresent = response.request.url.queryParameterNames.contains("q")
|
||||
val result = response.parseAs<List<SearchManga>>()
|
||||
return MangasPage(
|
||||
result.map { it.toSManga(useScaledCover) },
|
||||
/*
|
||||
api always returns `limit` amount of results
|
||||
for text search and page>=2 is always empty
|
||||
so here we are checking if url has the text query parameter
|
||||
to avoid false 'No result found' toasts.
|
||||
*/
|
||||
hasNextPage = !isQueryPresent && result.size >= 50,
|
||||
)
|
||||
}
|
||||
override fun searchMangaParse(response: Response) = popularMangaParse(response)
|
||||
|
||||
/** Manga Details **/
|
||||
override fun mangaDetailsRequest(manga: SManga): Request {
|
||||
|
@ -212,7 +219,7 @@ abstract class ComickFun(
|
|||
|
||||
override fun mangaDetailsParse(response: Response): SManga {
|
||||
val mangaData = response.parseAs<Manga>()
|
||||
return mangaData.toSManga(useScaledCover)
|
||||
return mangaData.toSManga()
|
||||
}
|
||||
|
||||
override fun getMangaUrl(manga: SManga): String {
|
||||
|
@ -290,28 +297,11 @@ abstract class ComickFun(
|
|||
throw UnsupportedOperationException("Not used")
|
||||
}
|
||||
|
||||
protected open val defaultPopularSort: Int = 0
|
||||
protected open val defaultLatestSort: Int = 4
|
||||
|
||||
override fun getFilterList() = getFilters()
|
||||
|
||||
override fun setupPreferenceScreen(screen: PreferenceScreen) {
|
||||
ListPreference(screen.context).apply {
|
||||
key = coverQualityPref
|
||||
title = "Cover Quality"
|
||||
entries = arrayOf("Original", "Scaled")
|
||||
entryValues = arrayOf("orig", "scaled")
|
||||
setDefaultValue("orig")
|
||||
summary = "%s"
|
||||
}.let { screen.addPreference(it) }
|
||||
}
|
||||
|
||||
private val useScaledCover: Boolean by lazy {
|
||||
preferences.getString(coverQualityPref, "orig") != "orig"
|
||||
}
|
||||
|
||||
companion object {
|
||||
const val SLUG_SEARCH_PREFIX = "id:"
|
||||
private const val limit = 20
|
||||
val dateFormat by lazy {
|
||||
SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss'Z'", Locale.ENGLISH).apply {
|
||||
timeZone = TimeZone.getTimeZone("UTC")
|
||||
|
@ -320,6 +310,5 @@ abstract class ComickFun(
|
|||
val markdownLinksRegex = "\\[([^]]+)\\]\\(([^)]+)\\)".toRegex()
|
||||
val markdownItalicBoldRegex = "\\*+\\s*([^\\*]*)\\s*\\*+".toRegex()
|
||||
val markdownItalicRegex = "_+\\s*([^_]*)\\s*_+".toRegex()
|
||||
private const val coverQualityPref = "pref_cover_quality"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -13,11 +13,11 @@ data class SearchManga(
|
|||
val cover_url: String? = null,
|
||||
|
||||
) {
|
||||
fun toSManga(useScaledCover: Boolean) = SManga.create().apply {
|
||||
fun toSManga() = SManga.create().apply {
|
||||
// appennding # at end as part of migration from slug to hid
|
||||
url = "/comic/$hid#"
|
||||
title = this@SearchManga.title
|
||||
thumbnail_url = parseCover(cover_url, md_covers, useScaledCover)
|
||||
thumbnail_url = parseCover(cover_url, md_covers)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -28,21 +28,24 @@ data class Manga(
|
|||
val authors: List<Author> = emptyList(),
|
||||
val genres: List<Genre> = emptyList(),
|
||||
) {
|
||||
fun toSManga(useScaledCover: Boolean) = SManga.create().apply {
|
||||
fun toSManga() = SManga.create().apply {
|
||||
// appennding # at end as part of migration from slug to hid
|
||||
url = "/comic/${comic.hid}#"
|
||||
title = comic.title
|
||||
description = comic.desc.beautifyDescription()
|
||||
if (comic.altTitles.isNotEmpty()) {
|
||||
description += comic.altTitles.joinToString(
|
||||
separator = "\n",
|
||||
prefix = "\n\nAlternative Titles:\n",
|
||||
) {
|
||||
it.title.toString()
|
||||
if (description.isNullOrEmpty()) {
|
||||
description = "Alternative Titles:\n"
|
||||
} else {
|
||||
description += "\n\nAlternative Titles:\n"
|
||||
}
|
||||
|
||||
description += comic.altTitles.mapNotNull { title ->
|
||||
title.title?.let { "• $it" }
|
||||
}.joinToString("\n")
|
||||
}
|
||||
status = comic.status.parseStatus(comic.translation_completed)
|
||||
thumbnail_url = parseCover(comic.cover_url, comic.md_covers, useScaledCover)
|
||||
thumbnail_url = parseCover(comic.cover_url, comic.md_covers)
|
||||
artist = artists.joinToString { it.name.trim() }
|
||||
author = authors.joinToString { it.name.trim() }
|
||||
genre = genres.joinToString { it.name.trim() }
|
||||
|
|
|
@ -171,7 +171,7 @@ private val getCreatedAtList: Array<Pair<String, String>> = arrayOf(
|
|||
Pair("1 year", "365"),
|
||||
)
|
||||
|
||||
internal val getSortsList: Array<Pair<String, String>> = arrayOf(
|
||||
private val getSortsList: Array<Pair<String, String>> = arrayOf(
|
||||
Pair("Most popular", "follow"),
|
||||
Pair("Most follows", "user_follow_count"),
|
||||
Pair("Most views", "view"),
|
||||
|
|
|
@ -11,6 +11,7 @@ import org.jsoup.parser.Parser
|
|||
|
||||
internal fun String.beautifyDescription(): String {
|
||||
return Parser.unescapeEntities(this, false)
|
||||
.substringBefore("---")
|
||||
.replace(markdownLinksRegex, "")
|
||||
.replace(markdownItalicBoldRegex, "")
|
||||
.replace(markdownItalicRegex, "")
|
||||
|
@ -33,15 +34,11 @@ internal fun Int.parseStatus(translationComplete: Boolean): Int {
|
|||
}
|
||||
}
|
||||
|
||||
internal fun parseCover(thumbnailUrl: String?, mdCovers: List<MDcovers>, useScaled: Boolean): String? {
|
||||
internal fun parseCover(thumbnailUrl: String?, mdCovers: List<MDcovers>): String {
|
||||
val b2key = runCatching { mdCovers.first().b2key }
|
||||
.getOrNull() ?: ""
|
||||
|
||||
return if (useScaled) {
|
||||
"$thumbnailUrl#$b2key"
|
||||
} else {
|
||||
thumbnailUrl?.replaceAfterLast("/", b2key)
|
||||
}
|
||||
return "$thumbnailUrl#$b2key"
|
||||
}
|
||||
|
||||
internal fun thumbnailIntercept(chain: Interceptor.Chain): Response {
|
||||
|
|
Loading…
Reference in New Issue