diff --git a/lib/ratelimit/build.gradle b/lib/ratelimit/build.gradle
new file mode 100644
index 000000000..814d8e947
--- /dev/null
+++ b/lib/ratelimit/build.gradle
@@ -0,0 +1,29 @@
+apply plugin: 'com.android.library'
+apply plugin: 'kotlin-android'
+
+android {
+ compileSdkVersion 27
+ buildToolsVersion '28.0.3'
+
+ defaultConfig {
+ minSdkVersion 16
+ targetSdkVersion 27
+ versionCode 1
+ versionName '1.0.0'
+ }
+
+ buildTypes {
+ release {
+ minifyEnabled false
+ }
+ }
+}
+
+repositories {
+ mavenCentral()
+}
+
+dependencies {
+ compileOnly "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version"
+ compileOnly 'com.squareup.okhttp3:okhttp:3.10.0'
+}
diff --git a/lib/ratelimit/src/main/AndroidManifest.xml b/lib/ratelimit/src/main/AndroidManifest.xml
new file mode 100644
index 000000000..a08d6eaff
--- /dev/null
+++ b/lib/ratelimit/src/main/AndroidManifest.xml
@@ -0,0 +1,3 @@
+
+
diff --git a/lib/ratelimit/src/main/java/eu/kanade/tachiyomi/lib/ratelimit/RateLimitInterceptor.kt b/lib/ratelimit/src/main/java/eu/kanade/tachiyomi/lib/ratelimit/RateLimitInterceptor.kt
new file mode 100644
index 000000000..32bcbcb8d
--- /dev/null
+++ b/lib/ratelimit/src/main/java/eu/kanade/tachiyomi/lib/ratelimit/RateLimitInterceptor.kt
@@ -0,0 +1,58 @@
+package eu.kanade.tachiyomi.lib.ratelimit
+
+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(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())
+ }
+
+}
diff --git a/settings.gradle b/settings.gradle
index 4619bfc30..6ef57c2a2 100644
--- a/settings.gradle
+++ b/settings.gradle
@@ -7,5 +7,7 @@ new File(rootDir, "src").eachDir { dir ->
}
include ':duktape-stub'
include ':preference-stub'
+include ':lib-ratelimit'
project(':duktape-stub').projectDir = new File("lib/duktape-stub")
-project(':preference-stub').projectDir = new File("lib/preference-stub")
\ No newline at end of file
+project(':preference-stub').projectDir = new File("lib/preference-stub")
+project(':lib-ratelimit').projectDir = new File("lib/ratelimit")
diff --git a/src/all/mangadex/build.gradle b/src/all/mangadex/build.gradle
index 808c02713..b66737c16 100644
--- a/src/all/mangadex/build.gradle
+++ b/src/all/mangadex/build.gradle
@@ -5,11 +5,12 @@ ext {
appName = 'Tachiyomi: MangaDex'
pkgNameSuffix = 'all.mangadex'
extClass = '.MangadexFactory'
- extVersionCode = 56
+ extVersionCode = 57
libVersion = '1.2'
}
dependencies {
+ implementation project(':lib-ratelimit')
compileOnly project(':preference-stub')
compileOnly 'com.google.code.gson:gson:2.8.2'
compileOnly 'com.github.salomonbrys.kotson:kotson:2.5.0'
diff --git a/src/all/mangadex/src/eu/kanade/tachiyomi/extension/all/mangadex/Mangadex.kt b/src/all/mangadex/src/eu/kanade/tachiyomi/extension/all/mangadex/Mangadex.kt
index f3bd51892..37b954592 100644
--- a/src/all/mangadex/src/eu/kanade/tachiyomi/extension/all/mangadex/Mangadex.kt
+++ b/src/all/mangadex/src/eu/kanade/tachiyomi/extension/all/mangadex/Mangadex.kt
@@ -2,7 +2,6 @@ package eu.kanade.tachiyomi.extension.all.mangadex
import android.app.Application
import android.content.SharedPreferences
-import android.os.SystemClock
import android.support.v7.preference.ListPreference
import android.support.v7.preference.PreferenceScreen
import com.github.salomonbrys.kotson.forEach
@@ -16,6 +15,7 @@ import com.github.salomonbrys.kotson.string
import com.google.gson.JsonElement
import com.google.gson.JsonObject
import com.google.gson.JsonParser
+import eu.kanade.tachiyomi.lib.ratelimit.RateLimitInterceptor
import eu.kanade.tachiyomi.network.GET
import eu.kanade.tachiyomi.network.asObservableSuccess
import eu.kanade.tachiyomi.source.ConfigurableSource
@@ -28,7 +28,6 @@ import eu.kanade.tachiyomi.source.model.SManga
import eu.kanade.tachiyomi.source.online.ParsedHttpSource
import okhttp3.Headers
import okhttp3.HttpUrl
-import okhttp3.Interceptor
import okhttp3.OkHttpClient
import okhttp3.Request
import okhttp3.Response
@@ -57,37 +56,7 @@ open class Mangadex(override val lang: String, private val internalLang: String,
Injekt.get().getSharedPreferences("source_$id", 0x0000)
}
- private val requestsPerSecond = 4
- private val lastRequests = ArrayList(requestsPerSecond)
- private val rateLimitInterceptor = Interceptor {
- synchronized(this) {
- val now = SystemClock.elapsedRealtime()
- val waitTime = if (lastRequests.size < requestsPerSecond) {
- 0
- } else {
- val oldestReq = lastRequests[0]
- val newestReq = lastRequests[requestsPerSecond - 1]
-
- if (newestReq - oldestReq > 1000) {
- 0
- } else {
- oldestReq + 1000 - now // Remaining time for the next second
- }
- }
-
- if (lastRequests.size == requestsPerSecond) {
- lastRequests.removeAt(0)
- }
- if (waitTime > 0) {
- lastRequests.add(now + waitTime)
- Thread.sleep(waitTime) // Sleep inside synchronized to pause queued requests
- } else {
- lastRequests.add(now)
- }
- }
-
- it.proceed(it.request())
- }
+ private val rateLimitInterceptor = RateLimitInterceptor(4)
override val client: OkHttpClient = network.cloudflareClient.newBuilder()
.addNetworkInterceptor(rateLimitInterceptor)