mirror of
https://github.com/InsanusMokrassar/SauceNaoAPI.git
synced 2024-12-23 05:07:12 +00:00
started 0.4.0
This commit is contained in:
parent
9b9bbefaf0
commit
555e1da355
@ -1,4 +1,8 @@
|
||||
# 0.3.0
|
||||
# SauceNaoAPI Changelog
|
||||
|
||||
## 0.4.0
|
||||
|
||||
## 0.3.0
|
||||
|
||||
* Now `results` field of `SauceNaoAnswer` is optional and is empty list by default
|
||||
* Adapted structure almost completed and now can be used with raw results
|
||||
|
@ -1,4 +1,4 @@
|
||||
project.version = "0.3.0"
|
||||
project.version = "0.4.0"
|
||||
project.group = "com.github.insanusmokrassar"
|
||||
|
||||
buildscript {
|
||||
|
@ -1,9 +1,9 @@
|
||||
kotlin.code.style=official
|
||||
kotlin_version=1.3.31
|
||||
kotlin_coroutines_version=1.2.1
|
||||
kotlin_serialisation_runtime_version=0.11.0
|
||||
kotlin_version=1.3.41
|
||||
kotlin_coroutines_version=1.2.2
|
||||
kotlin_serialisation_runtime_version=0.11.1
|
||||
joda_time_version=2.10.1
|
||||
ktor_version=1.1.4
|
||||
ktor_version=1.2.3
|
||||
|
||||
project_public_name=SauceNao API
|
||||
project_public_description=SauceNao API library
|
||||
|
@ -0,0 +1,4 @@
|
||||
package com.github.insanusmokrassar.SauceNaoAPI
|
||||
|
||||
const val LONG_TIME_LIMIT_MILLIS: Int = 1 * 24 * 60 * 60 * 1000
|
||||
const val SHORT_TIME_LIMIT_MILLIS: Int = 30 * 1000
|
@ -1,16 +1,22 @@
|
||||
package com.github.insanusmokrassar.SauceNaoAPI
|
||||
|
||||
import kotlinx.coroutines.runBlocking
|
||||
import kotlinx.coroutines.*
|
||||
|
||||
fun main(vararg args: String) {
|
||||
val key = args.first()
|
||||
val api = SauceNaoAPI(key)
|
||||
|
||||
runBlocking {
|
||||
api.request(
|
||||
args[1]
|
||||
).also {
|
||||
println(it)
|
||||
val launch = GlobalScope.launch {
|
||||
api.use {
|
||||
it.request(
|
||||
args[1]
|
||||
).also {
|
||||
println(it)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
runBlocking {
|
||||
launch.join()
|
||||
}
|
||||
}
|
||||
|
@ -1,13 +1,19 @@
|
||||
package com.github.insanusmokrassar.SauceNaoAPI
|
||||
|
||||
import com.github.insanusmokrassar.SauceNaoAPI.exceptions.sauceNaoAPIException
|
||||
import com.github.insanusmokrassar.SauceNaoAPI.models.SauceNaoAnswer
|
||||
import io.ktor.client.HttpClient
|
||||
import io.ktor.client.call.call
|
||||
import io.ktor.client.engine.okhttp.OkHttp
|
||||
import io.ktor.client.request.parameter
|
||||
import io.ktor.client.request.url
|
||||
import io.ktor.client.features.ClientRequestException
|
||||
import io.ktor.client.request.*
|
||||
import io.ktor.client.response.readText
|
||||
import kotlinx.coroutines.*
|
||||
import kotlinx.coroutines.channels.Channel
|
||||
import kotlinx.io.core.Closeable
|
||||
import kotlinx.serialization.json.Json
|
||||
import org.joda.time.DateTime
|
||||
import java.util.logging.Logger
|
||||
import kotlin.coroutines.*
|
||||
|
||||
private const val API_TOKEN_FIELD = "api_key"
|
||||
private const val OUTPUT_TYPE_FIELD = "output_type"
|
||||
@ -25,7 +31,40 @@ data class SauceNaoAPI(
|
||||
private val outputType: OutputType = JsonOutputType,
|
||||
private val client: HttpClient = HttpClient(OkHttp),
|
||||
private val searchUrl: String = SEARCH_URL
|
||||
) {
|
||||
) : Closeable {
|
||||
private val logger = Logger.getLogger("SauceNaoAPI")
|
||||
|
||||
private val requestsChannel = Channel<Pair<Continuation<SauceNaoAnswer>, HttpRequestBuilder>>(Channel.UNLIMITED)
|
||||
private val requestsSendTimes = mutableListOf<DateTime>()
|
||||
|
||||
init {
|
||||
CoroutineScope(Dispatchers.Default).launch {
|
||||
for ((callback, requestBuilder) in requestsChannel) {
|
||||
try {
|
||||
val answer = makeRequest(requestBuilder)
|
||||
callback.resumeWith(Result.success(answer))
|
||||
|
||||
val sleepUntil = if (answer.header.longRemaining == 0) {
|
||||
getMostOldestInLongPeriod() ?.plusMillis(LONG_TIME_LIMIT_MILLIS)
|
||||
} else {
|
||||
if (answer.header.shortRemaining == 0) {
|
||||
getMostOldestInShortPeriod() ?.plusMillis(SHORT_TIME_LIMIT_MILLIS)
|
||||
} else {
|
||||
null
|
||||
}
|
||||
}
|
||||
|
||||
sleepUntil ?.also { _ ->
|
||||
logger.warning("LONG LIMIT REACHED, SLEEP UNTIL $sleepUntil")
|
||||
delay(sleepUntil.millis - DateTime.now().millis)
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
callback.resumeWith(Result.failure(e))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun request(
|
||||
url: String,
|
||||
resultsCount: Int? = null,
|
||||
@ -72,6 +111,57 @@ data class SauceNaoAPI(
|
||||
minSimilarity = minSimilarity
|
||||
)
|
||||
|
||||
private suspend fun makeRequest(
|
||||
builder: HttpRequestBuilder
|
||||
): SauceNaoAnswer {
|
||||
return try {
|
||||
val call = client.execute(builder)
|
||||
val answerText = call.response.readText()
|
||||
logger.info(answerText)
|
||||
addRequestTimesAndClear()
|
||||
Json.nonstrict.parse(
|
||||
SauceNaoAnswer.serializer(),
|
||||
answerText
|
||||
)
|
||||
} catch (e: ClientRequestException) {
|
||||
throw e.sauceNaoAPIException
|
||||
}
|
||||
}
|
||||
|
||||
private fun addRequestTimesAndClear() {
|
||||
val newDateTime = DateTime.now()
|
||||
|
||||
clearRequestTimes(newDateTime)
|
||||
|
||||
requestsSendTimes.add(newDateTime)
|
||||
}
|
||||
|
||||
private fun clearRequestTimes(relatedTo: DateTime = DateTime.now()) {
|
||||
val limitValue = relatedTo.minusMillis(LONG_TIME_LIMIT_MILLIS)
|
||||
|
||||
requestsSendTimes.removeAll {
|
||||
it < limitValue
|
||||
}
|
||||
}
|
||||
|
||||
private fun getMostOldestInLongPeriod(): DateTime? {
|
||||
clearRequestTimes()
|
||||
|
||||
return requestsSendTimes.min()
|
||||
}
|
||||
|
||||
private fun getMostOldestInShortPeriod(): DateTime? {
|
||||
val now = DateTime.now()
|
||||
|
||||
val limitTime = now.minusMillis(SHORT_TIME_LIMIT_MILLIS)
|
||||
|
||||
clearRequestTimes(now)
|
||||
|
||||
return requestsSendTimes.asSequence().filter {
|
||||
limitTime < it
|
||||
}.min()
|
||||
}
|
||||
|
||||
private suspend fun makeRequest(
|
||||
url: String,
|
||||
db: Int? = null,
|
||||
@ -80,21 +170,26 @@ data class SauceNaoAPI(
|
||||
resultsCount: Int? = null,
|
||||
minSimilarity: Float? = null
|
||||
): SauceNaoAnswer? {
|
||||
return client.call {
|
||||
url(searchUrl)
|
||||
parameter(URL_FIELD, url)
|
||||
parameter(API_TOKEN_FIELD, apiToken)
|
||||
parameter(OUTPUT_TYPE_FIELD, outputType.typeCode)
|
||||
db ?.also { parameter(DB_FIELD, it) }
|
||||
dbmask ?.also { parameter(DBMASK_FIELD, it) }
|
||||
dbmaski ?.also { parameter(DBMASKI_FIELD, it) }
|
||||
resultsCount ?.also { parameter(RESULTS_COUNT_FIELD, it) }
|
||||
minSimilarity ?.also { parameter(MINIMAL_SIMILARITY_FIELD, it) }
|
||||
}.response.readText().let {
|
||||
Json.nonstrict.parse(
|
||||
SauceNaoAnswer.serializer(),
|
||||
it
|
||||
return suspendCoroutine<SauceNaoAnswer> {
|
||||
requestsChannel.offer(
|
||||
it to HttpRequestBuilder().apply {
|
||||
url(searchUrl)
|
||||
parameter(URL_FIELD, url)
|
||||
parameter(API_TOKEN_FIELD, apiToken)
|
||||
parameter(OUTPUT_TYPE_FIELD, outputType.typeCode)
|
||||
db ?.also { parameter(DB_FIELD, it) }
|
||||
dbmask ?.also { parameter(DBMASK_FIELD, it) }
|
||||
dbmaski ?.also { parameter(DBMASKI_FIELD, it) }
|
||||
resultsCount ?.also { parameter(RESULTS_COUNT_FIELD, it) }
|
||||
minSimilarity ?.also { parameter(MINIMAL_SIMILARITY_FIELD, it) }
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
override fun close() {
|
||||
requestsChannel.close()
|
||||
requestsSendTimes.clear()
|
||||
client.close()
|
||||
}
|
||||
}
|
@ -5,14 +5,14 @@ import com.github.insanusmokrassar.SauceNaoAPI.models.Header
|
||||
|
||||
val Header.shortLimitStatus: LimitStatus
|
||||
get() = LimitStatus(
|
||||
shortRemaining ?: Int.MAX_VALUE,
|
||||
shortLimit ?: Int.MAX_VALUE
|
||||
shortRemaining,
|
||||
shortLimit
|
||||
)
|
||||
|
||||
val Header.longLimitStatus: LimitStatus
|
||||
get() = LimitStatus(
|
||||
longRemaining ?: Int.MAX_VALUE,
|
||||
longLimit ?: Int.MAX_VALUE
|
||||
longRemaining,
|
||||
longLimit
|
||||
)
|
||||
|
||||
val Header.limits
|
||||
|
@ -0,0 +1,13 @@
|
||||
package com.github.insanusmokrassar.SauceNaoAPI.exceptions
|
||||
|
||||
import io.ktor.client.features.ClientRequestException
|
||||
import io.ktor.http.HttpStatusCode.Companion.TooManyRequests
|
||||
import kotlinx.io.IOException
|
||||
|
||||
val ClientRequestException.sauceNaoAPIException: Exception
|
||||
get() = when (response.status) {
|
||||
TooManyRequests -> TooManyRequestsException()
|
||||
else -> this
|
||||
}
|
||||
|
||||
class TooManyRequestsException : IOException()
|
@ -25,13 +25,13 @@ data class Header(
|
||||
@SerialName("query_image")
|
||||
val queryImage: String? = null, // something like "uuid.jpg"
|
||||
@SerialName("short_remaining")
|
||||
val shortRemaining: Int? = null,
|
||||
val shortRemaining: Int = Int.MAX_VALUE,
|
||||
@SerialName("long_remaining")
|
||||
val longRemaining: Int? = null,
|
||||
val longRemaining: Int = Int.MAX_VALUE,
|
||||
@SerialName("short_limit")
|
||||
val shortLimit: Int? = null,
|
||||
val shortLimit: Int = Int.MAX_VALUE,
|
||||
@SerialName("long_limit")
|
||||
val longLimit: Int? = null,
|
||||
val longLimit: Int = Int.MAX_VALUE,
|
||||
@SerialName("account_type")
|
||||
val accountType: Int? = null,
|
||||
@SerialName("user_id")
|
||||
|
Loading…
Reference in New Issue
Block a user