mirror of
https://github.com/grimsi/gameyfin.git
synced 2026-02-06 11:27:07 +00:00
2.0.0.beta5 (#629)
* Remove unnecessary "requiresLogin" handles * Rename property in UserInfoDto * Fix #628 * Fix ant matchers (again) * Major performance improvements for game matching * Minor logging improvements
This commit is contained in:
parent
e506ad1bc2
commit
13d5fcc80a
@ -8,6 +8,7 @@ import {PaperPlaneRight, Pencil} from "@phosphor-icons/react";
|
||||
import MessageTemplateDto from "Frontend/generated/org/gameyfin/app/messages/templates/MessageTemplateDto";
|
||||
import SendTestNotificationModal from "Frontend/components/administration/messages/SendTestNotificationModal";
|
||||
import EditTemplateModal from "Frontend/components/administration/messages/EditTemplateModel";
|
||||
import * as Yup from "yup";
|
||||
|
||||
function MessageManagementLayout({getConfig, formik}: any) {
|
||||
|
||||
@ -126,4 +127,21 @@ function MessageManagementLayout({getConfig, formik}: any) {
|
||||
);
|
||||
}
|
||||
|
||||
export const MessageManagement = withConfigPage(MessageManagementLayout, "Messages", "messages");
|
||||
const validationSchema = Yup.object({
|
||||
messages: Yup.object({
|
||||
providers: Yup.object({
|
||||
email: Yup.object({
|
||||
enabled: Yup.boolean().required("Required"),
|
||||
host: Yup.string().required("Host is required"),
|
||||
port: Yup.number().required("Port is required")
|
||||
.min(0, "Port must be between 0 and 65535")
|
||||
.max(65535, "Port must be between 0 and 65535"),
|
||||
username: Yup.string()
|
||||
.email("Invalid email address")
|
||||
.required("Username is required"),
|
||||
})
|
||||
})
|
||||
})
|
||||
});
|
||||
|
||||
export const MessageManagement = withConfigPage(MessageManagementLayout, "Messages", validationSchema);
|
||||
@ -23,20 +23,18 @@ import SearchView from "Frontend/views/SearchView";
|
||||
import RecentlyAddedView from "Frontend/views/RecentlyAddedView";
|
||||
import LibraryView from "Frontend/views/LibraryView";
|
||||
import {RouterConfigurationBuilder} from "@vaadin/hilla-file-router/runtime.js";
|
||||
import {ConfigEndpoint} from "Frontend/generated/endpoints";
|
||||
|
||||
export const {router, routes} = new RouterConfigurationBuilder()
|
||||
.withReactRoutes([
|
||||
{
|
||||
element: <App/>,
|
||||
handle: {requiresLogin: false},
|
||||
children: [
|
||||
{
|
||||
element: <MainLayout/>,
|
||||
handle: {requiresLogin: !ConfigEndpoint.isPublicAccessEnabled()},
|
||||
children: [
|
||||
{
|
||||
index: true, element: <HomeView/>
|
||||
index: true,
|
||||
element: <HomeView/>
|
||||
},
|
||||
{
|
||||
path: 'search',
|
||||
@ -65,7 +63,6 @@ export const {router, routes} = new RouterConfigurationBuilder()
|
||||
{
|
||||
path: 'administration',
|
||||
element: <AdministrationView/>,
|
||||
handle: {requiresLogin: true},
|
||||
children: [
|
||||
{
|
||||
path: 'libraries',
|
||||
@ -86,19 +83,19 @@ export const {router, routes} = new RouterConfigurationBuilder()
|
||||
]
|
||||
},
|
||||
{
|
||||
path: 'login', element: <LoginView/>, handle: {requiresLogin: false}
|
||||
path: 'login', element: <LoginView/>
|
||||
},
|
||||
{
|
||||
path: 'setup', element: <SetupView/>, handle: {requiresLogin: false}
|
||||
path: 'setup', element: <SetupView/>
|
||||
},
|
||||
{
|
||||
path: 'accept-invitation', element: <InvitationRegistrationView/>, handle: {requiresLogin: false}
|
||||
path: 'accept-invitation', element: <InvitationRegistrationView/>
|
||||
},
|
||||
{
|
||||
path: 'reset-password', element: <PasswordResetView/>, handle: {requiresLogin: false}
|
||||
path: 'reset-password', element: <PasswordResetView/>
|
||||
},
|
||||
{
|
||||
path: 'confirm-email', element: <EmailConfirmationView/>, handle: {requiresLogin: true}
|
||||
path: 'confirm-email', element: <EmailConfirmationView/>
|
||||
},
|
||||
]
|
||||
}
|
||||
|
||||
@ -44,6 +44,7 @@ class SecurityConfig(
|
||||
.requestMatchers("/reset-password").permitAll()
|
||||
.requestMatchers("/accept-invitation").permitAll()
|
||||
.requestMatchers("/public/**").permitAll()
|
||||
.requestMatchers("/images/**").permitAll()
|
||||
|
||||
// Dynamic public access for certain endpoints
|
||||
auth.requestMatchers("/").access(DynamicPublicAccessAuthorizationManager(config))
|
||||
@ -51,8 +52,6 @@ class SecurityConfig(
|
||||
.requestMatchers("/library/**").access(DynamicPublicAccessAuthorizationManager(config))
|
||||
.requestMatchers("/search/**").access(DynamicPublicAccessAuthorizationManager(config))
|
||||
.requestMatchers("/download/**").access(DynamicPublicAccessAuthorizationManager(config))
|
||||
.requestMatchers("/images/**").access(DynamicPublicAccessAuthorizationManager(config))
|
||||
.requestMatchers("/images/**").access(DynamicPublicAccessAuthorizationManager(config))
|
||||
}
|
||||
|
||||
http.sessionManagement { sessionManagement ->
|
||||
|
||||
@ -35,6 +35,8 @@ import java.net.URI
|
||||
import java.nio.file.Path
|
||||
import java.time.ZoneId
|
||||
import java.time.ZoneOffset
|
||||
import java.util.concurrent.Executors
|
||||
import java.util.concurrent.Future
|
||||
import kotlin.time.Duration.Companion.milliseconds
|
||||
import kotlin.time.toJavaDuration
|
||||
import org.gameyfin.pluginapi.gamemetadata.GameMetadata as PluginApiMetadata
|
||||
@ -71,6 +73,8 @@ class GameService(
|
||||
fun emit(event: GameEvent) {
|
||||
gameEvents.tryEmitNext(event)
|
||||
}
|
||||
|
||||
private val executor = Executors.newVirtualThreadPerTaskExecutor()
|
||||
}
|
||||
|
||||
private val metadataPlugins: List<GameMetadataProvider>
|
||||
@ -237,15 +241,18 @@ class GameService(
|
||||
|
||||
fun getPotentialMatches(searchTerm: String): List<GameSearchResultDto> {
|
||||
// 1. Query all plugins for up to 10 results each
|
||||
val results = metadataPlugins.flatMap { plugin ->
|
||||
try {
|
||||
plugin.fetchByTitle(searchTerm, 10)
|
||||
.map { plugin to it }
|
||||
} catch (e: Exception) {
|
||||
log.error(e) { "Error fetching metadata for game with plugin ${plugin.javaClass.name}" }
|
||||
emptyList()
|
||||
val futures: List<Future<List<Pair<GameMetadataProvider, PluginApiMetadata>>>> = metadataPlugins.map { plugin ->
|
||||
executor.submit<List<Pair<GameMetadataProvider, PluginApiMetadata>>> {
|
||||
try {
|
||||
plugin.fetchByTitle(searchTerm, 10).map { plugin to it }
|
||||
} catch (e: Exception) {
|
||||
log.error(e) { "Error fetching metadata for searchterm '$searchTerm' with plugin ${plugin.javaClass.name}" }
|
||||
emptyList()
|
||||
}
|
||||
}
|
||||
}
|
||||
val results = futures.flatMap { it.get() }
|
||||
|
||||
val providerToManagementEntry =
|
||||
results.toMap().entries.associate { it.key to pluginService.getPluginManagementEntry(it.key.javaClass) }
|
||||
|
||||
@ -423,20 +430,17 @@ class GameService(
|
||||
* @return A map of metadata plugins and their respective results
|
||||
*/
|
||||
private fun queryPlugins(gameTitle: String): Map<GameMetadataProvider, PluginApiMetadata?> {
|
||||
return runBlocking {
|
||||
coroutineScope {
|
||||
metadataPlugins.associateWith {
|
||||
async {
|
||||
try {
|
||||
it.fetchByTitle(gameTitle).firstOrNull()
|
||||
} catch (e: Exception) {
|
||||
log.error(e) { "Error fetching metadata for game with plugin ${it.javaClass.name}" }
|
||||
null
|
||||
}
|
||||
}.await()
|
||||
val futures = metadataPlugins.associateWith { plugin ->
|
||||
executor.submit<PluginApiMetadata?> {
|
||||
try {
|
||||
plugin.fetchByTitle(gameTitle).firstOrNull()
|
||||
} catch (_: Exception) {
|
||||
log.error { "Error fetching metadata for game title '$gameTitle' with plugin ${plugin.javaClass.name}" }
|
||||
null
|
||||
}
|
||||
}
|
||||
}
|
||||
return futures.mapValues { it.value.get() }
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@ -299,7 +299,7 @@ class UserService(
|
||||
username = user.username,
|
||||
email = user.email,
|
||||
emailConfirmed = user.emailConfirmed,
|
||||
isEnabled = user.enabled,
|
||||
enabled = user.enabled,
|
||||
hasAvatar = user.avatar != null,
|
||||
avatarId = user.avatar?.id,
|
||||
managedBySso = user.oidcProviderId != null,
|
||||
|
||||
@ -7,7 +7,7 @@ data class UserInfoDto(
|
||||
val managedBySso: Boolean,
|
||||
val email: String,
|
||||
val emailConfirmed: Boolean,
|
||||
val isEnabled: Boolean,
|
||||
val enabled: Boolean,
|
||||
val hasAvatar: Boolean,
|
||||
val avatarId: Long? = null,
|
||||
var roles: List<Role>
|
||||
|
||||
@ -1,5 +1,8 @@
|
||||
package org.gameyfin.plugins.metadata.steamgriddb
|
||||
|
||||
import kotlinx.coroutines.async
|
||||
import kotlinx.coroutines.awaitAll
|
||||
import kotlinx.coroutines.coroutineScope
|
||||
import kotlinx.coroutines.runBlocking
|
||||
import org.gameyfin.pluginapi.core.config.*
|
||||
import org.gameyfin.pluginapi.core.wrapper.ConfigurableGameyfinPlugin
|
||||
@ -81,18 +84,21 @@ class SteamGridDbPlugin(wrapper: PluginWrapper) : ConfigurableGameyfinPlugin(wra
|
||||
override fun fetchByTitle(gameTitle: String, maxResults: Int): List<GameMetadata> {
|
||||
return runBlocking {
|
||||
val results = searchSteamGridDb(gameTitle)
|
||||
|
||||
results.map { game ->
|
||||
val grids = getGridsForGame(game.id)
|
||||
val heroes = getHeroesForGame(game.id)
|
||||
GameMetadata(
|
||||
originalId = game.id.toString(),
|
||||
title = game.name,
|
||||
release = game.releaseDate,
|
||||
coverUrls = grids?.map { URI(it.url) },
|
||||
headerUrls = heroes?.map { URI(it.url) }
|
||||
)
|
||||
}.take(maxResults)
|
||||
coroutineScope {
|
||||
results.map { game ->
|
||||
async {
|
||||
val grids = getGridsForGame(game.id)
|
||||
val heroes = getHeroesForGame(game.id)
|
||||
GameMetadata(
|
||||
originalId = game.id.toString(),
|
||||
title = game.name,
|
||||
release = game.releaseDate,
|
||||
coverUrls = grids?.map { URI(it.url) },
|
||||
headerUrls = heroes?.map { URI(it.url) }
|
||||
)
|
||||
}
|
||||
}.awaitAll().take(maxResults)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Loading…
Reference in New Issue
Block a user