Implement type-safe config for plugins in BE and FE

This commit is contained in:
grimsi
2025-06-03 17:51:17 +02:00
parent 6e390df900
commit 0050ab1f74
30 changed files with 372 additions and 259 deletions
@@ -0,0 +1,18 @@
package de.grimsi.gameyfin.plugins.directdownload
import de.grimsi.gameyfin.plugins.directdownload.CompressionMode.*
import java.util.zip.Deflater
enum class CompressionMode {
NONE,
FAST,
BEST;
}
fun CompressionMode.deflaterLevel(): Int {
return when (this) {
NONE -> Deflater.NO_COMPRESSION
FAST -> Deflater.BEST_SPEED
BEST -> Deflater.BEST_COMPRESSION
}
}
@@ -1,8 +1,8 @@
package de.grimsi.gameyfin.plugins.directdownload
import de.grimsi.gameyfin.pluginapi.core.ConfigurableGameyfinPlugin
import de.grimsi.gameyfin.pluginapi.core.PluginConfigElement
import de.grimsi.gameyfin.pluginapi.core.PluginConfigValidationResult
import de.grimsi.gameyfin.pluginapi.core.config.ConfigMetadata
import de.grimsi.gameyfin.pluginapi.core.config.PluginConfigMetadata
import de.grimsi.gameyfin.pluginapi.core.wrapper.ConfigurableGameyfinPlugin
import de.grimsi.gameyfin.pluginapi.download.Download
import de.grimsi.gameyfin.pluginapi.download.DownloadProvider
import de.grimsi.gameyfin.pluginapi.download.FileDownload
@@ -14,7 +14,6 @@ import java.io.PipedInputStream
import java.io.PipedOutputStream
import java.nio.file.*
import java.nio.file.attribute.BasicFileAttributes
import java.util.zip.Deflater
import java.util.zip.ZipEntry
import java.util.zip.ZipOutputStream
import kotlin.io.path.exists
@@ -24,31 +23,25 @@ import kotlin.io.path.isDirectory
class DirectDownloadPlugin(wrapper: PluginWrapper) : ConfigurableGameyfinPlugin(wrapper) {
override val configMetadata: List<PluginConfigElement> = listOf(
PluginConfigElement(
companion object {
lateinit var plugin: DirectDownloadPlugin
private set
}
init {
plugin = this
}
override val configMetadata: PluginConfigMetadata = listOf(
ConfigMetadata(
key = "compressionMode",
name = "Compression mode (\"none\" = default, \"fast\", \"best\")",
type = CompressionMode::class.java,
label = "Compression mode",
description = "Higher compression modes are more resource intensive, but save bandwidth",
default = CompressionMode.NONE
)
)
override fun validateConfig(config: Map<String, String?>): PluginConfigValidationResult {
val compressionMode = config["compressionMode"]
if (compressionMode != null) {
return try {
CompressionMode.valueOf(compressionMode.uppercase())
PluginConfigValidationResult.VALID
} catch (_: IllegalArgumentException) {
PluginConfigValidationResult.INVALID(
mapOf("compressionMode" to "Invalid compression mode: $compressionMode (must be \"none\", \"fast\", or \"best\")")
)
}
}
return PluginConfigValidationResult.VALID
}
@Extension
class DirectDownloadProvider : DownloadProvider {
override fun download(path: Path): Download {
@@ -95,9 +88,8 @@ class DirectDownloadPlugin(wrapper: PluginWrapper) : ConfigurableGameyfinPlugin(
try {
ZipOutputStream(pipeOut).use { zos ->
zos.setLevel(CompressionMode.toDeflaterLevel(plugin.config["compressionMode"]?.let {
CompressionMode.valueOf(it.uppercase())
} ?: CompressionMode.NONE))
val compressionMode = plugin.config<CompressionMode>("compressionMode")
zos.setLevel(compressionMode.deflaterLevel())
Files.walkFileTree(path, object : SimpleFileVisitor<Path>() {
override fun visitFile(file: Path, attrs: BasicFileAttributes): FileVisitResult {
@@ -124,21 +116,4 @@ class DirectDownloadPlugin(wrapper: PluginWrapper) : ConfigurableGameyfinPlugin(
return pipeIn
}
}
}
enum class CompressionMode {
NONE,
FAST,
BEST;
companion object {
fun toDeflaterLevel(mode: CompressionMode): Int {
return when (mode) {
NONE -> Deflater.NO_COMPRESSION
FAST -> Deflater.BEST_SPEED
BEST -> Deflater.BEST_COMPRESSION
}
}
}
}
@@ -5,7 +5,8 @@ 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 de.grimsi.gameyfin.pluginapi.core.*
import de.grimsi.gameyfin.pluginapi.core.config.*
import de.grimsi.gameyfin.pluginapi.core.wrapper.ConfigurableGameyfinPlugin
import de.grimsi.gameyfin.pluginapi.gamemetadata.GameMetadata
import de.grimsi.gameyfin.pluginapi.gamemetadata.GameMetadataProvider
import me.xdrop.fuzzywuzzy.FuzzySearch
@@ -16,26 +17,35 @@ import proto.Game
import java.time.Instant
import java.util.concurrent.TimeUnit
class IgdbPlugin(wrapper: PluginWrapper) : GameyfinPlugin(wrapper), Configurable {
class IgdbPlugin(wrapper: PluginWrapper) : ConfigurableGameyfinPlugin(wrapper) {
override val configMetadata = listOf(
PluginConfigElement(
override val configMetadata: PluginConfigMetadata = listOf(
ConfigMetadata(
key = "clientId",
name = "Twitch client ID",
type = String::class.java,
label = "Twitch client ID",
description = "Your Twitch Client ID"
),
PluginConfigElement(
ConfigMetadata(
key = "clientSecret",
name = "Twitch client secret",
type = String::class.java,
label = "Twitch client secret",
description = "Your Twitch Client Secret",
isSecret = true
)
)
override var config: Map<String, String?> = emptyMap()
override fun validateConfig(config: Map<String, String?>): PluginConfigValidationResult {
val pluginConfigValidationResult = super.validateConfig(config)
if (pluginConfigValidationResult.result == PluginConfigValidationResultType.INVALID) {
return pluginConfigValidationResult
}
try {
authenticate(config["clientId"], config["clientSecret"])
val clientIdToValidate = config["clientId"]
val clientSecretToValidate = config["clientSecret"]
authenticate(clientIdToValidate, clientSecretToValidate)
return PluginConfigValidationResult.VALID
} catch (e: PluginConfigError) {
log.error(e.message)
@@ -50,7 +60,7 @@ class IgdbPlugin(wrapper: PluginWrapper) : GameyfinPlugin(wrapper), Configurable
override fun start() {
try {
authenticate(config["clientId"], config["clientSecret"])
authenticate(config("clientId"), config("clientSecret"))
} catch (e: PluginConfigError) {
log.error(e.message)
}
@@ -1,6 +1,6 @@
package de.grimsi.gameyfin.plugins.steam
import de.grimsi.gameyfin.pluginapi.core.GameyfinPlugin
import de.grimsi.gameyfin.pluginapi.core.wrapper.GameyfinPlugin
import de.grimsi.gameyfin.pluginapi.gamemetadata.GameMetadata
import de.grimsi.gameyfin.pluginapi.gamemetadata.GameMetadataProvider
import de.grimsi.gameyfin.plugins.steam.dto.SteamDetailsResultWrapper
@@ -1,9 +1,7 @@
package de.grimsi.gameyfin.plugins.steamgriddb
import de.grimsi.gameyfin.pluginapi.core.ConfigurableGameyfinPlugin
import de.grimsi.gameyfin.pluginapi.core.PluginConfigElement
import de.grimsi.gameyfin.pluginapi.core.PluginConfigError
import de.grimsi.gameyfin.pluginapi.core.PluginConfigValidationResult
import de.grimsi.gameyfin.pluginapi.core.config.*
import de.grimsi.gameyfin.pluginapi.core.wrapper.ConfigurableGameyfinPlugin
import de.grimsi.gameyfin.pluginapi.gamemetadata.GameMetadata
import de.grimsi.gameyfin.pluginapi.gamemetadata.GameMetadataProvider
import de.grimsi.gameyfin.plugins.steamgriddb.api.SteamGridDbApiClient
@@ -20,18 +18,26 @@ class SteamGridDbPlugin(wrapper: PluginWrapper) : ConfigurableGameyfinPlugin(wra
private var client: SteamGridDbApiClient? = null
}
override val configMetadata: List<PluginConfigElement> = listOf(
PluginConfigElement(
override val configMetadata: PluginConfigMetadata = listOf(
ConfigMetadata(
key = "apiKey",
name = "SteamGridDB API key",
description = "Your SteamGridDB API key",
type = String::class.java,
label = "SteamGridDB API key",
description = "The API key can be obtained from your SteamGridDB account preferences",
isSecret = true
)
)
override fun validateConfig(config: Map<String, String?>): PluginConfigValidationResult {
val pluginConfigValidationResult = super.validateConfig(config)
if (pluginConfigValidationResult.result == PluginConfigValidationResultType.INVALID) {
return pluginConfigValidationResult
}
try {
runBlocking { authenticate(config["apiKey"]) }
val apiKeyToValidate = config["apiKey"]
runBlocking { authenticate(apiKeyToValidate) }
return PluginConfigValidationResult.VALID
} catch (e: PluginConfigError) {
log.error(e.message)
@@ -43,7 +49,7 @@ class SteamGridDbPlugin(wrapper: PluginWrapper) : ConfigurableGameyfinPlugin(wra
override fun start() {
try {
runBlocking { authenticate(config["apiKey"]) }
runBlocking { authenticate(config("apiKey")) }
} catch (e: PluginConfigError) {
log.error(e.message)
}