Rate limiting for AniList tracker updates

Co-Authored-By: Gauthier <2139133+gotson@users.noreply.github.com>
This commit is contained in:
Jays2Kings 2021-10-12 14:56:03 -04:00
parent 4aff8af508
commit 2230e69e8c
5 changed files with 132 additions and 3 deletions

View file

@ -12,6 +12,7 @@ import com.google.gson.JsonParser
import eu.kanade.tachiyomi.data.database.models.Track import eu.kanade.tachiyomi.data.database.models.Track
import eu.kanade.tachiyomi.data.track.model.TrackSearch import eu.kanade.tachiyomi.data.track.model.TrackSearch
import eu.kanade.tachiyomi.network.await import eu.kanade.tachiyomi.network.await
import eu.kanade.tachiyomi.network.interceptor.RateLimitInterceptor
import eu.kanade.tachiyomi.network.jsonMime import eu.kanade.tachiyomi.network.jsonMime
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext import kotlinx.coroutines.withContext
@ -25,7 +26,10 @@ import java.util.concurrent.TimeUnit
class AnilistApi(val client: OkHttpClient, interceptor: AnilistInterceptor) { class AnilistApi(val client: OkHttpClient, interceptor: AnilistInterceptor) {
private val authClient = client.newBuilder().addInterceptor(interceptor).build() private val authClient = client.newBuilder()
.addInterceptor(interceptor)
.addInterceptor(RateLimitInterceptor(85, 1, TimeUnit.MINUTES))
.build()
suspend fun addLibManga(track: Track): Track { suspend fun addLibManga(track: Track): Track {
return withContext(Dispatchers.IO) { return withContext(Dispatchers.IO) {

View file

@ -1,4 +1,4 @@
package eu.kanade.tachiyomi.network package eu.kanade.tachiyomi.network.interceptor
import android.annotation.SuppressLint import android.annotation.SuppressLint
import android.content.Context import android.content.Context
@ -8,6 +8,7 @@ import android.webkit.WebSettings
import android.webkit.WebView import android.webkit.WebView
import android.widget.Toast import android.widget.Toast
import eu.kanade.tachiyomi.R import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.network.NetworkHelper
import eu.kanade.tachiyomi.source.online.HttpSource import eu.kanade.tachiyomi.source.online.HttpSource
import eu.kanade.tachiyomi.util.system.WebViewClientCompat import eu.kanade.tachiyomi.util.system.WebViewClientCompat
import eu.kanade.tachiyomi.util.system.WebViewUtil import eu.kanade.tachiyomi.util.system.WebViewUtil

View file

@ -0,0 +1,58 @@
package eu.kanade.tachiyomi.network.interceptor
import android.os.SystemClock
import okhttp3.Interceptor
import okhttp3.Response
import java.util.concurrent.TimeUnit
/**
* An OkHttp interceptor that handles rate limiting.
*
* Examples:
*
* permits = 5, period = 1, unit = seconds => 5 requests per second
* permits = 10, period = 2, unit = minutes => 10 requests per 2 minutes
*
* @param permits {Int} Number of requests allowed within a period of units.
* @param period {Long} The limiting duration. Defaults to 1.
* @param unit {TimeUnit} The unit of time for the period. Defaults to seconds.
*/
class RateLimitInterceptor(
private val permits: Int,
private val period: Long = 1,
private val unit: TimeUnit = TimeUnit.SECONDS
) : Interceptor {
private val requestQueue = ArrayList<Long>(permits)
private val rateLimitMillis = unit.toMillis(period)
override fun intercept(chain: Interceptor.Chain): Response {
synchronized(requestQueue) {
val now = SystemClock.elapsedRealtime()
val waitTime = if (requestQueue.size < permits) {
0
} else {
val oldestReq = requestQueue[0]
val newestReq = requestQueue[permits - 1]
if (newestReq - oldestReq > rateLimitMillis) {
0
} else {
oldestReq + rateLimitMillis - now // Remaining time
}
}
if (requestQueue.size == permits) {
requestQueue.removeAt(0)
}
if (waitTime > 0) {
requestQueue.add(now + waitTime)
Thread.sleep(waitTime) // Sleep inside synchronized to pause queued requests
} else {
requestQueue.add(now)
}
}
return chain.proceed(chain.request())
}
}

View file

@ -0,0 +1,66 @@
package eu.kanade.tachiyomi.network.interceptor
import android.os.SystemClock
import okhttp3.HttpUrl
import okhttp3.Interceptor
import okhttp3.Response
import java.util.concurrent.TimeUnit
/**
* An OkHttp interceptor that handles given url host's rate limiting.
*
* Examples:
*
* httpUrl = "api.manga.com".toHttpUrlOrNull(), permits = 5, period = 1, unit = seconds => 5 requests per second to api.manga.com
* httpUrl = "imagecdn.manga.com".toHttpUrlOrNull(), permits = 10, period = 2, unit = minutes => 10 requests per 2 minutes to imagecdn.manga.com
*
* @param httpUrl {HttpUrl} The url host that this interceptor should handle. Will get url's host by using HttpUrl.host()
* @param permits {Int} Number of requests allowed within a period of units.
* @param period {Long} The limiting duration. Defaults to 1.
* @param unit {TimeUnit} The unit of time for the period. Defaults to seconds.
*/
@Suppress("unused")
class SpecificHostRateLimitInterceptor(
private val httpUrl: HttpUrl,
private val permits: Int,
private val period: Long = 1,
private val unit: TimeUnit = TimeUnit.SECONDS
) : Interceptor {
private val requestQueue = ArrayList<Long>(permits)
private val rateLimitMillis = unit.toMillis(period)
private val host = httpUrl.host
override fun intercept(chain: Interceptor.Chain): Response {
if (chain.request().url.host != host) {
return chain.proceed(chain.request())
}
synchronized(requestQueue) {
val now = SystemClock.elapsedRealtime()
val waitTime = if (requestQueue.size < permits) {
0
} else {
val oldestReq = requestQueue[0]
val newestReq = requestQueue[permits - 1]
if (newestReq - oldestReq > rateLimitMillis) {
0
} else {
oldestReq + rateLimitMillis - now // Remaining time
}
}
if (requestQueue.size == permits) {
requestQueue.removeAt(0)
}
if (waitTime > 0) {
requestQueue.add(now + waitTime)
Thread.sleep(waitTime) // Sleep inside synchronized to pause queued requests
} else {
requestQueue.add(now)
}
}
return chain.proceed(chain.request())
}
}

View file

@ -1,4 +1,4 @@
package eu.kanade.tachiyomi.network package eu.kanade.tachiyomi.network.interceptor
import eu.kanade.tachiyomi.source.online.HttpSource import eu.kanade.tachiyomi.source.online.HttpSource
import okhttp3.Interceptor import okhttp3.Interceptor