[Grouple] Fix pages url extraction
This commit is contained in:
@@ -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('|')
|
||||
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)
|
||||
}
|
||||
if (servers.isEmpty()) {
|
||||
if (parts.size < 2) {
|
||||
throw ParseException("No servers found for page", page.url)
|
||||
}
|
||||
val server = try {
|
||||
coroutineScope {
|
||||
servers.map { server ->
|
||||
async {
|
||||
val host = server.trim().ifEmpty { "https://$domain/" }
|
||||
if (tryHead(concatUrl(host, path))) host else null
|
||||
val path = parts.last()
|
||||
// fast path
|
||||
cachedPagesServer?.let { host ->
|
||||
val url = concatUrl("https://$host/", path)
|
||||
if (tryHead(url)) {
|
||||
return url
|
||||
} else {
|
||||
cachedPagesServer = null
|
||||
}
|
||||
}
|
||||
// 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('?')))
|
||||
}
|
||||
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> 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 }
|
||||
suspend fun <T> Iterable<Deferred<T>>.awaitFirst(): T {
|
||||
return channelFlow {
|
||||
for (deferred in this@awaitFirst) {
|
||||
launch {
|
||||
send(deferred.await())
|
||||
}
|
||||
}
|
||||
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()
|
||||
suspend fun <T> Collection<Deferred<T>>.awaitFirst(condition: (T) -> Boolean): T {
|
||||
return channelFlow {
|
||||
for (deferred in this@awaitFirst) {
|
||||
launch {
|
||||
val result = deferred.await()
|
||||
if (condition(result)) {
|
||||
send(result)
|
||||
}
|
||||
}
|
||||
}
|
||||
}.first().also { this@awaitFirst.cancelAll() }
|
||||
}
|
||||
|
||||
@@ -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}")
|
||||
|
||||
Reference in New Issue
Block a user