From 9b6567f5e4bfa4b8a4845400216551fd28545094 Mon Sep 17 00:00:00 2001 From: Alessandro Jean <14254807+alessandrojean@users.noreply.github.com> Date: Fri, 4 Aug 2023 18:11:43 -0300 Subject: [PATCH] Add support to kotlin.time APIs in the rate limit interceptor (#9797) * Add support to kotlin.time APIs in the rate limit interceptor. * Add a missing line break in the doc. * Move the specific host to the same file. * Add kotlin.time rule to Proguard and remove specific host rule. * Mark the old version as deprecated and address review. * Remove unused import. * Remove yet another unused import. --- app/proguard-rules.pro | 1 + .../data/track/anilist/AnilistApi.kt | 4 +- .../interceptor/RateLimitInterceptor.kt | 37 +++++++++--- .../SpecificHostRateLimitInterceptor.kt | 59 +++++++++++++++++-- 4 files changed, 87 insertions(+), 14 deletions(-) diff --git a/app/proguard-rules.pro b/app/proguard-rules.pro index e8f9b52202..4679656733 100644 --- a/app/proguard-rules.pro +++ b/app/proguard-rules.pro @@ -8,6 +8,7 @@ -keep,allowoptimization class kotlin.** { public protected *; } -keep,allowoptimization class kotlinx.coroutines.** { public protected *; } -keep,allowoptimization class kotlinx.serialization.** { public protected *; } +-keep,allowoptimization class kotlin.time.** { public protected *; } -keep,allowoptimization class okhttp3.** { public protected *; } -keep,allowoptimization class okio.** { public protected *; } -keep,allowoptimization class rx.** { public protected *; } diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/track/anilist/AnilistApi.kt b/app/src/main/java/eu/kanade/tachiyomi/data/track/anilist/AnilistApi.kt index d18312e662..b1142c74d5 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/data/track/anilist/AnilistApi.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/track/anilist/AnilistApi.kt @@ -27,7 +27,7 @@ import okhttp3.RequestBody.Companion.toRequestBody import tachiyomi.core.util.lang.withIOContext import uy.kohesive.injekt.injectLazy import java.util.Calendar -import java.util.concurrent.TimeUnit +import kotlin.time.Duration.Companion.minutes class AnilistApi(val client: OkHttpClient, interceptor: AnilistInterceptor) { @@ -35,7 +35,7 @@ class AnilistApi(val client: OkHttpClient, interceptor: AnilistInterceptor) { private val authClient = client.newBuilder() .addInterceptor(interceptor) - .rateLimit(permits = 85, period = 1, unit = TimeUnit.MINUTES) + .rateLimit(permits = 85, period = 1.minutes) .build() suspend fun addLibManga(track: Track): Track { diff --git a/core/src/main/java/eu/kanade/tachiyomi/network/interceptor/RateLimitInterceptor.kt b/core/src/main/java/eu/kanade/tachiyomi/network/interceptor/RateLimitInterceptor.kt index 6b101e35e2..c4727f8a9b 100644 --- a/core/src/main/java/eu/kanade/tachiyomi/network/interceptor/RateLimitInterceptor.kt +++ b/core/src/main/java/eu/kanade/tachiyomi/network/interceptor/RateLimitInterceptor.kt @@ -8,10 +8,17 @@ import java.io.IOException import java.util.ArrayDeque import java.util.concurrent.Semaphore import java.util.concurrent.TimeUnit +import kotlin.time.Duration +import kotlin.time.Duration.Companion.seconds +import kotlin.time.toDuration +import kotlin.time.toDurationUnit /** * An OkHttp interceptor that handles rate limiting. * + * This uses `java.time` APIs and is the legacy method, kept + * for compatibility reasons with existing extensions. + * * Examples: * * permits = 5, period = 1, unit = seconds => 5 requests per second @@ -19,27 +26,43 @@ import java.util.concurrent.TimeUnit * * @since extension-lib 1.3 * - * @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. + * @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. */ +@Deprecated("Use the version with kotlin.time APIs instead.") fun OkHttpClient.Builder.rateLimit( permits: Int, period: Long = 1, unit: TimeUnit = TimeUnit.SECONDS, -) = addInterceptor(RateLimitInterceptor(null, permits, period, unit)) +) = addInterceptor(RateLimitInterceptor(null, permits, period.toDuration(unit.toDurationUnit()))) + +/** + * An OkHttp interceptor that handles rate limiting. + * + * Examples: + * + * permits = 5, period = 1.seconds => 5 requests per second + * permits = 10, period = 2.minutes => 10 requests per 2 minutes + * + * @since extension-lib 1.5 + * + * @param permits [Int] Number of requests allowed within a period of units. + * @param period [Duration] The limiting duration. Defaults to 1.seconds. + */ +fun OkHttpClient.Builder.rateLimit(permits: Int, period: Duration = 1.seconds) = + addInterceptor(RateLimitInterceptor(null, permits, period)) /** We can probably accept domains or wildcards by comparing with [endsWith], etc. */ @Suppress("PLATFORM_CLASS_MAPPED_TO_KOTLIN") internal class RateLimitInterceptor( private val host: String?, private val permits: Int, - period: Long, - unit: TimeUnit, + period: Duration, ) : Interceptor { private val requestQueue = ArrayDeque(permits) - private val rateLimitMillis = unit.toMillis(period) + private val rateLimitMillis = period.inWholeMilliseconds private val fairLock = Semaphore(1, true) override fun intercept(chain: Interceptor.Chain): Response { diff --git a/core/src/main/java/eu/kanade/tachiyomi/network/interceptor/SpecificHostRateLimitInterceptor.kt b/core/src/main/java/eu/kanade/tachiyomi/network/interceptor/SpecificHostRateLimitInterceptor.kt index 5687c9f576..b364db7438 100644 --- a/core/src/main/java/eu/kanade/tachiyomi/network/interceptor/SpecificHostRateLimitInterceptor.kt +++ b/core/src/main/java/eu/kanade/tachiyomi/network/interceptor/SpecificHostRateLimitInterceptor.kt @@ -1,12 +1,20 @@ package eu.kanade.tachiyomi.network.interceptor import okhttp3.HttpUrl +import okhttp3.HttpUrl.Companion.toHttpUrlOrNull import okhttp3.OkHttpClient import java.util.concurrent.TimeUnit +import kotlin.time.Duration +import kotlin.time.Duration.Companion.seconds +import kotlin.time.toDuration +import kotlin.time.toDurationUnit /** * An OkHttp interceptor that handles given url host's rate limiting. * + * This uses Java Time APIs and is the legacy method, kept + * for compatibility reasons with existing extensions. + * * Examples: * * httpUrl = "api.manga.com".toHttpUrlOrNull(), permits = 5, period = 1, unit = seconds => 5 requests per second to api.manga.com @@ -14,14 +22,55 @@ import java.util.concurrent.TimeUnit * * @since extension-lib 1.3 * - * @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. + * @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. */ +@Deprecated("Use the version with kotlin.time APIs instead.") fun OkHttpClient.Builder.rateLimitHost( httpUrl: HttpUrl, permits: Int, period: Long = 1, unit: TimeUnit = TimeUnit.SECONDS, -) = addInterceptor(RateLimitInterceptor(httpUrl.host, permits, period, unit)) +) = addInterceptor(RateLimitInterceptor(httpUrl.host, permits, period.toDuration(unit.toDurationUnit()))) + +/** + * An OkHttp interceptor that handles given url host's rate limiting. + * + * Examples: + * + * httpUrl = "https://api.manga.com".toHttpUrlOrNull(), permits = 5, period = 1.seconds => 5 requests per second to api.manga.com + * httpUrl = "https://imagecdn.manga.com".toHttpUrlOrNull(), permits = 10, period = 2.minutes => 10 requests per 2 minutes to imagecdn.manga.com + * + * @since extension-lib 1.5 + * + * @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 [Duration] The limiting duration. Defaults to 1.seconds. + */ +fun OkHttpClient.Builder.rateLimitHost( + httpUrl: HttpUrl, + permits: Int, + period: Duration = 1.seconds, +) = addInterceptor(RateLimitInterceptor(httpUrl.host, permits, period)) + +/** + * An OkHttp interceptor that handles given url host's rate limiting. + * + * Examples: + * + * url = "https://api.manga.com", permits = 5, period = 1.seconds => 5 requests per second to api.manga.com + * url = "https://imagecdn.manga.com", permits = 10, period = 2.minutes => 10 requests per 2 minutes to imagecdn.manga.com + * + * @since extension-lib 1.5 + * + * @param url [String] 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 [Duration] The limiting duration. Defaults to 1.seconds. + */ +fun OkHttpClient.Builder.rateLimitHost( + url: String, + permits: Int, + period: Duration = 1.seconds, +) = addInterceptor(RateLimitInterceptor(url.toHttpUrlOrNull()?.host, permits, period))