mirror of
https://github.com/BrenBroZAYT/gameyfin.git
synced 2026-06-13 16:40:01 +00:00
Refactor and extend the plugin MANIFEST.MF parser
Redesign the PluginDetailsModal
This commit is contained in:
Generated
+1233
-3
File diff suppressed because it is too large
Load Diff
@@ -43,8 +43,10 @@
|
||||
"react-aria-components": "^1.7.1",
|
||||
"react-confetti-boom": "^1.0.0",
|
||||
"react-dom": "18.3.1",
|
||||
"react-markdown": "^10.1.0",
|
||||
"react-player": "^2.16.0",
|
||||
"react-router": "7.5.2",
|
||||
"remark-breaks": "^4.0.0",
|
||||
"swiper": "^11.2.6",
|
||||
"yup": "^1.6.1"
|
||||
},
|
||||
@@ -132,7 +134,9 @@
|
||||
"rand-seed": "$rand-seed",
|
||||
"react-router": "$react-router",
|
||||
"swiper": "$swiper",
|
||||
"react-player": "$react-player"
|
||||
"react-player": "$react-player",
|
||||
"react-markdown": "$react-markdown",
|
||||
"remark-breaks": "$remark-breaks"
|
||||
},
|
||||
"vaadin": {
|
||||
"dependencies": {
|
||||
@@ -193,6 +197,6 @@
|
||||
"workbox-core": "7.3.0",
|
||||
"workbox-precaching": "7.3.0"
|
||||
},
|
||||
"hash": "b7f2b9b343406ec77ce90fe42104642df3b7205f522d2cc60db71051097a32de"
|
||||
"hash": "f66bddf01ef8dd09a431102050ddd561b4587fdd43bcbff127b93872febbee92"
|
||||
}
|
||||
}
|
||||
@@ -1,11 +1,13 @@
|
||||
import React, {useEffect, useState} from "react";
|
||||
import {addToast, Button, Modal, ModalBody, ModalContent, ModalFooter, ModalHeader} from "@heroui/react";
|
||||
import {addToast, Button, Link, Modal, ModalBody, ModalContent, ModalFooter, ModalHeader} from "@heroui/react";
|
||||
import {Form, Formik} from "formik";
|
||||
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/Input";
|
||||
import PluginLogo from "Frontend/components/general/PluginLogo";
|
||||
import Markdown from "react-markdown";
|
||||
import remarkBreaks from "remark-breaks";
|
||||
|
||||
interface PluginDetailsModalProps {
|
||||
plugin: PluginDto;
|
||||
@@ -58,18 +60,53 @@ export default function PluginDetailsModal({plugin, isOpen, onOpenChange, update
|
||||
Plugin configuration for {plugin.name}
|
||||
</ModalHeader>
|
||||
<ModalBody>
|
||||
<h4 className="text-l font-bold">Details</h4>
|
||||
<div className="flex flex-row gap-8">
|
||||
<PluginLogo plugin={plugin}/>
|
||||
<div className="grid grid-cols-2">
|
||||
<p>Author: {plugin.author}</p>
|
||||
<p>Version: {plugin.version}</p>
|
||||
<p>Plugin ID: {plugin.id}</p>
|
||||
<p>Status: {plugin.state?.toLowerCase()}</p>
|
||||
<div className="flex flex-col text-sm">
|
||||
<div className="flex flex-row items-center gap-8 mb-4">
|
||||
<PluginLogo plugin={plugin}/>
|
||||
<table className="text-left table-auto">
|
||||
<tbody>
|
||||
{Object.entries({
|
||||
"Author": plugin.author,
|
||||
"Version": plugin.version,
|
||||
"License": plugin.license,
|
||||
"URL": <Link isExternal
|
||||
showAnchorIcon
|
||||
color="foreground"
|
||||
size="sm"
|
||||
href={plugin.url}>
|
||||
{plugin.url}
|
||||
</Link>,
|
||||
}).map(([key, value]) => {
|
||||
if (!value) return;
|
||||
return (
|
||||
<tr key={key}>
|
||||
<td className="text-foreground/60 w-0 min-w-20">{key}</td>
|
||||
<td className="flex flex-row gap-1">{value}</td>
|
||||
</tr>
|
||||
)
|
||||
})}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<p className="text-foreground/60">Description</p>
|
||||
<Markdown
|
||||
remarkPlugins={[remarkBreaks]}
|
||||
components={{
|
||||
a(props) {
|
||||
return <Link isExternal
|
||||
showAnchorIcon
|
||||
color="foreground"
|
||||
underline="always"
|
||||
href={props.href}
|
||||
size="sm">
|
||||
{props.children}
|
||||
</Link>
|
||||
}
|
||||
}}
|
||||
>{plugin.description}</Markdown>
|
||||
</div>
|
||||
|
||||
<h4 className="text-l font-bold mt-6">Configuration</h4>
|
||||
<h4 className="text-l font-bold mt-4">Configuration</h4>
|
||||
{(pluginConfigMeta && pluginConfigMeta.length > 0) ?
|
||||
pluginConfigMeta.map((entry: PluginConfigElement) => (
|
||||
<Input key={entry.key} name={entry.key} label={entry.name}
|
||||
|
||||
+3
-5
@@ -10,13 +10,11 @@ class DatabasePluginStatusProvider(
|
||||
) : PluginStatusProvider {
|
||||
|
||||
override fun isPluginDisabled(pluginId: String): Boolean {
|
||||
var pluginManagement = pluginManagementRepository.findByIdOrNull(pluginId)
|
||||
val pluginManagement = pluginManagementRepository.findByIdOrNull(pluginId)
|
||||
|
||||
if (pluginManagement == null) {
|
||||
return true
|
||||
}
|
||||
if (pluginManagement == null) return true
|
||||
|
||||
return pluginManagement.enabled != true
|
||||
return !pluginManagement.enabled
|
||||
}
|
||||
|
||||
override fun disablePlugin(pluginId: String) {
|
||||
|
||||
+30
@@ -0,0 +1,30 @@
|
||||
package de.grimsi.gameyfin.core.plugins.management
|
||||
|
||||
import org.pf4j.ManifestPluginDescriptorFinder
|
||||
import java.util.jar.Manifest
|
||||
|
||||
class GameyfinManifestPluginDescriptorFinder() : ManifestPluginDescriptorFinder() {
|
||||
|
||||
companion object {
|
||||
const val PLUGIN_NAME: String = "Plugin-Name"
|
||||
const val PLUGIN_AUTHOR: String = "Plugin-Author"
|
||||
const val PLUGIN_URL: String = "Plugin-Url"
|
||||
}
|
||||
|
||||
override fun createPluginDescriptor(manifest: Manifest?): GameyfinPluginDescriptor {
|
||||
if (manifest == null) throw IllegalArgumentException("Manifest cannot be null")
|
||||
|
||||
val pluginDescriptor = super.createPluginDescriptor(manifest)
|
||||
|
||||
val attributes = manifest.mainAttributes
|
||||
|
||||
return GameyfinPluginDescriptor(
|
||||
descriptor = pluginDescriptor,
|
||||
name = attributes.getValue(PLUGIN_NAME)
|
||||
?: throw IllegalStateException("Plugin-Name not found in manifest"),
|
||||
author = attributes.getValue(PLUGIN_AUTHOR)
|
||||
?: throw IllegalStateException("Plugin-Author not found in manifest"),
|
||||
url = attributes.getValue(PLUGIN_URL) ?: "",
|
||||
)
|
||||
}
|
||||
}
|
||||
+39
@@ -0,0 +1,39 @@
|
||||
package de.grimsi.gameyfin.core.plugins.management
|
||||
|
||||
import org.pf4j.DefaultPluginDescriptor
|
||||
import org.pf4j.PluginDescriptor
|
||||
|
||||
data class GameyfinPluginDescriptor(
|
||||
var pluginUrl: String,
|
||||
var pluginName: String,
|
||||
var author: String
|
||||
) : DefaultPluginDescriptor() {
|
||||
|
||||
constructor(
|
||||
descriptor: PluginDescriptor,
|
||||
url: String,
|
||||
name: String,
|
||||
author: String
|
||||
) : this(
|
||||
pluginUrl = url,
|
||||
pluginName = name,
|
||||
author = author
|
||||
) {
|
||||
this.pluginId = descriptor.pluginId
|
||||
// The Manifest parser ignores newlines in the description
|
||||
this.pluginDescription = descriptor.pluginDescription.replace("<br>", "\n")
|
||||
this.pluginClass = descriptor.pluginClass
|
||||
this.setPluginVersion(descriptor.version)
|
||||
this.requires = descriptor.requires
|
||||
this.license = descriptor.license
|
||||
|
||||
// Use reflection to access the private 'dependencies' field
|
||||
// This is because the internal (List<PluginDependency>) and external (List<String>) representation of the field differ
|
||||
this.javaClass.superclass.getDeclaredField("dependencies").let {
|
||||
it.isAccessible = true
|
||||
it.set(this, descriptor.dependencies)
|
||||
}
|
||||
}
|
||||
|
||||
override fun getProvider() = author
|
||||
}
|
||||
+10
-1
@@ -62,8 +62,17 @@ class GameyfinPluginManager(
|
||||
return dbPluginStatusProvider
|
||||
}
|
||||
|
||||
override fun createPluginDescriptorFinder(): PluginDescriptorFinder {
|
||||
return GameyfinManifestPluginDescriptorFinder()
|
||||
}
|
||||
|
||||
override fun loadPluginFromPath(pluginPath: Path?): PluginWrapper? {
|
||||
val pluginWrapper = super.loadPluginFromPath(pluginPath)
|
||||
val pluginWrapper = try {
|
||||
super.loadPluginFromPath(pluginPath)
|
||||
} catch (e: Exception) {
|
||||
log.error { "Failed to load plugin $pluginPath: ${e.message}" }
|
||||
null
|
||||
}
|
||||
|
||||
if (pluginWrapper == null || pluginPath == null) return null
|
||||
|
||||
|
||||
@@ -5,10 +5,13 @@ import org.pf4j.PluginState
|
||||
data class PluginDto(
|
||||
val id: String,
|
||||
val name: String,
|
||||
val description: String,
|
||||
val version: String,
|
||||
val author: String,
|
||||
val license: String? = null,
|
||||
val url: String? = null,
|
||||
val hasLogo: Boolean,
|
||||
val state: PluginState,
|
||||
val priority: Int,
|
||||
val trustLevel: PluginTrustLevel
|
||||
val trustLevel: PluginTrustLevel,
|
||||
)
|
||||
+13
-8
@@ -87,15 +87,20 @@ class PluginManagementService(
|
||||
false
|
||||
}
|
||||
|
||||
val descriptor = pluginWrapper.descriptor as GameyfinPluginDescriptor
|
||||
|
||||
return PluginDto(
|
||||
pluginWrapper.pluginId,
|
||||
pluginWrapper.descriptor.pluginDescription,
|
||||
pluginWrapper.descriptor.version,
|
||||
pluginWrapper.descriptor.provider,
|
||||
hasLogo,
|
||||
pluginWrapper.pluginState,
|
||||
pluginManagementEntry.priority,
|
||||
pluginManagementEntry.trustLevel
|
||||
id = descriptor.pluginId,
|
||||
name = descriptor.pluginName,
|
||||
description = descriptor.pluginDescription,
|
||||
version = descriptor.version,
|
||||
author = descriptor.author,
|
||||
license = descriptor.license,
|
||||
url = descriptor.pluginUrl,
|
||||
hasLogo = hasLogo,
|
||||
state = pluginWrapper.pluginState,
|
||||
priority = pluginManagementEntry.priority,
|
||||
trustLevel = pluginManagementEntry.trustLevel
|
||||
)
|
||||
}
|
||||
}
|
||||
+1
-1
@@ -3,7 +3,7 @@ package de.grimsi.gameyfin.pluginapi.gamemetadata
|
||||
import java.net.URI
|
||||
import java.time.Instant
|
||||
|
||||
class GameMetadata(
|
||||
data class GameMetadata(
|
||||
val originalId: String,
|
||||
val title: String,
|
||||
val description: String? = null,
|
||||
|
||||
@@ -1,6 +1,10 @@
|
||||
Manifest-Version: 1.0
|
||||
Plugin-Version: 1.0.0-alpha6
|
||||
Plugin-Class: de.grimsi.gameyfin.plugins.igdb.IgdbPlugin
|
||||
Plugin-Id: igdb
|
||||
Plugin-Description: IGDB Metadata
|
||||
Plugin-Version: 1.0.0-alpha5
|
||||
Plugin-Provider: grimsi
|
||||
Plugin-Id: de.grimsi.gameyfin.igdb
|
||||
Plugin-Name: IGDB Metadata
|
||||
Plugin-Description: Fetches metadata from IGDB.<br>
|
||||
Requires a Twitch account and IGDB API credentials.<br>
|
||||
Details see in the [IGDB API docs](https://api-docs.igdb.com/#account-creation).
|
||||
Plugin-Author: grimsi
|
||||
Plugin-License: MIT
|
||||
Plugin-Url: https://github.com/gameyfin
|
||||
|
||||
@@ -1,6 +1,9 @@
|
||||
Manifest-Version: 1.0
|
||||
Plugin-Version: 1.0.0-alpha7
|
||||
Plugin-Class: de.grimsi.gameyfin.plugins.steam.SteamPlugin
|
||||
Plugin-Id: steam
|
||||
Plugin-Description: Steam Metadata
|
||||
Plugin-Version: 1.0.0-alpha6
|
||||
Plugin-Provider: grimsi
|
||||
Plugin-Id: de.grimsi.gameyfin.steam
|
||||
Plugin-Name: Steam Metadata
|
||||
Plugin-Description: Fetches metadata from Steam using undocumented public API endpoints.<br>
|
||||
This is more of a proof of concept and is prone to breaking when the Steam API changes.
|
||||
Plugin-Author: grimsi
|
||||
Plugin-License: MIT
|
||||
Plugin-Url: https://github.com/gameyfin
|
||||
|
||||
@@ -1,6 +1,10 @@
|
||||
Manifest-Version: 1.0
|
||||
Plugin-Version: 1.0.0-alpha3
|
||||
Plugin-Class: de.grimsi.gameyfin.plugins.steamgriddb.SteamGridDbPlugin
|
||||
Plugin-Id: steamgriddb
|
||||
Plugin-Description: SteamGridDB covers
|
||||
Plugin-Version: 1.0.0-alpha2
|
||||
Plugin-Provider: grimsi
|
||||
Plugin-Id: de.grimsi.gameyfin.steamgriddb
|
||||
Plugin-Name: SteamGridDB Covers
|
||||
Plugin-Description: Fetches covers from SteamGridDB.<br>
|
||||
Requires a SteamGridDB account and an API key.<br>
|
||||
The API key can be obtained [here](https://www.steamgriddb.com/profile/preferences/api).
|
||||
Plugin-Author: grimsi
|
||||
Plugin-License: MIT
|
||||
Plugin-Url: https://github.com/gameyfin
|
||||
|
||||
Reference in New Issue
Block a user