Add javadoc to plugin-api

Configure plugin-api build to publish to Maven Central
This commit is contained in:
grimsi 2025-06-15 17:35:57 +02:00
parent c84c6a1d56
commit 8d9ce92c51
13 changed files with 364 additions and 14 deletions

35
.github/workflows/pluginapi-release.yml vendored Normal file
View File

@ -0,0 +1,35 @@
name: Plugin-API Release
on:
push:
tags:
- '*'
jobs:
release:
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Set up Java
uses: actions/setup-java@v4
with:
distribution: temurin
java-version: 17
cache: gradle
- name: Decrypt and import GPG key
run: |
echo "$GPG_PRIVATE_KEY" | gpg --batch --import
env:
GPG_PRIVATE_KEY: ${{ secrets.GPG_PRIVATE_KEY }}
- name: Build and deploy with JReleaser
run: ./gradlew jreleaserFullRelease
env:
GPG_PASSPHRASE: ${{ secrets.GPG_PASSPHRASE }}
MAVENCENTRAL_USERNAME: ${{ secrets.MAVENCENTRAL_USERNAME }}
MAVENCENTRAL_TOKEN: ${{ secrets.MAVENCENTRAL_TOKEN }}
JRELEASER_GITHUB_TOKEN: ${{ GITHUB_TOKEN }}

View File

@ -0,0 +1,25 @@
<component name="ProjectRunConfigurationManager">
<configuration default="false" name="Publish Plugin API" type="GradleRunConfiguration" factoryName="Gradle">
<ExternalSystemSettings>
<option name="executionName" />
<option name="externalProjectPath" value="$PROJECT_DIR$" />
<option name="externalSystemIdString" value="GRADLE" />
<option name="scriptParameters" value="--no-configuration-cache" />
<option name="taskDescriptions">
<list />
</option>
<option name="taskNames">
<list>
<option value=":plugin-api:clean" />
<option value=":plugin-api:publishAndReleaseToMavenCentral" />
</list>
</option>
<option name="vmOptions" />
</ExternalSystemSettings>
<ExternalSystemDebugServerProcess>true</ExternalSystemDebugServerProcess>
<ExternalSystemReattachDebugProcess>true</ExternalSystemReattachDebugProcess>
<DebugAllEnabled>false</DebugAllEnabled>
<RunAsTest>false</RunAsTest>
<method v="2" />
</configuration>
</component>

View File

@ -1,23 +1,49 @@
import com.vanniktech.maven.publish.SonatypeHost
plugins {
kotlin("jvm")
`maven-publish`
`java-library`
id("com.vanniktech.maven.publish") version "0.32.0"
}
group = "org.gameyfin"
repositories {
mavenCentral()
}
publishing {
publications {
create<MavenPublication>("maven") {
from(components["java"])
}
}
}
dependencies {
// PF4J (shared)
api("org.pf4j:pf4j:${rootProject.extra["pf4jVersion"]}")
}
mavenPublishing {
publishToMavenCentral(SonatypeHost.CENTRAL_PORTAL)
signAllPublications()
coordinates(project.group.toString(), project.name, project.version.toString())
pom {
name = "Gameyfin Plugin API"
description =
"The Gameyfin Plugin API provides the necessary interfaces and classes to create plugins for Gameyfin."
url = "https://gameyfin.org/"
licenses {
license {
name = "GNU Affero General Public License v3.0"
url = "https://www.gnu.org/licenses/agpl-3.0.en.html"
}
}
developers {
developer {
id = "grimsi"
name = "Simon Grimme"
url = "https://github.com/grimsi"
}
}
scm {
url = "https://github.com/gameyfin/gameyfin"
connection = "scm:git:git://github.com/gameyfin/gameyfin.git"
developerConnection = "scm:git:ssh://git@github.com/gameyfin/gameyfin.git"
}
}
}

View File

@ -2,8 +2,24 @@ package org.gameyfin.pluginapi.core.config
import java.io.Serializable
/**
* Alias for a list of ConfigMetadata objects for plugin configuration.
*/
typealias PluginConfigMetadata = List<ConfigMetadata<*>>
/**
* Represents metadata for a configuration property.
*
* @param T The type of the configuration value, must be Serializable.
* @property key The unique (in the scope of the plugin) key for the configuration property.
* @property type The class type of the configuration value.
* @property label A human-readable label for the configuration property.
* @property description A short description of the configuration property.
* @property default The default value for the configuration property, if any.
* @property isSecret Whether the configuration value is secret (e.g., password). Affects how the value is displayed in the UI.
* @property isRequired Whether the configuration property is required.
* @property allowedValues The allowed values for the configuration property, if applicable (e.g., for enums). Will be populated automatically if the type is an enum.
*/
data class ConfigMetadata<T : Serializable>(
val key: String,
val type: Class<T>,
@ -13,6 +29,9 @@ data class ConfigMetadata<T : Serializable>(
val isSecret: Boolean = false,
val isRequired: Boolean = true,
) {
/**
* List of allowed values for the configuration property, if the type is an enum.
*/
var allowedValues: List<T>? = null
init {

View File

@ -2,14 +2,52 @@ package org.gameyfin.pluginapi.core.config
import java.io.Serializable
/**
* Interface for classes that can be configured using plugin configuration metadata.
* Provides methods for loading, validating, and accessing configuration values.
*/
interface Configurable {
/**
* The metadata describing the configuration options for this configurable instance.
*/
val configMetadata: PluginConfigMetadata
/**
* Loads configuration values from the provided map.
*
* @param config A map of configuration keys to their string values (values are nullable).
*/
fun loadConfig(config: Map<String, String?>)
/**
* Validates the current configuration state.
*
* @return The result of the configuration validation.
*/
fun validateConfig(): PluginConfigValidationResult
/**
* Validates the provided configuration map.
*
* @param config A map of configuration keys to their string values (nullable).
* @return The result of the configuration validation.
*/
fun validateConfig(config: Map<String, String?>): PluginConfigValidationResult
/**
* Retrieves a required configuration value by key.
*
* @param key The configuration key.
* @return The configuration value of type T.
* @throws Exception if the key is missing or the value is invalid.
*/
fun <T : Serializable> config(key: String): T
/**
* Retrieves an optional configuration value by key.
*
* @param key The configuration key.
* @return The configuration value of type T, or null if not present.
*/
fun <T : Serializable> optionalConfig(key: String): T?
}

View File

@ -1,3 +1,11 @@
package org.gameyfin.pluginapi.core.config
/**
* Exception thrown when there is an error in plugin configuration.
*
* This exception is used to indicate problems such as missing, invalid, or malformed configuration values
* when loading or validating plugin configuration.
*
* @param message The detail message describing the configuration error.
*/
class PluginConfigError(message: String) : RuntimeException(message)

View File

@ -1,22 +1,50 @@
package org.gameyfin.pluginapi.core.config
/**
* Represents the result of plugin configuration validation.
*
* @property result The type of validation result (VALID, INVALID, UNKNOWN).
* @property errors A map of configuration keys to error messages, present if validation failed.
*/
data class PluginConfigValidationResult(
val result: PluginConfigValidationResultType,
val errors: Map<String, String>? = null
) {
companion object {
/**
* A valid configuration result with no errors.
*/
val VALID = PluginConfigValidationResult(PluginConfigValidationResultType.VALID)
/**
* An unknown configuration validation result.
*/
val UNKNOWN = PluginConfigValidationResult(PluginConfigValidationResultType.UNKNWOWN)
/**
* Creates an invalid configuration result with the specified errors.
*
* @param errors A map of configuration keys to error messages.
* @return An invalid PluginConfigValidationResult instance.
*/
fun INVALID(errors: Map<String, String>): PluginConfigValidationResult {
return PluginConfigValidationResult(PluginConfigValidationResultType.INVALID, errors)
}
}
/**
* Checks if the configuration is valid.
*
* @return True if the result is VALID, false otherwise.
*/
fun isValid(): Boolean {
return result == PluginConfigValidationResultType.VALID
}
}
/**
* Enum representing the possible types of plugin configuration validation results.
*/
enum class PluginConfigValidationResultType {
VALID,
INVALID,

View File

@ -7,17 +7,47 @@ import org.gameyfin.pluginapi.core.config.PluginConfigValidationResult
import org.pf4j.PluginWrapper
import java.io.Serializable
/**
* Abstract base class for Gameyfin plugins that support configuration.
*
* This class implements the [Configurable] interface and provides default logic for loading,
* validating, and accessing plugin configuration values using metadata.
*
* @constructor Creates a configurable Gameyfin plugin with the given [PluginWrapper].
* @param wrapper The plugin wrapper provided by the Gameyfin application.
*/
@Suppress("UNCHECKED_CAST")
abstract class ConfigurableGameyfinPlugin(wrapper: PluginWrapper) : GameyfinPlugin(wrapper), Configurable {
/**
* The current configuration map, where keys are configuration property names and values are their string representations.
*/
private var config: Map<String, String?> = emptyMap()
/**
* Loads configuration values from the provided map.
*
* @param config A map of configuration keys to their string values (nullable).
*/
override fun loadConfig(config: Map<String, String?>) {
this.config = config
}
/**
* Validates the current configuration state.
*
* @return The result of the configuration validation.
*/
override fun validateConfig(): PluginConfigValidationResult = validateConfig(config)
/**
* Validates the provided configuration map.
* The validation covers basic checks such as required fields, type casting, and allowed values.
* It's recommended to override this method in subclasses to implement additional validation logic specific to the plugin.
*
* @param config A map of configuration keys to their string values (nullable).
* @return The result of the configuration validation.
*/
override fun validateConfig(config: Map<String, String?>): PluginConfigValidationResult {
val errors = mutableMapOf<String, String>()
@ -43,6 +73,13 @@ abstract class ConfigurableGameyfinPlugin(wrapper: PluginWrapper) : GameyfinPlug
}
}
/**
* Retrieves an optional configuration value by key.
*
* @param key The configuration key.
* @return The configuration value of type T, or null if not present or invalid.
* @throws PluginConfigError if the value cannot be cast to the expected type.
*/
override fun <T : Serializable> optionalConfig(key: String): T? {
val meta = resolveMetadata(key)
val value = resolveValue(key)
@ -55,6 +92,13 @@ abstract class ConfigurableGameyfinPlugin(wrapper: PluginWrapper) : GameyfinPlug
}
}
/**
* Retrieves a required configuration value by key.
*
* @param key The configuration key.
* @return The configuration value of type T.
* @throws PluginConfigError if the key is missing or the value is invalid.
*/
override fun <T : Serializable> config(key: String): T {
val value = optionalConfig<T>(key)
if (value == null) {
@ -63,6 +107,16 @@ abstract class ConfigurableGameyfinPlugin(wrapper: PluginWrapper) : GameyfinPlug
return value
}
/**
* Casts a configuration value to the expected type defined in the metadata.
*
* Handles enums, common primitive types, and attempts to use valueOf/parse methods via reflection for custom types.
*
* @param meta The configuration metadata describing the expected type.
* @param value The value to cast.
* @return The cast value, or throws PluginConfigError if casting fails.
* @throws PluginConfigError if the value cannot be cast to the expected type.
*/
private fun castConfigValue(meta: ConfigMetadata<*>, value: Any): Any? {
val expectedType = meta.type
@ -106,11 +160,25 @@ abstract class ConfigurableGameyfinPlugin(wrapper: PluginWrapper) : GameyfinPlug
}
}
/**
* Resolves the configuration metadata for a given key.
*
* @param key The configuration key to look up.
* @return The corresponding ConfigMetadata instance.
* @throws PluginConfigError if the key is unknown.
*/
private fun resolveMetadata(key: String): ConfigMetadata<*> {
return configMetadata.find { it.key == key }
?: throw PluginConfigError("Unknown configuration key: $key")
}
/**
* Resolves the value for a configuration key, optionally using an override map.
*
* @param key The configuration key to resolve.
* @param configOverride An optional map to override the current configuration.
* @return The resolved value, or the default from metadata if not present.
*/
private fun resolveValue(key: String, configOverride: Map<String, Serializable?>? = null): Serializable? {
val meta = resolveMetadata(key)
val conf = configOverride ?: config

View File

@ -3,21 +3,48 @@ package org.gameyfin.pluginapi.core.wrapper
import org.pf4j.Plugin
import org.pf4j.PluginWrapper
/**
* Abstract base class for all Gameyfin plugins.
*
* This class extends the PF4J [Plugin] class and provides utility methods for plugin logo management.
* It also maintains a static reference to the current plugin instance.
*
* @constructor Creates a Gameyfin plugin with the given [PluginWrapper].
* @param wrapper The plugin wrapper provided by the Gameyfin application.
*/
@Suppress("DEPRECATION")
abstract class GameyfinPlugin(wrapper: PluginWrapper) : Plugin(wrapper) {
companion object {
/**
* The base name of the logo file (without extension).
*/
const val LOGO_FILE_NAME: String = "logo"
/**
* Supported logo file formats.
*/
val SUPPORTED_LOGO_FORMATS: List<String> = listOf("png", "jpg", "jpeg", "gif", "svg", "webp")
/**
* Reference to the current plugin instance.
*/
lateinit var plugin: GameyfinPlugin
private set
}
/**
* Initializes the plugin and sets the static plugin reference.
*/
init {
plugin = this
}
/**
* Checks if the plugin contains a logo file in any supported format.
*
* @return True if a logo file is found, false otherwise.
*/
fun hasLogo(): Boolean {
for (format in SUPPORTED_LOGO_FORMATS) {
val resourcePath = "$LOGO_FILE_NAME.$format"
@ -30,6 +57,11 @@ abstract class GameyfinPlugin(wrapper: PluginWrapper) : Plugin(wrapper) {
return false
}
/**
* Retrieves the logo file as a byte array, if present.
*
* @return The logo file as a byte array, or null if not found.
*/
fun getLogo(): ByteArray? {
for (format in SUPPORTED_LOGO_FORMATS) {
val resourcePath = "$LOGO_FILE_NAME.$format"

View File

@ -2,15 +2,29 @@ package org.gameyfin.pluginapi.download
import java.io.InputStream
/**
* Represents a downloadable resource, which can be either a file or a link.
*/
sealed interface Download
/**
* Represents a file-based download.
*
* @property data The input stream containing the file data.
* @property fileExtension The file extension (e.g., "zip", "png"), or null if none.
* @property size The size of the file in bytes, or null if unknown.
*/
data class FileDownload(
val data: InputStream,
val fileExtension: String? = null,
val size: Long? = null
) : Download
/**
* Represents a link-based download.
*
* @property url The URL to the downloadable resource.
*/
data class LinkDownload(
val url: String
) : Download

View File

@ -3,7 +3,19 @@ package org.gameyfin.pluginapi.download
import org.pf4j.ExtensionPoint
import java.nio.file.Path
/**
* Extension point for providing downloadable resources.
*
* Implementations of this interface are responsible for handling download requests for specific paths.
* This is typically used to allow plugins to serve files or links for download.
*/
interface DownloadProvider : ExtensionPoint {
/**
* Downloads a resource for the given path.
*
* @param path The path to the resource to download.
* @return A [Download] representing the downloadable resource (file or link).
*/
fun download(path: Path): Download
}

View File

@ -3,6 +3,26 @@ package org.gameyfin.pluginapi.gamemetadata
import java.net.URI
import java.time.Instant
/**
* Represents metadata for a game, including identifiers, descriptive information, media, ratings, and categorization.
*
* @property originalId The unique identifier for the game from the original source.
* @property title The title of the game.
* @property description A description of the game, or null if not available.
* @property coverUrl The URI to the game's cover image, or null if not available.
* @property release The release date and time of the game, or null if not available.
* @property userRating The user rating for the game, or null if not available.
* @property criticRating The critic rating for the game, or null if not available.
* @property developedBy The set of developer names, or null if not available.
* @property publishedBy The set of publisher names, or null if not available.
* @property genres The set of genres associated with the game, or null if not available.
* @property themes The set of themes associated with the game, or null if not available.
* @property keywords The set of keywords associated with the game, or null if not available.
* @property screenshotUrls The set of URIs to screenshots, or null if not available.
* @property videoUrls The set of URIs to videos, or null if not available.
* @property features The set of features associated with the game, or null if not available.
* @property perspectives The set of player perspectives, or null if not available.
*/
data class GameMetadata(
val originalId: String,
val title: String,
@ -22,6 +42,9 @@ data class GameMetadata(
val perspectives: Set<PlayerPerspective>? = null
)
/**
* Enum representing the genre of a game.
*/
enum class Genre {
UNKNOWN,
ACTION,
@ -51,6 +74,9 @@ enum class Genre {
QUIZ_TRIVIA
}
/**
* Enum representing the theme of a game.
*/
enum class Theme {
UNKNOWN,
ACTION,

View File

@ -2,8 +2,27 @@ package org.gameyfin.pluginapi.gamemetadata
import org.pf4j.ExtensionPoint
/**
* Extension point for providing game metadata.
*
* Implementations of this interface are responsible for fetching game metadata by title or by unique identifier.
* This is typically used to allow plugins to provide metadata for games from various sources.
*/
interface GameMetadataProvider : ExtensionPoint {
/**
* Fetches a list of game metadata entries matching the given game title.
*
* @param gameTitle The title of the game to search for.
* @param maxResults The maximum number of results to return. Defaults to 1.
* @return A list of [GameMetadata] objects matching the title, or an empty list if none found.
*/
fun fetchByTitle(gameTitle: String, maxResults: Int = 1): List<GameMetadata>
/**
* Fetches game metadata by its unique identifier.
*
* @param id The unique identifier of the game.
* @return The [GameMetadata] for the given id, or null if not found.
*/
fun fetchById(id: String): GameMetadata?
}