mirror of
https://github.com/BrenBroZAYT/gameyfin.git
synced 2026-06-15 16:20:03 +00:00
Added support for Array in ConfigProperties
Added ArrayInput to config page Added game file extension management
This commit is contained in:
@@ -3,6 +3,7 @@ import React from "react";
|
|||||||
import Input from "Frontend/components/general/input/Input";
|
import Input from "Frontend/components/general/input/Input";
|
||||||
import CheckboxInput from "Frontend/components/general/input/CheckboxInput";
|
import CheckboxInput from "Frontend/components/general/input/CheckboxInput";
|
||||||
import SelectInput from "Frontend/components/general/input/SelectInput";
|
import SelectInput from "Frontend/components/general/input/SelectInput";
|
||||||
|
import ArrayInput from "Frontend/components/general/input/ArrayInput";
|
||||||
|
|
||||||
export default function ConfigFormField({configElement, ...props}: any) {
|
export default function ConfigFormField({configElement, ...props}: any) {
|
||||||
function inputElement(configElement: ConfigEntryDto) {
|
function inputElement(configElement: ConfigEntryDto) {
|
||||||
@@ -34,10 +35,14 @@ export default function ConfigFormField({configElement, ...props}: any) {
|
|||||||
<Input label={configElement.description} name={configElement.key} type="number"
|
<Input label={configElement.description} name={configElement.key} type="number"
|
||||||
step="1" {...props}/>
|
step="1" {...props}/>
|
||||||
);
|
);
|
||||||
|
case "Array":
|
||||||
|
return (
|
||||||
|
<ArrayInput label={configElement.description} name={configElement.key} type="text" {...props}/>
|
||||||
|
);
|
||||||
default:
|
default:
|
||||||
return <pre>Unsupported type: {configElement.type} for key {configElement.key}</pre>;
|
return <pre>Unsupported type: {configElement.type} for key {configElement.key}</pre>;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return (inputElement(configElement!));
|
return inputElement(configElement!);
|
||||||
}
|
}
|
||||||
@@ -50,6 +50,7 @@ function LibraryManagementLayout({getConfig, formik}: any) {
|
|||||||
|
|
||||||
<Section title="Scanning"/>
|
<Section title="Scanning"/>
|
||||||
<ConfigFormField configElement={getConfig("library.scan.enable-filesystem-watcher")}/>
|
<ConfigFormField configElement={getConfig("library.scan.enable-filesystem-watcher")}/>
|
||||||
|
<ConfigFormField configElement={getConfig("library.scan.game-file-extensions")}/>
|
||||||
|
|
||||||
<Section title="Metadata"/>
|
<Section title="Metadata"/>
|
||||||
<div className="flex flex-row">
|
<div className="flex flex-row">
|
||||||
|
|||||||
@@ -21,7 +21,7 @@ function UserManagementLayout({getConfig, formik}: any) {
|
|||||||
);
|
);
|
||||||
|
|
||||||
ConfigEndpoint.get("sso.oidc.auto-register-new-users").then(
|
ConfigEndpoint.get("sso.oidc.auto-register-new-users").then(
|
||||||
(response) => setAutoRegisterNewUsers(response === "true")
|
(response) => setAutoRegisterNewUsers(response as boolean)
|
||||||
);
|
);
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
|
|||||||
@@ -62,13 +62,35 @@ export default function withConfigPage(WrappedComponent: React.ComponentType<any
|
|||||||
let value: any;
|
let value: any;
|
||||||
switch (item.type) {
|
switch (item.type) {
|
||||||
case 'Boolean':
|
case 'Boolean':
|
||||||
value = item.value === 'true';
|
value = typeof item.value == 'boolean' ? item.value : item.value === 'true';
|
||||||
break;
|
break;
|
||||||
case 'Int':
|
case 'Int':
|
||||||
value = parseInt(item.value!);
|
value = typeof item.value == 'number' ? item.value : 0;
|
||||||
break;
|
break;
|
||||||
case 'Float':
|
case 'Float':
|
||||||
value = parseFloat(item.value!);
|
value = typeof item.value == 'number' ? item.value : 0.0;
|
||||||
|
break;
|
||||||
|
case 'Array':
|
||||||
|
if (Array.isArray(item.value)) {
|
||||||
|
switch (item.elementType) {
|
||||||
|
case 'Boolean':
|
||||||
|
value = item.value.map(v => typeof v === 'boolean' ? v : v === 'true');
|
||||||
|
break;
|
||||||
|
case 'Int':
|
||||||
|
case 'Integer':
|
||||||
|
value = item.value.map(v => typeof v == 'number' ? v : 0);
|
||||||
|
break;
|
||||||
|
case 'Float':
|
||||||
|
value = item.value.map(v => typeof v == 'number' ? v : 0.0);
|
||||||
|
break;
|
||||||
|
case 'String':
|
||||||
|
default:
|
||||||
|
value = item.value.map(v => v.toString());
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
value = [];
|
||||||
|
}
|
||||||
break;
|
break;
|
||||||
case 'String':
|
case 'String':
|
||||||
default:
|
default:
|
||||||
|
|||||||
@@ -0,0 +1,68 @@
|
|||||||
|
import {FieldArray, useField} from "formik";
|
||||||
|
import {Button, Chip, Input, Popover, PopoverContent, PopoverTrigger} from "@heroui/react";
|
||||||
|
import {KeyboardEvent, useState} from "react";
|
||||||
|
import {Plus, XCircle} from "@phosphor-icons/react";
|
||||||
|
import {SmallInfoField} from "Frontend/components/general/SmallInfoField";
|
||||||
|
|
||||||
|
// @ts-ignore
|
||||||
|
const ArrayInput = ({label, ...props}) => {
|
||||||
|
// @ts-ignore
|
||||||
|
const [field, meta] = useField(props);
|
||||||
|
const [newElementValue, setNewElementValue] = useState<string>("");
|
||||||
|
|
||||||
|
return (
|
||||||
|
<FieldArray name={field.name}
|
||||||
|
render={arrayHelpers => {
|
||||||
|
function handleKeyDown(event: KeyboardEvent<HTMLInputElement>) {
|
||||||
|
if (event.key === "Enter" || event.key == "Tab" || event.key === ",") {
|
||||||
|
event.preventDefault();
|
||||||
|
let trimmedValue = newElementValue.trim();
|
||||||
|
if (trimmedValue !== "") {
|
||||||
|
arrayHelpers.push(trimmedValue);
|
||||||
|
setNewElementValue("");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="flex flex-col gap-2">
|
||||||
|
<div className="flex flex-row justify-between">
|
||||||
|
<p>{label}</p>
|
||||||
|
<small>{field.value.length} {field.value.length == 1 ? "element" : "elements"}</small>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="flex flex-row flex-wrap gap-2 items-center">
|
||||||
|
{field.value.map((element: any, index: number) => (
|
||||||
|
<Chip key={index} onClose={() => arrayHelpers.remove(index)}>
|
||||||
|
{element}
|
||||||
|
</Chip>
|
||||||
|
))}
|
||||||
|
<Popover placement="bottom" showArrow={true}>
|
||||||
|
<PopoverTrigger>
|
||||||
|
<Button isIconOnly size="sm" variant="light" radius="full"><Plus/></Button>
|
||||||
|
</PopoverTrigger>
|
||||||
|
<PopoverContent>
|
||||||
|
<Input
|
||||||
|
value={newElementValue}
|
||||||
|
onChange={(e) => setNewElementValue(e.target.value)}
|
||||||
|
onKeyDown={handleKeyDown}
|
||||||
|
placeholder="New element..."
|
||||||
|
variant="bordered"
|
||||||
|
/>
|
||||||
|
</PopoverContent>
|
||||||
|
</Popover>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="min-h-6 text-danger">
|
||||||
|
{meta.touched && meta.error && meta.error.trim().length > 0 && (
|
||||||
|
<SmallInfoField icon={XCircle} message={meta.error}/>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default ArrayInput;
|
||||||
@@ -7,7 +7,7 @@ const CheckboxInput = ({label, ...props}) => {
|
|||||||
const [field] = useField(props);
|
const [field] = useField(props);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="flex flex-row flex-1 items-center gap-2 mb-2">
|
<div className="flex flex-row flex-1 items-center gap-2 mb-6">
|
||||||
<Checkbox
|
<Checkbox
|
||||||
{...field}
|
{...field}
|
||||||
{...props}
|
{...props}
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import de.grimsi.gameyfin.config.dto.ConfigValuePairDto
|
|||||||
import de.grimsi.gameyfin.core.Role
|
import de.grimsi.gameyfin.core.Role
|
||||||
import jakarta.annotation.security.PermitAll
|
import jakarta.annotation.security.PermitAll
|
||||||
import jakarta.annotation.security.RolesAllowed
|
import jakarta.annotation.security.RolesAllowed
|
||||||
|
import java.io.Serializable
|
||||||
|
|
||||||
@Endpoint
|
@Endpoint
|
||||||
@RolesAllowed(Role.Names.ADMIN)
|
@RolesAllowed(Role.Names.ADMIN)
|
||||||
@@ -19,7 +20,7 @@ class ConfigEndpoint(
|
|||||||
return config.getAll(prefix)
|
return config.getAll(prefix)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun get(key: String): String? {
|
fun get(key: String): Serializable? {
|
||||||
return config.get(key)
|
return config.get(key)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -32,7 +33,7 @@ class ConfigEndpoint(
|
|||||||
}
|
}
|
||||||
|
|
||||||
fun resetConfig(key: String) {
|
fun resetConfig(key: String) {
|
||||||
config.resetConfigValue(key)
|
config.deleteConfig(key)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun deleteConfig(key: String) {
|
fun deleteConfig(key: String) {
|
||||||
|
|||||||
@@ -29,11 +29,32 @@ sealed class ConfigProperties<T : Serializable>(
|
|||||||
true
|
true
|
||||||
)
|
)
|
||||||
|
|
||||||
data object GameFileExtensions : ConfigProperties<String>(
|
data object GameFileExtensions : ConfigProperties<Array<String>>(
|
||||||
String::class,
|
Array<String>::class,
|
||||||
"library.scan.game-file-extensions",
|
"library.scan.game-file-extensions",
|
||||||
"File extensions to consider as games",
|
"File extensions to consider as games",
|
||||||
"zip, tar, gz, rar, 7z, bz2, xz, iso, jar, tgz, exe, bat, cmd, com, msi, bin, run, app, dmg, elf"
|
arrayOf(
|
||||||
|
"zip",
|
||||||
|
"tar",
|
||||||
|
"gz",
|
||||||
|
"rar",
|
||||||
|
"7z",
|
||||||
|
"bz2",
|
||||||
|
"xz",
|
||||||
|
"iso",
|
||||||
|
"jar",
|
||||||
|
"tgz",
|
||||||
|
"exe",
|
||||||
|
"bat",
|
||||||
|
"cmd",
|
||||||
|
"com",
|
||||||
|
"msi",
|
||||||
|
"bin",
|
||||||
|
"run",
|
||||||
|
"app",
|
||||||
|
"dmg",
|
||||||
|
"elf"
|
||||||
|
)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import de.grimsi.gameyfin.config.dto.ConfigValuePairDto
|
|||||||
import de.grimsi.gameyfin.config.entities.ConfigEntry
|
import de.grimsi.gameyfin.config.entities.ConfigEntry
|
||||||
import de.grimsi.gameyfin.config.persistence.ConfigRepository
|
import de.grimsi.gameyfin.config.persistence.ConfigRepository
|
||||||
import io.github.oshai.kotlinlogging.KotlinLogging
|
import io.github.oshai.kotlinlogging.KotlinLogging
|
||||||
|
import org.springframework.data.repository.findByIdOrNull
|
||||||
import org.springframework.stereotype.Service
|
import org.springframework.stereotype.Service
|
||||||
import java.io.Serializable
|
import java.io.Serializable
|
||||||
|
|
||||||
@@ -33,14 +34,22 @@ class ConfigService(
|
|||||||
}
|
}
|
||||||
|
|
||||||
return configProperties.map { configProperty ->
|
return configProperties.map { configProperty ->
|
||||||
val appConfig = appConfigRepository.findById(configProperty.key).orElse(null)
|
val appConfig = appConfigRepository.findByIdOrNull(configProperty.key)
|
||||||
|
|
||||||
|
val parsedValue =
|
||||||
|
if (configProperty.type.java.isArray)
|
||||||
|
appConfig?.value?.split(",")?.toTypedArray()
|
||||||
|
else
|
||||||
|
appConfig?.value
|
||||||
|
|
||||||
ConfigEntryDto(
|
ConfigEntryDto(
|
||||||
key = configProperty.key,
|
key = configProperty.key,
|
||||||
value = appConfig?.value ?: configProperty.default?.toString(),
|
value = parsedValue ?: configProperty.default,
|
||||||
defaultValue = configProperty.default?.toString(),
|
defaultValue = configProperty.default,
|
||||||
type = configProperty.type.simpleName ?: "Unknown",
|
type = configProperty.type.simpleName ?: "Unknown",
|
||||||
description = configProperty.description,
|
elementType = configProperty.type.java.componentType?.simpleName,
|
||||||
allowedValues = configProperty.allowedValues?.map { it.toString() }
|
allowedValues = configProperty.allowedValues?.map { it.toString() },
|
||||||
|
description = configProperty.description
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -71,18 +80,18 @@ class ConfigService(
|
|||||||
* @param key: The key of the config property
|
* @param key: The key of the config property
|
||||||
* @return The current value if set or the default value or null if no value is set and no default value exists
|
* @return The current value if set or the default value or null if no value is set and no default value exists
|
||||||
*/
|
*/
|
||||||
fun get(key: String): String? {
|
fun get(key: String): Serializable? {
|
||||||
|
|
||||||
log.info { "Getting config value '$key'" }
|
log.info { "Getting config value '$key'" }
|
||||||
|
|
||||||
val configProperty = findConfigProperty(key)
|
val configProperty = findConfigProperty(key)
|
||||||
val appConfig = appConfigRepository.findById(configProperty.key).orElse(null)
|
val appConfig = appConfigRepository.findById(configProperty.key).orElse(null)
|
||||||
|
|
||||||
return if (appConfig != null) {
|
if (appConfig != null) return getValue(appConfig.value, configProperty)
|
||||||
getValue(appConfig.value, configProperty).toString()
|
|
||||||
} else {
|
if (configProperty.default == null) return null
|
||||||
configProperty.default?.toString() ?: return null
|
|
||||||
}
|
return configProperty.default
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -116,51 +125,32 @@ class ConfigService(
|
|||||||
* @param value: Value to set the config property to
|
* @param value: Value to set the config property to
|
||||||
* @throws IllegalArgumentException if the value can't be cast to the type defined for the config property
|
* @throws IllegalArgumentException if the value can't be cast to the type defined for the config property
|
||||||
*/
|
*/
|
||||||
|
@Suppress("UNCHECKED_CAST")
|
||||||
fun <T : Serializable> set(key: String, value: T) {
|
fun <T : Serializable> set(key: String, value: T) {
|
||||||
log.info { "Set config value '$key'" }
|
log.info { "Set config value '$key'" }
|
||||||
|
|
||||||
val configKey = findConfigProperty(key)
|
val configProperty = findConfigProperty(key)
|
||||||
|
|
||||||
// Check if the value can be cast to the type defined for the config property
|
var configEntry = appConfigRepository.findByIdOrNull(key)
|
||||||
val castedValue = getValue(value.toString(), configKey)
|
|
||||||
|
|
||||||
var configEntry = appConfigRepository.findById(key).orElse(null)
|
val parsedValue =
|
||||||
|
if (value.javaClass.isArray)
|
||||||
|
(value as Array<Serializable>).joinToString(",")
|
||||||
|
else
|
||||||
|
value.toString()
|
||||||
|
|
||||||
if (configEntry == null) {
|
if (configEntry == null) {
|
||||||
configEntry = ConfigEntry(configKey.key, castedValue.toString())
|
configEntry = ConfigEntry(configProperty.key, parsedValue)
|
||||||
} else {
|
} else {
|
||||||
configEntry.value = castedValue.toString()
|
configEntry.value = parsedValue
|
||||||
}
|
}
|
||||||
|
|
||||||
appConfigRepository.save(configEntry)
|
appConfigRepository.save(configEntry)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Reset a given config property to its default value if it has a default value.
|
* Remove a config property from the database.
|
||||||
* Otherwise, delete the config key from the database.
|
* This will also cause it to reset to its default value.
|
||||||
*
|
|
||||||
* @param key: Key of the config property
|
|
||||||
*/
|
|
||||||
fun resetConfigValue(key: String) {
|
|
||||||
|
|
||||||
log.info { "Reset config value '$key'" }
|
|
||||||
|
|
||||||
val configKey = findConfigProperty(key)
|
|
||||||
|
|
||||||
if (configKey.default == null) {
|
|
||||||
deleteConfig(key)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
val appConfig = appConfigRepository.findById(configKey.key).orElse(null)
|
|
||||||
if (appConfig != null) {
|
|
||||||
appConfig.value = configKey.default.toString()
|
|
||||||
appConfigRepository.save(appConfig)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Remove a config property from the database
|
|
||||||
*
|
*
|
||||||
* @param key: Key of the config property
|
* @param key: Key of the config property
|
||||||
*/
|
*/
|
||||||
@@ -176,21 +166,34 @@ class ConfigService(
|
|||||||
* Get the value of the config property in a type-safe way.
|
* Get the value of the config property in a type-safe way.
|
||||||
*/
|
*/
|
||||||
@Suppress("UNCHECKED_CAST")
|
@Suppress("UNCHECKED_CAST")
|
||||||
private fun <T : Serializable> getValue(value: String, configProperty: ConfigProperties<T>): T {
|
private fun <T : Serializable> getValue(value: Serializable, configProperty: ConfigProperties<T>): T {
|
||||||
return when (configProperty.type) {
|
val value = value.toString()
|
||||||
String::class -> value as T
|
return when {
|
||||||
Boolean::class -> value.toBoolean() as T
|
configProperty.type == String::class -> value as T
|
||||||
Int::class -> value.toFloat().toInt() as T
|
configProperty.type == Boolean::class -> value.toBoolean() as T
|
||||||
Float::class -> value.toFloat() as T
|
configProperty.type == Int::class -> value.toFloat().toInt() as T
|
||||||
else -> {
|
configProperty.type == Float::class -> value.toFloat() as T
|
||||||
if (configProperty.type.java.isEnum) {
|
|
||||||
val enumConstants = configProperty.type.java.enumConstants
|
configProperty.type.java.isEnum -> {
|
||||||
enumConstants.firstOrNull { it.toString() == value }
|
val enumConstants = configProperty.type.java.enumConstants
|
||||||
?: throw IllegalArgumentException("Unknown enum value '$value' for key ${configProperty.key}")
|
enumConstants.firstOrNull { it.toString() == value }
|
||||||
} else {
|
?: throw IllegalArgumentException("Unknown enum value '$value' for key ${configProperty.key}")
|
||||||
throw IllegalArgumentException("Unknown config type ${configProperty.type}: '$value' for key ${configProperty.key}")
|
}
|
||||||
|
|
||||||
|
configProperty.type.java.isArray -> {
|
||||||
|
val componentType = configProperty.type.java.componentType
|
||||||
|
// Remove the brackets and split the string by commas
|
||||||
|
val elements = value.removeSurrounding("[", "]").split(",")
|
||||||
|
when (componentType) {
|
||||||
|
String::class.java -> elements.toTypedArray() as T
|
||||||
|
Boolean::class.java -> elements.map { it.toBoolean() }.toTypedArray() as T
|
||||||
|
Int::class.java -> elements.map { it.toInt() }.toTypedArray() as T
|
||||||
|
Float::class.java -> elements.map { it.toFloat() }.toTypedArray() as T
|
||||||
|
else -> throw IllegalArgumentException("Unsupported array type: ${componentType.name}")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
else -> throw IllegalArgumentException("Unknown config type ${configProperty.type}: '$value' for key ${configProperty.key}")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,14 +1,15 @@
|
|||||||
package de.grimsi.gameyfin.config.dto
|
package de.grimsi.gameyfin.config.dto
|
||||||
|
|
||||||
import com.fasterxml.jackson.annotation.JsonInclude
|
import com.fasterxml.jackson.annotation.JsonInclude
|
||||||
import jakarta.annotation.Nonnull
|
import java.io.Serializable
|
||||||
|
|
||||||
@JsonInclude(JsonInclude.Include.ALWAYS)
|
@JsonInclude(JsonInclude.Include.ALWAYS)
|
||||||
data class ConfigEntryDto(
|
data class ConfigEntryDto(
|
||||||
@field:Nonnull val key: String,
|
val key: String,
|
||||||
val value: String?,
|
val description: String,
|
||||||
val defaultValue: String?,
|
val value: Serializable?,
|
||||||
@field:Nonnull val type: String,
|
val defaultValue: Serializable?,
|
||||||
@field:Nonnull val description: String,
|
val type: String,
|
||||||
|
val elementType: String?,
|
||||||
val allowedValues: List<String>?
|
val allowedValues: List<String>?
|
||||||
)
|
)
|
||||||
@@ -1,6 +1,12 @@
|
|||||||
package de.grimsi.gameyfin.config.dto
|
package de.grimsi.gameyfin.config.dto
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.databind.annotation.JsonDeserialize
|
||||||
|
import de.grimsi.gameyfin.core.serialization.ArrayDeserializer
|
||||||
|
import java.io.Serializable
|
||||||
|
|
||||||
data class ConfigValuePairDto(
|
data class ConfigValuePairDto(
|
||||||
val key: String,
|
val key: String,
|
||||||
val value: String?
|
|
||||||
|
@field:JsonDeserialize(using = ArrayDeserializer::class)
|
||||||
|
val value: Serializable?
|
||||||
)
|
)
|
||||||
@@ -18,9 +18,7 @@ class FilesystemService(
|
|||||||
private val log = KotlinLogging.logger {}
|
private val log = KotlinLogging.logger {}
|
||||||
|
|
||||||
private val gameFileExtensions
|
private val gameFileExtensions
|
||||||
get() = config.get(ConfigProperties.Libraries.Scan.GameFileExtensions)!!
|
get() = config.get(ConfigProperties.Libraries.Scan.GameFileExtensions)!!.map { it.trim().lowercase() }
|
||||||
.split(",")
|
|
||||||
.map { it.trim().lowercase() }
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Lists all files and directories in the given path.
|
* Lists all files and directories in the given path.
|
||||||
@@ -45,7 +43,7 @@ class FilesystemService(
|
|||||||
return safeReadDirectoryContents(roots.first().toString())
|
return safeReadDirectoryContents(roots.first().toString())
|
||||||
}
|
}
|
||||||
|
|
||||||
var path = FilenameUtils.separatorsToSystem(path)
|
val path = FilenameUtils.separatorsToSystem(path)
|
||||||
|
|
||||||
return safeReadDirectoryContents(path)
|
return safeReadDirectoryContents(path)
|
||||||
}
|
}
|
||||||
@@ -93,8 +91,8 @@ class FilesystemService(
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Return all paths that are directories or match the game file extensions
|
// Return all paths that are directories or match the game file extensions
|
||||||
return validDirectories.flatMap {
|
return validDirectories.flatMap { validDirectory ->
|
||||||
safeReadDirectoryContents(it)
|
safeReadDirectoryContents(validDirectory)
|
||||||
.filter { it.isDirectory() || it.extension.lowercase() in gamefileExtensions }
|
.filter { it.isDirectory() || it.extension.lowercase() in gamefileExtensions }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,18 @@
|
|||||||
|
package de.grimsi.gameyfin.core.serialization
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.core.JsonParser
|
||||||
|
import com.fasterxml.jackson.databind.DeserializationContext
|
||||||
|
import com.fasterxml.jackson.databind.JsonDeserializer
|
||||||
|
import com.fasterxml.jackson.databind.JsonNode
|
||||||
|
import java.io.Serializable
|
||||||
|
|
||||||
|
class ArrayDeserializer : JsonDeserializer<Serializable>() {
|
||||||
|
override fun deserialize(p: JsonParser, ctxt: DeserializationContext): Serializable {
|
||||||
|
val node = p.codec.readTree<JsonNode>(p)
|
||||||
|
return if (node.isArray) {
|
||||||
|
node.map { it.asText() }.toTypedArray()
|
||||||
|
} else {
|
||||||
|
p.codec.treeToValue(node, Serializable::class.java)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
+1
-1
@@ -16,7 +16,7 @@ abstract class AbstractMessageProvider(
|
|||||||
private val configKey = String.format("%s.%s.enabled", BASE_KEY, providerKey)
|
private val configKey = String.format("%s.%s.enabled", BASE_KEY, providerKey)
|
||||||
|
|
||||||
val enabled: Boolean
|
val enabled: Boolean
|
||||||
get() = config.get(configKey).toBoolean()
|
get() = config.get(configKey) as Boolean
|
||||||
|
|
||||||
abstract fun testCredentials(credentials: Properties): Boolean
|
abstract fun testCredentials(credentials: Properties): Boolean
|
||||||
|
|
||||||
|
|||||||
@@ -1,3 +1,7 @@
|
|||||||
logging.level.de.grimsi.gameyfin: DEBUG
|
logging.level.de.grimsi.gameyfin: DEBUG
|
||||||
logging.level.org.hibernate.SQL: DEBUG
|
logging.level.org.hibernate.SQL: DEBUG
|
||||||
logging.level.org.hibernate.type: TRACE
|
logging.level.org.hibernate.type: TRACE
|
||||||
|
|
||||||
|
spring:
|
||||||
|
datasource:
|
||||||
|
url: jdbc:h2:file:./db/${spring.datasource.db-name};AUTO_SERVER=TRUE
|
||||||
Reference in New Issue
Block a user