mirror of
https://github.com/InsanusMokrassar/SauceNaoAPI.git
synced 2025-12-11 23:45:44 +00:00
upmigration
This commit is contained in:
@@ -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")
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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()
|
||||
}
|
||||
}
|
||||
@@ -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()
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
103
src/commonMain/kotlin/dev/inmo/saucenaoapi/utils/TimeManager.kt
Normal file
103
src/commonMain/kotlin/dev/inmo/saucenaoapi/utils/TimeManager.kt
Normal 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()
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user