mirror of
https://github.com/BrenBroZAYT/gameyfin.git
synced 2026-06-17 16:20:03 +00:00
Verify plugin config before starting plugin
Fix issue with SLF4J in plugins
This commit is contained in:
@@ -39,6 +39,7 @@ export default function PluginManagement() {
|
|||||||
<div className="flex flex-row flex-grow justify-between my-8">
|
<div className="flex flex-row flex-grow justify-between my-8">
|
||||||
<h2 className="text-xl font-bold">Notifications</h2>
|
<h2 className="text-xl font-bold">Notifications</h2>
|
||||||
</div>
|
</div>
|
||||||
|
<p>Notification plugins not yet supported.</p>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -2,7 +2,7 @@ import React, {useEffect, useState} from "react";
|
|||||||
import {Button, Modal, ModalBody, ModalContent, ModalFooter, ModalHeader} from "@nextui-org/react";
|
import {Button, Modal, ModalBody, ModalContent, ModalFooter, ModalHeader} from "@nextui-org/react";
|
||||||
import {toast} from "sonner";
|
import {toast} from "sonner";
|
||||||
import {Form, Formik} from "formik";
|
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 PluginDto from "Frontend/generated/de/grimsi/gameyfin/core/plugins/management/PluginDto";
|
||||||
import PluginConfigElement from "Frontend/generated/de/grimsi/gameyfin/pluginapi/core/PluginConfigElement";
|
import PluginConfigElement from "Frontend/generated/de/grimsi/gameyfin/pluginapi/core/PluginConfigElement";
|
||||||
import Input from "Frontend/components/general/Input";
|
import Input from "Frontend/components/general/Input";
|
||||||
@@ -12,9 +12,10 @@ interface PluginDetailsModalProps {
|
|||||||
plugin: PluginDto;
|
plugin: PluginDto;
|
||||||
isOpen: boolean;
|
isOpen: boolean;
|
||||||
onOpenChange: () => void;
|
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 [pluginConfigMeta, setPluginConfigMeta] = useState<(PluginConfigElement)[]>();
|
||||||
const [pluginConfig, setPluginConfig] = useState<Record<string, string>>();
|
const [pluginConfig, setPluginConfig] = useState<Record<string, string>>();
|
||||||
|
|
||||||
@@ -32,6 +33,9 @@ export default function PluginDetailsModal({plugin, isOpen, onOpenChange}: Plugi
|
|||||||
async function saveConfig(values: Record<string, string>) {
|
async function saveConfig(values: Record<string, string>) {
|
||||||
await PluginConfigEndpoint.setConfigEntries(plugin.id, values);
|
await PluginConfigEndpoint.setConfigEntries(plugin.id, values);
|
||||||
toast.success(`Configuration for ${plugin.name} saved!`);
|
toast.success(`Configuration for ${plugin.name} saved!`);
|
||||||
|
let updatedPlugin = await PluginManagementEndpoint.getPlugin(plugin.id);
|
||||||
|
if (updatedPlugin === undefined) return;
|
||||||
|
updatePlugin(updatedPlugin);
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|||||||
@@ -18,11 +18,7 @@ export function PluginManagementCard({plugin, updatePlugin}: {
|
|||||||
if (response === undefined) return;
|
if (response === undefined) return;
|
||||||
setConfigValid(response);
|
setConfigValid(response);
|
||||||
});
|
});
|
||||||
}, []);
|
}, [pluginDetailsModal.isOpen]);
|
||||||
|
|
||||||
function iconColor(state: PluginState | undefined): "white" | "default" {
|
|
||||||
return "default";
|
|
||||||
}
|
|
||||||
|
|
||||||
function borderColor(state: PluginState | undefined): "success" | "warning" | "danger" | "default" {
|
function borderColor(state: PluginState | undefined): "success" | "warning" | "danger" | "default" {
|
||||||
if (isDisabled(state)) return "warning";
|
if (isDisabled(state)) return "warning";
|
||||||
@@ -37,7 +33,7 @@ export function PluginManagementCard({plugin, updatePlugin}: {
|
|||||||
return "success";
|
return "success";
|
||||||
case PluginState.DISABLED:
|
case PluginState.DISABLED:
|
||||||
return "warning";
|
return "warning";
|
||||||
case PluginState.STOPPED:
|
case PluginState.FAILED:
|
||||||
return "danger";
|
return "danger";
|
||||||
default:
|
default:
|
||||||
return "default";
|
return "default";
|
||||||
@@ -101,6 +97,7 @@ export function PluginManagementCard({plugin, updatePlugin}: {
|
|||||||
<PluginDetailsModal plugin={plugin}
|
<PluginDetailsModal plugin={plugin}
|
||||||
isOpen={pluginDetailsModal.isOpen}
|
isOpen={pluginDetailsModal.isOpen}
|
||||||
onOpenChange={pluginDetailsModal.onOpenChange}
|
onOpenChange={pluginDetailsModal.onOpenChange}
|
||||||
|
updatePlugin={updatePlugin}
|
||||||
/>
|
/>
|
||||||
</>
|
</>
|
||||||
|
|
||||||
|
|||||||
+8
-2
@@ -10,8 +10,14 @@ class DatabasePluginStatusProvider(
|
|||||||
) : PluginStatusProvider {
|
) : PluginStatusProvider {
|
||||||
|
|
||||||
override fun isPluginDisabled(pluginId: String): Boolean {
|
override fun isPluginDisabled(pluginId: String): Boolean {
|
||||||
val pluginManagement = pluginManagementRepository.findByIdOrNull(pluginId)
|
var pluginManagement = pluginManagementRepository.findByIdOrNull(pluginId)
|
||||||
return pluginManagement?.enabled != true
|
|
||||||
|
// 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) {
|
override fun disablePlugin(pluginId: String) {
|
||||||
|
|||||||
+53
-2
@@ -63,11 +63,62 @@ class GameyfinPluginManager(
|
|||||||
return pluginWrapper
|
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) {
|
fun restart(pluginId: String) {
|
||||||
val plugin = getPlugin(pluginId)?.plugin ?: return
|
val plugin = getPlugin(pluginId)?.plugin ?: return
|
||||||
plugin.stop()
|
stopPlugin(pluginId)
|
||||||
(plugin as GameyfinPlugin).loadConfig(getConfig(pluginId))
|
(plugin as GameyfinPlugin).loadConfig(getConfig(pluginId))
|
||||||
plugin.start()
|
startPlugin(pluginId)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun validatePluginConfig(pluginId: String): Boolean {
|
fun validatePluginConfig(pluginId: String): Boolean {
|
||||||
|
|||||||
@@ -10,11 +10,7 @@ repositories {
|
|||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
// PF4J (shared)
|
// PF4J (shared)
|
||||||
api("org.pf4j:pf4j:${rootProject.extra["pf4jVersion"]}") {
|
api("org.pf4j:pf4j:${rootProject.extra["pf4jVersion"]}")
|
||||||
exclude(group = "org.slf4j")
|
|
||||||
}
|
|
||||||
|
|
||||||
api("org.slf4j:slf4j-api:2.0.16")
|
|
||||||
|
|
||||||
implementation(kotlin("stdlib"))
|
implementation(kotlin("stdlib"))
|
||||||
|
|
||||||
|
|||||||
@@ -16,6 +16,8 @@ import kotlinx.serialization.json.*
|
|||||||
import me.xdrop.fuzzywuzzy.FuzzySearch
|
import me.xdrop.fuzzywuzzy.FuzzySearch
|
||||||
import org.pf4j.Extension
|
import org.pf4j.Extension
|
||||||
import org.pf4j.PluginWrapper
|
import org.pf4j.PluginWrapper
|
||||||
|
import org.slf4j.Logger
|
||||||
|
import org.slf4j.LoggerFactory
|
||||||
import java.net.URLEncoder
|
import java.net.URLEncoder
|
||||||
import java.nio.charset.StandardCharsets
|
import java.nio.charset.StandardCharsets
|
||||||
import java.time.Instant
|
import java.time.Instant
|
||||||
@@ -66,6 +68,8 @@ class SteamPlugin(wrapper: PluginWrapper) : GameyfinPlugin(wrapper) {
|
|||||||
|
|
||||||
@Extension
|
@Extension
|
||||||
class SteamMetadataProvider : GameMetadataProvider {
|
class SteamMetadataProvider : GameMetadataProvider {
|
||||||
|
val log: Logger = LoggerFactory.getLogger(javaClass)
|
||||||
|
|
||||||
val client = HttpClient(CIO) {
|
val client = HttpClient(CIO) {
|
||||||
install(ContentNegotiation) {
|
install(ContentNegotiation) {
|
||||||
json()
|
json()
|
||||||
@@ -89,7 +93,7 @@ class SteamPlugin(wrapper: PluginWrapper) : GameyfinPlugin(wrapper) {
|
|||||||
val searchResult: SteamSearchResult = client.get(url).body()
|
val searchResult: SteamSearchResult = client.get(url).body()
|
||||||
searchResult.items
|
searchResult.items
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
println(e.message)
|
log.error("Failed to search Steam store: ${e.message}")
|
||||||
emptyList()
|
emptyList()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user