diff --git a/gameyfin/src/main/frontend/components/administration/PluginManagement.tsx b/gameyfin/src/main/frontend/components/administration/PluginManagement.tsx
index 2abf2c1..fba1bda 100644
--- a/gameyfin/src/main/frontend/components/administration/PluginManagement.tsx
+++ b/gameyfin/src/main/frontend/components/administration/PluginManagement.tsx
@@ -39,6 +39,7 @@ export default function PluginManagement() {
Notifications
+ Notification plugins not yet supported.
);
}
\ No newline at end of file
diff --git a/gameyfin/src/main/frontend/components/general/PluginDetailsModal.tsx b/gameyfin/src/main/frontend/components/general/PluginDetailsModal.tsx
index 51882c9..94584e8 100644
--- a/gameyfin/src/main/frontend/components/general/PluginDetailsModal.tsx
+++ b/gameyfin/src/main/frontend/components/general/PluginDetailsModal.tsx
@@ -2,7 +2,7 @@ import React, {useEffect, useState} from "react";
import {Button, Modal, ModalBody, ModalContent, ModalFooter, ModalHeader} from "@nextui-org/react";
import {toast} from "sonner";
import {Form, Formik} from "formik";
-import {PluginConfigEndpoint} from "Frontend/generated/endpoints";
+import {PluginConfigEndpoint, PluginManagementEndpoint} from "Frontend/generated/endpoints";
import PluginDto from "Frontend/generated/de/grimsi/gameyfin/core/plugins/management/PluginDto";
import PluginConfigElement from "Frontend/generated/de/grimsi/gameyfin/pluginapi/core/PluginConfigElement";
import Input from "Frontend/components/general/Input";
@@ -12,9 +12,10 @@ interface PluginDetailsModalProps {
plugin: PluginDto;
isOpen: boolean;
onOpenChange: () => void;
+ updatePlugin: (plugin: PluginDto) => void;
}
-export default function PluginDetailsModal({plugin, isOpen, onOpenChange}: PluginDetailsModalProps) {
+export default function PluginDetailsModal({plugin, isOpen, onOpenChange, updatePlugin}: PluginDetailsModalProps) {
const [pluginConfigMeta, setPluginConfigMeta] = useState<(PluginConfigElement)[]>();
const [pluginConfig, setPluginConfig] = useState>();
@@ -32,6 +33,9 @@ export default function PluginDetailsModal({plugin, isOpen, onOpenChange}: Plugi
async function saveConfig(values: Record) {
await PluginConfigEndpoint.setConfigEntries(plugin.id, values);
toast.success(`Configuration for ${plugin.name} saved!`);
+ let updatedPlugin = await PluginManagementEndpoint.getPlugin(plugin.id);
+ if (updatedPlugin === undefined) return;
+ updatePlugin(updatedPlugin);
}
return (
diff --git a/gameyfin/src/main/frontend/components/general/PluginManagementCard.tsx b/gameyfin/src/main/frontend/components/general/PluginManagementCard.tsx
index 59400ce..b8eb2ef 100644
--- a/gameyfin/src/main/frontend/components/general/PluginManagementCard.tsx
+++ b/gameyfin/src/main/frontend/components/general/PluginManagementCard.tsx
@@ -18,11 +18,7 @@ export function PluginManagementCard({plugin, updatePlugin}: {
if (response === undefined) return;
setConfigValid(response);
});
- }, []);
-
- function iconColor(state: PluginState | undefined): "white" | "default" {
- return "default";
- }
+ }, [pluginDetailsModal.isOpen]);
function borderColor(state: PluginState | undefined): "success" | "warning" | "danger" | "default" {
if (isDisabled(state)) return "warning";
@@ -37,7 +33,7 @@ export function PluginManagementCard({plugin, updatePlugin}: {
return "success";
case PluginState.DISABLED:
return "warning";
- case PluginState.STOPPED:
+ case PluginState.FAILED:
return "danger";
default:
return "default";
@@ -101,6 +97,7 @@ export function PluginManagementCard({plugin, updatePlugin}: {
>
diff --git a/gameyfin/src/main/kotlin/de/grimsi/gameyfin/core/plugins/management/DatabasePluginStatusProvider.kt b/gameyfin/src/main/kotlin/de/grimsi/gameyfin/core/plugins/management/DatabasePluginStatusProvider.kt
index 9f974e4..f597ef4 100644
--- a/gameyfin/src/main/kotlin/de/grimsi/gameyfin/core/plugins/management/DatabasePluginStatusProvider.kt
+++ b/gameyfin/src/main/kotlin/de/grimsi/gameyfin/core/plugins/management/DatabasePluginStatusProvider.kt
@@ -10,8 +10,14 @@ class DatabasePluginStatusProvider(
) : PluginStatusProvider {
override fun isPluginDisabled(pluginId: String): Boolean {
- val pluginManagement = pluginManagementRepository.findByIdOrNull(pluginId)
- return pluginManagement?.enabled != true
+ var pluginManagement = pluginManagementRepository.findByIdOrNull(pluginId)
+
+ // If the plugin is unknown, persist it as enabled
+ if(pluginManagement == null) {
+ pluginManagement = pluginManagementRepository.save(PluginManagementEntry(pluginId = pluginId, enabled = true))
+ }
+
+ return pluginManagement.enabled != true
}
override fun disablePlugin(pluginId: String) {
diff --git a/gameyfin/src/main/kotlin/de/grimsi/gameyfin/core/plugins/management/GameyfinPluginManager.kt b/gameyfin/src/main/kotlin/de/grimsi/gameyfin/core/plugins/management/GameyfinPluginManager.kt
index 2109405..a57906c 100644
--- a/gameyfin/src/main/kotlin/de/grimsi/gameyfin/core/plugins/management/GameyfinPluginManager.kt
+++ b/gameyfin/src/main/kotlin/de/grimsi/gameyfin/core/plugins/management/GameyfinPluginManager.kt
@@ -63,11 +63,62 @@ class GameyfinPluginManager(
return pluginWrapper
}
+ override fun startPlugin(pluginId: String?): PluginState? {
+ if(pluginId == null) return PluginState.FAILED
+
+ // Validate config before starting the plugin
+ if (!validatePluginConfig(pluginId)) {
+ log.error { "Plugin $pluginId has invalid configuration" }
+
+ val pluginWrapper = getPlugin(pluginId)
+ pluginWrapper.pluginState = PluginState.FAILED
+ this.firePluginStateEvent(PluginStateEvent(this, pluginWrapper, pluginWrapper.pluginState));
+ return pluginWrapper.pluginState
+ }
+
+ return super.startPlugin(pluginId)
+ }
+
+ override fun startPlugins() {
+ for (pluginWrapper in resolvedPlugins) {
+ val pluginState = pluginWrapper.pluginState
+ if (!pluginState.isDisabled && !pluginState.isStarted) {
+
+ // Validate config before starting the plugin
+ if (!validatePluginConfig(pluginWrapper.pluginId)) {
+ log.error { "Plugin ${pluginWrapper.pluginId} has invalid configuration" }
+ pluginWrapper.pluginState = PluginState.FAILED
+
+ firePluginStateEvent(PluginStateEvent(this, pluginWrapper, pluginState))
+ return
+ }
+
+ try {
+ log.info { "${"Start plugin '{}'"} ${getPluginLabel(pluginWrapper.descriptor)}"}
+ pluginWrapper.plugin.start()
+ pluginWrapper.pluginState = PluginState.STARTED
+ pluginWrapper.failedException = null
+ startedPlugins.add(pluginWrapper)
+ } catch (e: LinkageError) {
+ pluginWrapper.pluginState = PluginState.FAILED
+ pluginWrapper.failedException = e
+ log.error { "${"Unable to start plugin '{}'"} ${getPluginLabel(pluginWrapper.descriptor)} $e"}
+ } catch (e: Exception) {
+ pluginWrapper.pluginState = PluginState.FAILED
+ pluginWrapper.failedException = e
+ log.error { "${"Unable to start plugin '{}'"} ${getPluginLabel(pluginWrapper.descriptor)} $e"}
+ } finally {
+ firePluginStateEvent(PluginStateEvent(this, pluginWrapper, pluginState))
+ }
+ }
+ }
+ }
+
fun restart(pluginId: String) {
val plugin = getPlugin(pluginId)?.plugin ?: return
- plugin.stop()
+ stopPlugin(pluginId)
(plugin as GameyfinPlugin).loadConfig(getConfig(pluginId))
- plugin.start()
+ startPlugin(pluginId)
}
fun validatePluginConfig(pluginId: String): Boolean {
diff --git a/plugin-api/build.gradle.kts b/plugin-api/build.gradle.kts
index 980066a..48a1835 100644
--- a/plugin-api/build.gradle.kts
+++ b/plugin-api/build.gradle.kts
@@ -10,11 +10,7 @@ repositories {
dependencies {
// PF4J (shared)
- api("org.pf4j:pf4j:${rootProject.extra["pf4jVersion"]}") {
- exclude(group = "org.slf4j")
- }
-
- api("org.slf4j:slf4j-api:2.0.16")
+ api("org.pf4j:pf4j:${rootProject.extra["pf4jVersion"]}")
implementation(kotlin("stdlib"))
diff --git a/plugins/steam/src/main/kotlin/de/grimsi/gameyfin/plugins/steam/SteamPlugin.kt b/plugins/steam/src/main/kotlin/de/grimsi/gameyfin/plugins/steam/SteamPlugin.kt
index 4e2554c..99eab6b 100644
--- a/plugins/steam/src/main/kotlin/de/grimsi/gameyfin/plugins/steam/SteamPlugin.kt
+++ b/plugins/steam/src/main/kotlin/de/grimsi/gameyfin/plugins/steam/SteamPlugin.kt
@@ -16,6 +16,8 @@ import kotlinx.serialization.json.*
import me.xdrop.fuzzywuzzy.FuzzySearch
import org.pf4j.Extension
import org.pf4j.PluginWrapper
+import org.slf4j.Logger
+import org.slf4j.LoggerFactory
import java.net.URLEncoder
import java.nio.charset.StandardCharsets
import java.time.Instant
@@ -66,6 +68,8 @@ class SteamPlugin(wrapper: PluginWrapper) : GameyfinPlugin(wrapper) {
@Extension
class SteamMetadataProvider : GameMetadataProvider {
+ val log: Logger = LoggerFactory.getLogger(javaClass)
+
val client = HttpClient(CIO) {
install(ContentNegotiation) {
json()
@@ -89,7 +93,7 @@ class SteamPlugin(wrapper: PluginWrapper) : GameyfinPlugin(wrapper) {
val searchResult: SteamSearchResult = client.get(url).body()
searchResult.items
} catch (e: Exception) {
- println(e.message)
+ log.error("Failed to search Steam store: ${e.message}")
emptyList()
}
}