diff --git a/plugins/igdb/build.gradle.kts b/plugins/igdb/build.gradle.kts index 6c3ce88..58f4918 100644 --- a/plugins/igdb/build.gradle.kts +++ b/plugins/igdb/build.gradle.kts @@ -1,3 +1,5 @@ +val resilience4jVersion = "2.2.0" + plugins { id("com.google.devtools.ksp") } @@ -8,6 +10,17 @@ dependencies { // IGDB API client implementation("io.github.husnjak:igdb-api-jvm:1.3.1") + // Resilience4j for rate limiting + implementation("io.github.resilience4j:resilience4j-ratelimiter:${resilience4jVersion}") { + exclude(group = "org.slf4j") + } + implementation("io.github.resilience4j:resilience4j-bulkhead:${resilience4jVersion}") { + exclude(group = "org.slf4j") + } + implementation("io.github.resilience4j:resilience4j-all:${resilience4jVersion}") { + exclude(group = "org.slf4j") + } + // Fuzzy string matching implementation("me.xdrop:fuzzywuzzy:1.4.0") } \ No newline at end of file diff --git a/plugins/igdb/src/main/kotlin/org/gameyfin/plugins/metadata/igdb/IgdbPlugin.kt b/plugins/igdb/src/main/kotlin/org/gameyfin/plugins/metadata/igdb/IgdbPlugin.kt index 33f6fa3..ef9bb2b 100644 --- a/plugins/igdb/src/main/kotlin/org/gameyfin/plugins/metadata/igdb/IgdbPlugin.kt +++ b/plugins/igdb/src/main/kotlin/org/gameyfin/plugins/metadata/igdb/IgdbPlugin.kt @@ -1,10 +1,14 @@ package org.gameyfin.plugins.metadata.igdb import com.api.igdb.apicalypse.APICalypse -import com.api.igdb.exceptions.RequestException import com.api.igdb.request.IGDBWrapper import com.api.igdb.request.TwitchAuthenticator import com.api.igdb.request.games +import io.github.resilience4j.bulkhead.Bulkhead +import io.github.resilience4j.bulkhead.BulkheadConfig +import io.github.resilience4j.decorators.Decorators +import io.github.resilience4j.ratelimiter.RateLimiter +import io.github.resilience4j.ratelimiter.RateLimiterConfig import me.xdrop.fuzzywuzzy.FuzzySearch import org.gameyfin.pluginapi.core.config.ConfigMetadata import org.gameyfin.pluginapi.core.config.PluginConfigError @@ -15,10 +19,9 @@ import org.gameyfin.pluginapi.gamemetadata.GameMetadata import org.gameyfin.pluginapi.gamemetadata.GameMetadataProvider import org.pf4j.Extension import org.pf4j.PluginWrapper -import org.slf4j.LoggerFactory import proto.Game +import java.time.Duration import java.time.Instant -import java.util.concurrent.TimeUnit class IgdbPlugin(wrapper: PluginWrapper) : ConfigurableGameyfinPlugin(wrapper) { @@ -89,7 +92,21 @@ class IgdbPlugin(wrapper: PluginWrapper) : ConfigurableGameyfinPlugin(wrapper) { class IgdbMetadataProvider : GameMetadataProvider { companion object { - private val log = LoggerFactory.getLogger(this::class.java) + private val rateLimiter: RateLimiter = RateLimiter.of( + "igdb-api", + RateLimiterConfig.custom() + .limitForPeriod(4) + .limitRefreshPeriod(Duration.ofSeconds(1)) + .timeoutDuration(Duration.ofMinutes(10)) + .build() + ) + private val bulkhead: Bulkhead = Bulkhead.of( + "igdb-api", + BulkheadConfig.custom() + .maxConcurrentCalls(8) + .maxWaitDuration(Duration.ofMinutes(10)) // Wait up to 10s for a slot + .build() + ) private val QUERY_FIELDS = listOf( "slug", @@ -168,21 +185,12 @@ class IgdbPlugin(wrapper: PluginWrapper) : ConfigurableGameyfinPlugin(wrapper) { } private fun queryIgdbGames(query: APICalypse): List { - return try { - IGDBWrapper.games(query) - } catch (e: RequestException) { - // FIXME: Handle rate limit errors with exponential backoff - if (e.statusCode == 429) { - val randomInterval = (1..5).random().toLong() - log.warn("IGDB rate limit exceeded, retrying in $randomInterval seconds...") - TimeUnit.SECONDS.sleep(randomInterval) - - return queryIgdbGames(query) - } - - log.error("Request to IGDB API failed with HTTP ${e.statusCode}") - emptyList() - } + val supplier = { IGDBWrapper.games(query) } + val decorated = Decorators.ofSupplier(supplier) + .withBulkhead(bulkhead) + .withRateLimiter(rateLimiter) + .decorate() + return decorated.get() } private fun toGameMetadata(game: Game): GameMetadata { diff --git a/plugins/igdb/src/main/resources/MANIFEST.MF b/plugins/igdb/src/main/resources/MANIFEST.MF index 68c4c5c..8d39525 100644 --- a/plugins/igdb/src/main/resources/MANIFEST.MF +++ b/plugins/igdb/src/main/resources/MANIFEST.MF @@ -1,4 +1,4 @@ -Plugin-Version: 1.0.0.beta2 +Plugin-Version: 1.0.0.beta3 Plugin-Class: org.gameyfin.plugins.metadata.igdb.IgdbPlugin Plugin-Id: org.gameyfin.plugins.metadata.igdb Plugin-Name: IGDB Metadata