[Grouple] Fix pages url extraction

source/neox^2
Koitharu 3 years ago
parent 5b94badfc2
commit 306d46ea93
No known key found for this signature in database
GPG Key ID: 8E861F8CE6E7CE27

@ -1,9 +1,11 @@
package org.koitharu.kotatsu.parsers.site.grouple
import kotlinx.coroutines.async
import kotlinx.coroutines.coroutineScope
import kotlinx.coroutines.flow.channelFlow
import kotlinx.coroutines.flow.first
import kotlinx.coroutines.launch
import okhttp3.Headers
import okhttp3.HttpUrl.Companion.toHttpUrl
import okhttp3.HttpUrl.Companion.toHttpUrlOrNull
import okhttp3.Interceptor
import okhttp3.Response
import okhttp3.internal.headersContentLength
@ -236,30 +238,42 @@ internal abstract class GroupleParser(
override suspend fun getPageUrl(page: MangaPage): String {
val parts = page.url.split('|')
if (parts.size < 2) {
throw ParseException("No servers found for page", page.url)
}
val path = parts.last()
val servers = parts.dropLast(1).toSet()
val cachedServer = cachedPagesServer
if (!cachedServer.isNullOrEmpty() && cachedServer in servers && tryHead(concatUrl(cachedServer, path))) {
return concatUrl(cachedServer, path)
// fast path
cachedPagesServer?.let { host ->
val url = concatUrl("https://$host/", path)
if (tryHead(url)) {
return url
} else {
cachedPagesServer = null
}
}
if (servers.isEmpty()) {
throw ParseException("No servers found for page", page.url)
// slow path
val candidates = HashSet<String>((parts.size - 1) * 2)
for (i in 0 until parts.size - 1) {
val server = parts[i].trim().ifEmpty { "https://$domain/" }
candidates.add(concatUrl(server, path))
candidates.add(concatUrl(server, path.substringBeforeLast('?')))
}
val server = try {
coroutineScope {
servers.map { server ->
async {
val host = server.trim().ifEmpty { "https://$domain/" }
if (tryHead(concatUrl(host, path))) host else null
return try {
channelFlow {
for (url in candidates) {
launch {
if (tryHead(url)) {
send(url)
}
}
}.awaitFirst { it != null }
}.also {
cachedPagesServer = it
}
}.first().also {
cachedPagesServer = it.toHttpUrlOrNull()?.host
}
} catch (e: NoSuchElementException) {
servers.random()
assert(false) { e.toString() }
candidates.random()
}
return concatUrl(checkNotNull(server).ifEmpty { "https://$domain/" }, path)
}
override suspend fun getTags(): Set<MangaTag> {

@ -2,39 +2,34 @@ package org.koitharu.kotatsu.parsers.util
import kotlinx.coroutines.Deferred
import kotlinx.coroutines.Job
import kotlinx.coroutines.selects.select
import kotlinx.coroutines.flow.channelFlow
import kotlinx.coroutines.flow.first
import kotlinx.coroutines.launch
import kotlin.coroutines.cancellation.CancellationException
fun Iterable<Job>.cancelAll(cause: CancellationException? = null) {
forEach { it.cancel(cause) }
}
suspend fun <T> Iterable<Deferred<T>>.awaitFirst(): T = select<T> {
for (async in this@awaitFirst) {
async.onAwait { it }
}
}.also { this@awaitFirst.cancelAll() }
suspend fun <T> Iterable<Deferred<T>>.awaitFirst(): T {
return channelFlow {
for (deferred in this@awaitFirst) {
launch {
send(deferred.await())
}
}
}.first().also { this@awaitFirst.cancelAll() }
}
suspend fun <T> Collection<Deferred<T>>.awaitFirst(condition: (T) -> Boolean): T {
var result: Any? = NULL
var counter = size
while (result === NULL && counter > 0) {
val candidate = select<T> {
for (async in this@awaitFirst) {
async.onAwait { it }
return channelFlow {
for (deferred in this@awaitFirst) {
launch {
val result = deferred.await()
if (condition(result)) {
send(result)
}
}
}
if (condition(candidate)) {
result = candidate
}
counter--
}
cancelAll()
if (result === NULL) {
throw NoSuchElementException()
}
@Suppress("UNCHECKED_CAST")
return result as T
}.first().also { this@awaitFirst.cancelAll() }
}
private val NULL = Any()

@ -114,20 +114,24 @@ internal class MangaParserTest {
@MangaSources
fun pages(source: MangaSource) = runTest {
val parser = source.newParser(context)
val list = parser.getList(0, sortOrder = SortOrder.POPULARITY, tags = null)
val list = parser.getList(0, sortOrder = SortOrder.UPDATED, tags = null)
val manga = list.first()
val chapter = parser.getDetails(manga).chapters?.firstOrNull() ?: error("Chapter is null")
val chapter = parser.getDetails(manga).chapters?.firstOrNull() ?: error("Chapter is null at ${manga.publicUrl}")
val pages = parser.getPages(chapter)
assert(pages.isNotEmpty())
assert(pages.isDistinctBy { it.id })
assert(pages.all { it.source == source })
val page = pages.medianOrNull() ?: error("No page")
val pageUrl = parser.getPageUrl(page)
assert(pageUrl.isNotEmpty())
assert(pageUrl.isUrlAbsolute())
checkImageRequest(pageUrl, page.source)
arrayOf(
pages.first(),
pages.medianOrNull() ?: error("No page"),
).forEach { page ->
val pageUrl = parser.getPageUrl(page)
assert(pageUrl.isNotEmpty())
assert(pageUrl.isUrlAbsolute())
checkImageRequest(pageUrl, page.source)
}
}
@ParameterizedTest(name = "{index}|favicon|{0}")

Loading…
Cancel
Save