Update utils

master
Koitharu 1 year ago
parent 8481fadbd0
commit b0a1cc48a6
Signed by: Koitharu
GPG Key ID: 676DEE768C17A9D7

@ -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(

Loading…
Cancel
Save