upmigration

This commit is contained in:
2020-12-02 14:39:54 +06:00
parent 7bc1e822fc
commit f27c493771
29 changed files with 94 additions and 87 deletions

View File

@@ -0,0 +1,18 @@
package dev.inmo.saucenaoapi.utils
import kotlinx.serialization.KSerializer
import kotlinx.serialization.Serializer
import kotlinx.serialization.builtins.serializer
import kotlinx.serialization.encoding.Decoder
import kotlinx.serialization.json.*
@Serializer(String::class)
object CommonMultivariantStringSerializer : KSerializer<String> by String.serializer() {
override fun deserialize(decoder: Decoder): String {
return when (val parsed = JsonElement.serializer().deserialize(decoder)) {
is JsonPrimitive -> parsed.content
is JsonArray -> parsed.joinToString { it.jsonPrimitive.content }
else -> error("Unexpected answer object has been received: $parsed")
}
}
}

View File

@@ -0,0 +1,103 @@
package dev.inmo.saucenaoapi.utils
import dev.inmo.saucenaoapi.additional.LONG_TIME_RECALCULATING_MILLIS
import dev.inmo.saucenaoapi.additional.SHORT_TIME_RECALCULATING_MILLIS
import dev.inmo.saucenaoapi.exceptions.TooManyRequestsException
import dev.inmo.saucenaoapi.exceptions.TooManyRequestsLongException
import dev.inmo.saucenaoapi.models.Header
import dev.inmo.saucenaoapi.models.LimitsState
import com.soywiz.klock.DateTime
import kotlinx.coroutines.*
import kotlinx.coroutines.channels.Channel
import kotlin.coroutines.suspendCoroutine
import kotlin.math.max
import kotlin.math.min
internal class RequestQuotaManager (
scope: CoroutineScope
) : SauceCloseable {
private var longQuota = 1
private var shortQuota = 1
private var longMaxQuota = 1
private var shortMaxQuota = 1
val limitsState: LimitsState
get() = LimitsState(
shortMaxQuota,
longMaxQuota,
shortQuota,
longQuota
)
private val quotaActions = Channel<suspend () -> Unit>(Channel.UNLIMITED)
private val quotaJob = scope.launch {
for (callback in quotaActions) {
callback()
}
}
private suspend fun updateQuota(
newLongQuota: Int,
newShortQuota: Int,
newMaxLongQuota: Int?,
newMaxShortQuota: Int?,
timeManager: TimeManager
) {
quotaActions.send(
suspend {
longMaxQuota = newMaxLongQuota ?: longMaxQuota
shortMaxQuota = newMaxShortQuota ?: shortMaxQuota
longQuota = min(newLongQuota, longMaxQuota)
shortQuota = min(newShortQuota, shortMaxQuota)
when {
longQuota < 1 -> (timeManager.getMostOldestInLongPeriod() ?: DateTime.now()) + LONG_TIME_RECALCULATING_MILLIS
shortQuota < 1 -> (timeManager.getMostOldestInShortPeriod() ?: DateTime.now()) + SHORT_TIME_RECALCULATING_MILLIS
else -> null
} ?.also {
delay((it - DateTime.now()).millisecondsLong)
shortQuota = max(shortQuota, 1)
longQuota = max(longQuota, 1)
}
Unit
}
)
}
suspend fun updateQuota(header: Header, timeManager: TimeManager) = updateQuota(
header.longRemaining,
header.shortRemaining,
header.longLimit,
header.shortLimit,
timeManager
)
suspend fun happenTooManyRequests(timeManager: TimeManager, e: TooManyRequestsException) = updateQuota(
if (e is TooManyRequestsLongException) 0 else 1,
0,
null,
null,
timeManager
)
suspend fun getQuota() {
return suspendCoroutine {
lateinit var callback: suspend () -> Unit
callback = suspend {
if (longQuota > 0 && shortQuota > 0) {
it.resumeWith(Result.success(Unit))
} else {
quotaActions.send(callback)
}
}
quotaActions.offer(callback)
}
}
override fun close() {
quotaJob.cancel()
quotaActions.close()
}
}

View File

@@ -0,0 +1,21 @@
package dev.inmo.saucenaoapi.utils
import kotlinx.coroutines.supervisorScope
interface SauceCloseable {
fun close()
}
fun <T> SauceCloseable.use(block: (SauceCloseable) -> T): T = try {
block(this)
} finally {
close()
}
suspend fun <T> SauceCloseable.useSafe(block: suspend (SauceCloseable) -> T): T = try {
supervisorScope {
block(this@useSafe)
}
} finally {
close()
}

View File

@@ -0,0 +1,18 @@
package dev.inmo.saucenaoapi.utils
import dev.inmo.saucenaoapi.additional.LONG_TIME_RECALCULATING_MILLIS
import dev.inmo.saucenaoapi.additional.SHORT_TIME_RECALCULATING_MILLIS
import dev.inmo.saucenaoapi.models.Header
import com.soywiz.klock.DateTime
internal suspend fun calculateSleepTime(
header: Header,
mostOldestInShortPeriodGetter: suspend () -> DateTime?,
mostOldestInLongPeriodGetter: suspend () -> DateTime?
): DateTime? {
return when {
header.longRemaining < 1 -> mostOldestInLongPeriodGetter() ?.plus(LONG_TIME_RECALCULATING_MILLIS)
header.shortRemaining < 1 -> mostOldestInShortPeriodGetter() ?.plus(SHORT_TIME_RECALCULATING_MILLIS)
else -> null
}
}

View File

@@ -0,0 +1,103 @@
package dev.inmo.saucenaoapi.utils
import dev.inmo.saucenaoapi.additional.LONG_TIME_RECALCULATING_MILLIS
import dev.inmo.saucenaoapi.additional.SHORT_TIME_RECALCULATING_MILLIS
import com.soywiz.klock.DateTime
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.channels.Channel
import kotlinx.coroutines.launch
import kotlin.coroutines.Continuation
import kotlin.coroutines.suspendCoroutine
private fun MutableList<DateTime>.clearTooOldTimes(relatedTo: DateTime = DateTime.now()) {
val limitValue = relatedTo - LONG_TIME_RECALCULATING_MILLIS
removeAll {
it < limitValue
}
}
private interface TimeManagerAction {
suspend fun makeChangeWith(times: MutableList<DateTime>)
suspend operator fun invoke(times: MutableList<DateTime>) = makeChangeWith(times)
}
private data class TimeManagerClean(private val relatedTo: DateTime = DateTime.now()) : TimeManagerAction {
override suspend fun makeChangeWith(times: MutableList<DateTime>) {
times.clearTooOldTimes(relatedTo)
}
}
private data class TimeManagerTimeAdder(
private val time: DateTime = DateTime.now()
) : TimeManagerAction {
override suspend fun makeChangeWith(times: MutableList<DateTime>) {
times.add(time)
times.clearTooOldTimes()
}
}
private data class TimeManagerMostOldestInLongGetter(
private val continuation: Continuation<DateTime?>
) : TimeManagerAction {
override suspend fun makeChangeWith(times: MutableList<DateTime>) {
times.clearTooOldTimes()
continuation.resumeWith(Result.success(times.minOrNull()))
}
}
private data class TimeManagerMostOldestInShortGetter(
private val continuation: Continuation<DateTime?>
) : TimeManagerAction {
override suspend fun makeChangeWith(times: MutableList<DateTime>) {
times.clearTooOldTimes()
val now = DateTime.now()
val limitTime = now - SHORT_TIME_RECALCULATING_MILLIS
continuation.resumeWith(
Result.success(
times.asSequence().filter {
limitTime < it
}.minOrNull()
)
)
}
}
internal class TimeManager(
scope: CoroutineScope
) : SauceCloseable {
private val actionsChannel = Channel<TimeManagerAction>(Channel.UNLIMITED)
private val timeUpdateJob = scope.launch {
val times = mutableListOf<DateTime>()
for (action in actionsChannel) {
action(times)
}
}
suspend fun addTimeAndClear() {
actionsChannel.send(TimeManagerTimeAdder())
}
suspend fun getMostOldestInLongPeriod(): DateTime? {
return suspendCoroutine {
actionsChannel.offer(
TimeManagerMostOldestInLongGetter(it)
)
}
}
suspend fun getMostOldestInShortPeriod(): DateTime? {
return suspendCoroutine {
actionsChannel.offer(TimeManagerMostOldestInShortGetter(it))
}
}
override fun close() {
actionsChannel.close()
timeUpdateJob.cancel()
}
}