Merge branch 'KotatsuApp:master' into sources/domain
This commit is contained in:
@@ -6,6 +6,8 @@ import androidx.collection.ArrayMap
|
||||
import androidx.collection.ArraySet
|
||||
import java.util.*
|
||||
|
||||
public fun Collection<*>?.sizeOrZero(): Int = this?.size ?: 0
|
||||
|
||||
public fun <T> MutableCollection<T>.replaceWith(subject: Iterable<T>) {
|
||||
clear()
|
||||
addAll(subject)
|
||||
@@ -66,3 +68,22 @@ public fun <K> MutableMap<K, Int>.incrementAndGet(key: K): Int {
|
||||
put(key, value)
|
||||
return value
|
||||
}
|
||||
|
||||
public inline fun <T> MutableSet(size: Int, init: (index: Int) -> T): MutableSet<T> {
|
||||
val set = ArraySet<T>(size)
|
||||
repeat(size) { index -> set.add(init(index)) }
|
||||
return set
|
||||
}
|
||||
|
||||
public inline fun <T> Set(size: Int, init: (index: Int) -> T): Set<T> = when (size) {
|
||||
0 -> emptySet()
|
||||
1 -> Collections.singleton(init(0))
|
||||
else -> MutableSet(size, init)
|
||||
}
|
||||
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
public inline fun <T, reified R> Collection<T>.mapToArray(transform: (T) -> R): Array<R> {
|
||||
val result = arrayOfNulls<R>(size)
|
||||
forEachIndexed { index, t -> result[index] = transform(t) }
|
||||
return result as Array<R>
|
||||
}
|
||||
|
||||
@@ -6,8 +6,6 @@ import okhttp3.Cookie
|
||||
import okhttp3.CookieJar
|
||||
import okhttp3.HttpUrl
|
||||
|
||||
private const val SCHEME_HTTPS = "https"
|
||||
|
||||
public fun CookieJar.insertCookies(domain: String, vararg cookies: String) {
|
||||
val url = safeUrlOf(domain) ?: return
|
||||
saveFromResponse(
|
||||
|
||||
@@ -18,7 +18,7 @@ import org.koitharu.kotatsu.parsers.model.*
|
||||
*/
|
||||
@InternalParsersApi
|
||||
public fun MangaParser.generateUid(url: String): Long {
|
||||
var h = 1125899906842597L
|
||||
var h = LONG_HASH_SEED
|
||||
source.name.forEach { c ->
|
||||
h = 31 * h + c.code
|
||||
}
|
||||
@@ -37,7 +37,7 @@ public fun MangaParser.generateUid(url: String): Long {
|
||||
*/
|
||||
@InternalParsersApi
|
||||
public fun MangaParser.generateUid(id: Long): Long {
|
||||
var h = 1125899906842597L
|
||||
var h = LONG_HASH_SEED
|
||||
source.name.forEach { c ->
|
||||
h = 31 * h + c.code
|
||||
}
|
||||
@@ -82,9 +82,7 @@ private fun <T> Set<T>?.oneOrThrowIfMany(msg: String): T? = when {
|
||||
}
|
||||
|
||||
public val MangaParser.domain: String
|
||||
get() {
|
||||
return config[configKeyDomain]
|
||||
}
|
||||
get() = config[configKeyDomain]
|
||||
|
||||
@InternalParsersApi
|
||||
public fun MangaParser.getDomain(subdomain: String): String {
|
||||
@@ -95,6 +93,6 @@ public fun MangaParser.getDomain(subdomain: String): String {
|
||||
@InternalParsersApi
|
||||
public fun MangaParser.urlBuilder(subdomain: String? = null): HttpUrl.Builder {
|
||||
return HttpUrl.Builder()
|
||||
.scheme("https")
|
||||
.scheme(SCHEME_HTTPS)
|
||||
.host(if (subdomain == null) domain else "$subdomain.$domain")
|
||||
}
|
||||
|
||||
@@ -58,12 +58,23 @@ public fun Number.formatSimple(): String {
|
||||
}
|
||||
}
|
||||
|
||||
public inline fun Int.ifZero(defaultVale: () -> Int): Int {
|
||||
public inline fun Int.ifZero(defaultValue: () -> Int): Int {
|
||||
contract {
|
||||
callsInPlace(defaultVale, InvocationKind.AT_MOST_ONCE)
|
||||
callsInPlace(defaultValue, InvocationKind.AT_MOST_ONCE)
|
||||
}
|
||||
return if (this == 0) {
|
||||
defaultVale()
|
||||
defaultValue()
|
||||
} else {
|
||||
this
|
||||
}
|
||||
}
|
||||
|
||||
public inline fun Long.ifZero(defaultValue: () -> Long): Long {
|
||||
contract {
|
||||
callsInPlace(defaultValue, InvocationKind.AT_MOST_ONCE)
|
||||
}
|
||||
return if (this == 0L) {
|
||||
defaultValue()
|
||||
} else {
|
||||
this
|
||||
}
|
||||
|
||||
@@ -3,10 +3,7 @@
|
||||
package org.koitharu.kotatsu.parsers.util
|
||||
|
||||
import kotlinx.coroutines.suspendCancellableCoroutine
|
||||
import okhttp3.Call
|
||||
import okhttp3.Headers
|
||||
import okhttp3.Response
|
||||
import okhttp3.ResponseBody
|
||||
import okhttp3.*
|
||||
import kotlin.contracts.InvocationKind
|
||||
import kotlin.contracts.contract
|
||||
|
||||
@@ -19,9 +16,8 @@ public suspend fun Call.await(): Response = suspendCancellableCoroutine { contin
|
||||
public val Response.mimeType: String?
|
||||
get() = header("content-type")?.substringBefore(';')?.trim()?.nullIfEmpty()?.lowercase()
|
||||
|
||||
@Deprecated("")
|
||||
public val Response.contentDisposition: String?
|
||||
get() = header("Content-Disposition")
|
||||
public val HttpUrl.isHttpOrHttps: Boolean
|
||||
get() = scheme.equals("https", ignoreCase = true) || scheme.equals("http", ignoreCase = true)
|
||||
|
||||
public fun Headers.Builder.mergeWith(other: Headers, replaceExisting: Boolean): Headers.Builder {
|
||||
for ((name, value) in other) {
|
||||
@@ -52,3 +48,23 @@ public inline fun Response.map(mapper: (ResponseBody) -> ResponseBody): Response
|
||||
.build()
|
||||
} ?: this
|
||||
}
|
||||
|
||||
public fun Cookie.newBuilder(): Cookie.Builder = Cookie.Builder().also { c ->
|
||||
c.name(name)
|
||||
c.value(value)
|
||||
if (persistent) {
|
||||
c.expiresAt(expiresAt)
|
||||
}
|
||||
if (hostOnly) {
|
||||
c.hostOnlyDomain(domain)
|
||||
} else {
|
||||
c.domain(domain)
|
||||
}
|
||||
c.path(path)
|
||||
if (secure) {
|
||||
c.secure()
|
||||
}
|
||||
if (httpOnly) {
|
||||
c.httpOnly()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -13,7 +13,7 @@ import org.koitharu.kotatsu.parsers.InternalParsersApi
|
||||
import java.text.DateFormat
|
||||
|
||||
private val REGEX_SCHEME_PREFIX = Regex("^\\w{2,6}://", RegexOption.IGNORE_CASE)
|
||||
private const val SCHEME_HTTPS = "https"
|
||||
internal const val SCHEME_HTTPS = "https"
|
||||
|
||||
/**
|
||||
* Parse [Response] body as html document using Jsoup
|
||||
|
||||
@@ -13,8 +13,6 @@ public class RelatedMangaFinder(
|
||||
private val parsers: Collection<MangaParser>,
|
||||
) {
|
||||
|
||||
private val regexWhitespace = Regex("\\s+")
|
||||
|
||||
public suspend operator fun invoke(seed: Manga): List<Manga> = coroutineScope {
|
||||
parsers.singleOrNull()?.let { parser ->
|
||||
findRelatedImpl(this, parser, seed)
|
||||
@@ -27,9 +25,9 @@ public class RelatedMangaFinder(
|
||||
|
||||
private suspend fun findRelatedImpl(scope: CoroutineScope, parser: MangaParser, seed: Manga): List<Manga> {
|
||||
val words = HashSet<String>()
|
||||
words += seed.title.split(regexWhitespace)
|
||||
words += seed.title.splitByWhitespace()
|
||||
seed.altTitle?.let {
|
||||
words += it.split(regexWhitespace)
|
||||
words += it.splitByWhitespace()
|
||||
}
|
||||
if (words.isEmpty()) {
|
||||
return emptyList()
|
||||
|
||||
@@ -3,15 +3,17 @@
|
||||
package org.koitharu.kotatsu.parsers.util
|
||||
|
||||
import androidx.collection.MutableIntList
|
||||
import androidx.collection.arraySetOf
|
||||
import java.math.BigInteger
|
||||
import java.net.URLDecoder
|
||||
import java.net.URLEncoder
|
||||
import java.security.MessageDigest
|
||||
import java.util.*
|
||||
import kotlin.contracts.InvocationKind
|
||||
import kotlin.contracts.contract
|
||||
import kotlin.math.min
|
||||
|
||||
private val REGEX_WHITESPACE = Regex("\\s+")
|
||||
internal const val LONG_HASH_SEED = 1125899906842597L
|
||||
|
||||
public fun String.removeSurrounding(vararg chars: Char): String {
|
||||
if (isEmpty()) {
|
||||
@@ -25,6 +27,22 @@ public fun String.removeSurrounding(vararg chars: Char): String {
|
||||
return this
|
||||
}
|
||||
|
||||
public inline fun <C : R, R : CharSequence?> C?.ifNullOrEmpty(defaultValue: () -> R): R {
|
||||
contract {
|
||||
callsInPlace(defaultValue, InvocationKind.AT_MOST_ONCE)
|
||||
}
|
||||
return if (this.isNullOrEmpty()) defaultValue() else this
|
||||
}
|
||||
|
||||
public fun String.longHashCode(): Long {
|
||||
var h = LONG_HASH_SEED
|
||||
val len: Int = this.length
|
||||
for (i in 0 until len) {
|
||||
h = 31 * h + this[i].code
|
||||
}
|
||||
return h
|
||||
}
|
||||
|
||||
public fun String.toCamelCase(): String {
|
||||
if (isEmpty()) {
|
||||
return this
|
||||
@@ -44,6 +62,8 @@ public fun String.toCamelCase(): String {
|
||||
return result.toString()
|
||||
}
|
||||
|
||||
public fun String.digits(): String = filter { it.isDigit() }
|
||||
|
||||
public fun String.toTitleCase(): String {
|
||||
return replaceFirstChar { x -> x.uppercase() }
|
||||
}
|
||||
@@ -52,35 +72,6 @@ public fun String.toTitleCase(locale: Locale): String {
|
||||
return replaceFirstChar { x -> x.uppercase(locale) }
|
||||
}
|
||||
|
||||
public fun String.transliterate(skipMissing: Boolean): String {
|
||||
val cyr = charArrayOf(
|
||||
'а', 'б', 'в', 'г', 'д', 'е', 'ж', 'з', 'и', 'й', 'к', 'л', 'м', 'н', 'о', 'п',
|
||||
'р', 'с', 'т', 'у', 'ф', 'х', 'ц', 'ч', 'ш', 'щ', 'ъ', 'ы', 'ь', 'э', 'ю', 'я', 'ё', 'ў',
|
||||
)
|
||||
val lat = arrayOf(
|
||||
"a", "b", "v", "g", "d", "e", "zh", "z", "i", "y", "k", "l", "m", "n", "o", "p",
|
||||
"r", "s", "t", "u", "f", "h", "ts", "ch", "sh", "sch", "", "i", "", "e", "ju", "ja", "jo", "w",
|
||||
)
|
||||
return buildString(length + 5) {
|
||||
for (c in this@transliterate) {
|
||||
val p = cyr.binarySearch(c.lowercaseChar())
|
||||
if (p in lat.indices) {
|
||||
if (c.isUpperCase()) {
|
||||
append(lat[p].uppercase())
|
||||
} else {
|
||||
append(lat[p])
|
||||
}
|
||||
} else if (!skipMissing) {
|
||||
append(c)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public fun String.toFileNameSafe(): String = this.transliterate(false)
|
||||
.replace(Regex("[^a-z0-9_\\-]", arraySetOf(RegexOption.IGNORE_CASE)), " ")
|
||||
.replace(REGEX_WHITESPACE, "_")
|
||||
|
||||
public fun String.ellipsize(maxLength: Int): String = if (this.length > maxLength) {
|
||||
this.take(maxLength - 1) + Typography.ellipsis
|
||||
} else this
|
||||
|
||||
@@ -15,7 +15,13 @@ public interface SuspendLazy<T> {
|
||||
public fun peek(): T?
|
||||
}
|
||||
|
||||
public suspend fun <T> SuspendLazy<T>.getOrNull(): T? = runCatchingCancellable { get() }.getOrNull()
|
||||
public suspend fun <T> SuspendLazy<T>.getOrNull(): T? = runCatchingCancellable {
|
||||
get()
|
||||
}.getOrNull()
|
||||
|
||||
public suspend fun <R, T : R> SuspendLazy<T>.getOrDefault(defaultValue: R): R = runCatchingCancellable {
|
||||
get()
|
||||
}.getOrDefault(defaultValue)
|
||||
|
||||
public fun <T> suspendLazy(
|
||||
context: CoroutineContext = EmptyCoroutineContext,
|
||||
|
||||
@@ -5,6 +5,7 @@ import okhttp3.HttpUrl.Companion.toHttpUrlOrNull
|
||||
import org.koitharu.kotatsu.parsers.model.Manga
|
||||
import org.koitharu.kotatsu.parsers.model.MangaParserSource
|
||||
import org.koitharu.kotatsu.parsers.model.RATING_UNKNOWN
|
||||
import org.koitharu.kotatsu.parsers.util.LONG_HASH_SEED
|
||||
import org.koitharu.kotatsu.parsers.util.toRelativeUrl
|
||||
|
||||
private val PATTERN_URL_ABSOLUTE = Regex("^https?://[\\s\\S]+", setOf(RegexOption.IGNORE_CASE))
|
||||
@@ -55,7 +56,7 @@ inline operator fun <T> List<T>.component7(): T = get(6)
|
||||
|
||||
fun mangaOf(source: MangaParserSource, url: String): Manga {
|
||||
val httpUrl = url.toHttpUrlOrNull()
|
||||
var id = 1125899906842597L
|
||||
var id = LONG_HASH_SEED
|
||||
source.name.forEach { c -> id = 31 * id + c.code }
|
||||
url.forEach { c -> id = 31 * id + c.code }
|
||||
return Manga(
|
||||
|
||||
Reference in New Issue
Block a user