mirror of
https://github.com/BrenBroZAYT/gameyfin.git
synced 2026-06-16 16:20:04 +00:00
Add buffer to websocket event queues
This commit is contained in:
@@ -5,7 +5,7 @@ import ConfigUpdateDto from "Frontend/generated/de/grimsi/gameyfin/config/dto/Co
|
|||||||
import {Subscription} from "@vaadin/hilla-frontend";
|
import {Subscription} from "@vaadin/hilla-frontend";
|
||||||
|
|
||||||
type ConfigState = {
|
type ConfigState = {
|
||||||
subscription?: Subscription<ConfigUpdateDto>;
|
subscription?: Subscription<ConfigUpdateDto[]>;
|
||||||
isLoaded: boolean;
|
isLoaded: boolean;
|
||||||
state: Record<string, ConfigEntryDto>;
|
state: Record<string, ConfigEntryDto>;
|
||||||
config: NestedConfig;
|
config: NestedConfig;
|
||||||
@@ -32,12 +32,14 @@ export async function initializeConfigState() {
|
|||||||
});
|
});
|
||||||
|
|
||||||
// Subscribe to real-time updates
|
// Subscribe to real-time updates
|
||||||
configState.subscription = ConfigEndpoint.subscribe().onNext((updateDto: ConfigUpdateDto) => {
|
configState.subscription = ConfigEndpoint.subscribe().onNext((updateDtos: ConfigUpdateDto[]) => {
|
||||||
Object.entries(updateDto.updates).forEach(([key, value]) => {
|
updateDtos.forEach((updateDto: ConfigUpdateDto) => {
|
||||||
if (configState.state[key]) {
|
Object.entries(updateDto.updates).forEach(([key, value]) => {
|
||||||
configState.state[key].value = value;
|
if (configState.state[key]) {
|
||||||
}
|
configState.state[key].value = value;
|
||||||
});
|
}
|
||||||
|
});
|
||||||
|
})
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ import GameDto from "Frontend/generated/de/grimsi/gameyfin/games/dto/GameDto";
|
|||||||
import Rand from "rand-seed";
|
import Rand from "rand-seed";
|
||||||
|
|
||||||
type GameState = {
|
type GameState = {
|
||||||
subscription?: Subscription<GameEvent>;
|
subscription?: Subscription<GameEvent[]>;
|
||||||
isLoaded: boolean;
|
isLoaded: boolean;
|
||||||
state: Record<number, GameDto>;
|
state: Record<number, GameDto>;
|
||||||
games: GameDto[];
|
games: GameDto[];
|
||||||
@@ -125,18 +125,20 @@ export async function initializeGameState() {
|
|||||||
});
|
});
|
||||||
|
|
||||||
// Subscribe to real-time updates
|
// Subscribe to real-time updates
|
||||||
gameState.subscription = GameEndpoint.subscribe().onNext((gameEvent) => {
|
gameState.subscription = GameEndpoint.subscribe().onNext((gameEvents: GameEvent[]) => {
|
||||||
switch (gameEvent.type) {
|
gameEvents.forEach((gameEvent: GameEvent) => {
|
||||||
case "created":
|
switch (gameEvent.type) {
|
||||||
case "updated":
|
case "created":
|
||||||
//@ts-ignore
|
case "updated":
|
||||||
gameState.state[gameEvent.game.id] = gameEvent.game;
|
//@ts-ignore
|
||||||
break;
|
gameState.state[gameEvent.game.id] = gameEvent.game;
|
||||||
case "deleted":
|
break;
|
||||||
//@ts-ignore
|
case "deleted":
|
||||||
delete gameState.state[gameEvent.gameId];
|
//@ts-ignore
|
||||||
break;
|
delete gameState.state[gameEvent.gameId];
|
||||||
}
|
break;
|
||||||
|
}
|
||||||
|
})
|
||||||
});
|
});
|
||||||
|
|
||||||
return gameState;
|
return gameState;
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ import LibraryDto from "Frontend/generated/de/grimsi/gameyfin/libraries/dto/Libr
|
|||||||
import LibraryEvent from "Frontend/generated/de/grimsi/gameyfin/libraries/dto/LibraryEvent";
|
import LibraryEvent from "Frontend/generated/de/grimsi/gameyfin/libraries/dto/LibraryEvent";
|
||||||
|
|
||||||
type LibraryState = {
|
type LibraryState = {
|
||||||
subscription?: Subscription<LibraryEvent>;
|
subscription?: Subscription<LibraryEvent[]>;
|
||||||
isLoaded: boolean;
|
isLoaded: boolean;
|
||||||
state: Record<number, LibraryDto>;
|
state: Record<number, LibraryDto>;
|
||||||
libraries: LibraryDto[];
|
libraries: LibraryDto[];
|
||||||
@@ -39,18 +39,20 @@ export async function initializeLibraryState() {
|
|||||||
});
|
});
|
||||||
|
|
||||||
// Subscribe to real-time updates
|
// Subscribe to real-time updates
|
||||||
libraryState.subscription = LibraryEndpoint.subscribe().onNext((libraryEvent) => {
|
libraryState.subscription = LibraryEndpoint.subscribe().onNext((libraryEvents: LibraryEvent[]) => {
|
||||||
switch (libraryEvent.type) {
|
libraryEvents.forEach((libraryEvent: LibraryEvent) => {
|
||||||
case "created":
|
switch (libraryEvent.type) {
|
||||||
case "updated":
|
case "created":
|
||||||
//@ts-ignore
|
case "updated":
|
||||||
libraryState.state[libraryEvent.library.id] = libraryEvent.library;
|
//@ts-ignore
|
||||||
break;
|
libraryState.state[libraryEvent.library.id] = libraryEvent.library;
|
||||||
case "deleted":
|
break;
|
||||||
//@ts-ignore
|
case "deleted":
|
||||||
delete libraryState.state[libraryEvent.libraryId];
|
//@ts-ignore
|
||||||
break;
|
delete libraryState.state[libraryEvent.libraryId];
|
||||||
}
|
break;
|
||||||
|
}
|
||||||
|
})
|
||||||
});
|
});
|
||||||
|
|
||||||
return libraryState;
|
return libraryState;
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ import {proxy} from "valtio/index";
|
|||||||
import {PluginEndpoint} from "Frontend/generated/endpoints";
|
import {PluginEndpoint} from "Frontend/generated/endpoints";
|
||||||
|
|
||||||
type PluginState = {
|
type PluginState = {
|
||||||
subscription?: Subscription<PluginUpdateDto>;
|
subscription?: Subscription<PluginUpdateDto[]>;
|
||||||
isLoaded: boolean;
|
isLoaded: boolean;
|
||||||
state: Record<string, PluginDto>;
|
state: Record<string, PluginDto>;
|
||||||
plugins: PluginDto[];
|
plugins: PluginDto[];
|
||||||
@@ -36,15 +36,17 @@ export async function initializePluginState() {
|
|||||||
});
|
});
|
||||||
|
|
||||||
// Subscribe to real-time updates
|
// Subscribe to real-time updates
|
||||||
pluginState.subscription = PluginEndpoint.subscribe().onNext((updateDto: PluginUpdateDto) => {
|
pluginState.subscription = PluginEndpoint.subscribe().onNext((updateDtos: PluginUpdateDto[]) => {
|
||||||
// Make sure the plugin exists in the state
|
updateDtos.forEach((updateDto: PluginUpdateDto) => {
|
||||||
if (pluginState.state[updateDto.id]) {
|
// Make sure the plugin exists in the state
|
||||||
// Update the existing plugin by merging the new data using destructuring
|
if (pluginState.state[updateDto.id]) {
|
||||||
pluginState.state[updateDto.id] = {
|
// Update the existing plugin by merging the new data using destructuring
|
||||||
...pluginState.state[updateDto.id],
|
pluginState.state[updateDto.id] = {
|
||||||
...updateDto
|
...pluginState.state[updateDto.id],
|
||||||
};
|
...updateDto
|
||||||
}
|
};
|
||||||
|
}
|
||||||
|
})
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -24,9 +24,9 @@ class ConfigEndpoint(
|
|||||||
/** CRUD endpoints for admins **/
|
/** CRUD endpoints for admins **/
|
||||||
|
|
||||||
@PermitAll
|
@PermitAll
|
||||||
fun subscribe(): Flux<ConfigUpdateDto> {
|
fun subscribe(): Flux<List<ConfigUpdateDto>> {
|
||||||
val user = SecurityContextHolder.getContext().authentication.principal as UserDetails
|
val user = SecurityContextHolder.getContext().authentication.principal as UserDetails
|
||||||
return if (user.isAdmin()) configService.subscribe()
|
return if (user.isAdmin()) ConfigService.subscribe()
|
||||||
else Flux.empty()
|
else Flux.empty()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -10,6 +10,8 @@ import org.springframework.stereotype.Service
|
|||||||
import reactor.core.publisher.Flux
|
import reactor.core.publisher.Flux
|
||||||
import reactor.core.publisher.Sinks
|
import reactor.core.publisher.Sinks
|
||||||
import java.io.Serializable
|
import java.io.Serializable
|
||||||
|
import kotlin.time.Duration.Companion.milliseconds
|
||||||
|
import kotlin.time.toJavaDuration
|
||||||
|
|
||||||
@Service
|
@Service
|
||||||
class ConfigService(
|
class ConfigService(
|
||||||
@@ -17,17 +19,23 @@ class ConfigService(
|
|||||||
) {
|
) {
|
||||||
companion object {
|
companion object {
|
||||||
private val log = KotlinLogging.logger {}
|
private val log = KotlinLogging.logger {}
|
||||||
}
|
|
||||||
|
|
||||||
private val configUpdates = Sinks.many().multicast().onBackpressureBuffer<ConfigUpdateDto>(1024, false)
|
/* Websockets */
|
||||||
|
private val configUpdates = Sinks.many().multicast().onBackpressureBuffer<ConfigUpdateDto>(1024, false)
|
||||||
|
|
||||||
fun subscribe(): Flux<ConfigUpdateDto> {
|
fun subscribe(): Flux<List<ConfigUpdateDto>> {
|
||||||
log.debug { "New subscription for configUpdates (#${configUpdates.currentSubscriberCount()})" }
|
log.debug { "New subscription for configUpdates (#${configUpdates.currentSubscriberCount()})" }
|
||||||
return configUpdates.asFlux()
|
return configUpdates.asFlux()
|
||||||
.doOnSubscribe { log.debug { "Subscriber added to configUpdates [${configUpdates.currentSubscriberCount()}]" } }
|
.buffer(100.milliseconds.toJavaDuration())
|
||||||
.doFinally {
|
.doOnSubscribe { log.debug { "Subscriber added to configUpdates [${configUpdates.currentSubscriberCount()}]" } }
|
||||||
log.debug { "Subscriber removed from configUpdates with signal type $it [${configUpdates.currentSubscriberCount()}]" }
|
.doFinally {
|
||||||
}
|
log.debug { "Subscriber removed from configUpdates with signal type $it [${configUpdates.currentSubscriberCount()}]" }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun emit(update: ConfigUpdateDto) {
|
||||||
|
configUpdates.tryEmitNext(update)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -135,7 +143,7 @@ class ConfigService(
|
|||||||
set(key, value)
|
set(key, value)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
configUpdates.tryEmitNext(update)
|
emit(update)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -18,9 +18,9 @@ class PluginEndpoint(
|
|||||||
) {
|
) {
|
||||||
|
|
||||||
@PermitAll
|
@PermitAll
|
||||||
fun subscribe(): Flux<PluginUpdateDto> {
|
fun subscribe(): Flux<List<PluginUpdateDto>> {
|
||||||
val user = SecurityContextHolder.getContext().authentication.principal as UserDetails
|
val user = SecurityContextHolder.getContext().authentication.principal as UserDetails
|
||||||
return if (user.isAdmin()) pluginService.subscribe()
|
return if (user.isAdmin()) PluginService.subscribe()
|
||||||
else Flux.empty()
|
else Flux.empty()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -20,6 +20,8 @@ import org.springframework.data.repository.findByIdOrNull
|
|||||||
import org.springframework.stereotype.Service
|
import org.springframework.stereotype.Service
|
||||||
import reactor.core.publisher.Flux
|
import reactor.core.publisher.Flux
|
||||||
import reactor.core.publisher.Sinks
|
import reactor.core.publisher.Sinks
|
||||||
|
import kotlin.time.Duration.Companion.milliseconds
|
||||||
|
import kotlin.time.toJavaDuration
|
||||||
|
|
||||||
@Service
|
@Service
|
||||||
class PluginService(
|
class PluginService(
|
||||||
@@ -29,26 +31,34 @@ class PluginService(
|
|||||||
) {
|
) {
|
||||||
companion object {
|
companion object {
|
||||||
private val log = KotlinLogging.logger {}
|
private val log = KotlinLogging.logger {}
|
||||||
|
|
||||||
|
/* Websockets */
|
||||||
|
private val pluginUpdates = Sinks.many().multicast().onBackpressureBuffer<PluginUpdateDto>(1024, false)
|
||||||
|
|
||||||
|
fun subscribe(): Flux<List<PluginUpdateDto>> {
|
||||||
|
log.debug { "New subscription for pluginUpdates" }
|
||||||
|
return pluginUpdates.asFlux()
|
||||||
|
.buffer(100.milliseconds.toJavaDuration())
|
||||||
|
.doOnSubscribe { log.debug { "Subscriber added to pluginUpdates [${pluginUpdates.currentSubscriberCount()}]" } }
|
||||||
|
.doFinally {
|
||||||
|
log.debug { "Subscriber removed from pluginUpdates with signal type $it [${pluginUpdates.currentSubscriberCount()}]" }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun emit(update: PluginUpdateDto) {
|
||||||
|
pluginUpdates.tryEmitNext(update)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private val pluginUpdates = Sinks.many().multicast().onBackpressureBuffer<PluginUpdateDto>(1024, false)
|
|
||||||
private val pluginConfigValidationCache = mutableMapOf<String, PluginConfigValidationResult>()
|
private val pluginConfigValidationCache = mutableMapOf<String, PluginConfigValidationResult>()
|
||||||
|
|
||||||
init {
|
init {
|
||||||
pluginManager.addPluginStateListener { event ->
|
pluginManager.addPluginStateListener { event ->
|
||||||
pluginUpdates.tryEmitNext(PluginUpdateDto(id = event.plugin.pluginId, state = event.pluginState))
|
val update = PluginUpdateDto(id = event.plugin.pluginId, state = event.pluginState)
|
||||||
|
emit(update)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun subscribe(): Flux<PluginUpdateDto> {
|
|
||||||
log.debug { "New subscription for pluginUpdates" }
|
|
||||||
return pluginUpdates.asFlux()
|
|
||||||
.doOnSubscribe { log.debug { "Subscriber added to pluginUpdates [${pluginUpdates.currentSubscriberCount()}]" } }
|
|
||||||
.doFinally {
|
|
||||||
log.debug { "Subscriber removed from pluginUpdates with signal type $it [${pluginUpdates.currentSubscriberCount()}]" }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fun getSupportedPluginTypes(): List<String> {
|
fun getSupportedPluginTypes(): List<String> {
|
||||||
return pluginManager.plugins
|
return pluginManager.plugins
|
||||||
.flatMap { pluginManager.getExtensionTypes(it.pluginId) }
|
.flatMap { pluginManager.getExtensionTypes(it.pluginId) }
|
||||||
@@ -113,7 +123,7 @@ class PluginService(
|
|||||||
|
|
||||||
// Emit update event
|
// Emit update event
|
||||||
val update = PluginUpdateDto(pluginId, config = config, configValidation = result)
|
val update = PluginUpdateDto(pluginId, config = config, configValidation = result)
|
||||||
pluginUpdates.tryEmitNext(update)
|
emit(update)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun validatePluginConfig(pluginId: String, forceRevalidation: Boolean = false): PluginConfigValidationResult {
|
fun validatePluginConfig(pluginId: String, forceRevalidation: Boolean = false): PluginConfigValidationResult {
|
||||||
|
|||||||
@@ -14,7 +14,7 @@ import reactor.core.publisher.Flux
|
|||||||
class GameEndpoint(
|
class GameEndpoint(
|
||||||
private val gameService: GameService
|
private val gameService: GameService
|
||||||
) {
|
) {
|
||||||
fun subscribe(): Flux<GameEvent> {
|
fun subscribe(): Flux<List<GameEvent>> {
|
||||||
return GameService.subscribe()
|
return GameService.subscribe()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -29,6 +29,8 @@ import org.springframework.transaction.annotation.Transactional
|
|||||||
import reactor.core.publisher.Flux
|
import reactor.core.publisher.Flux
|
||||||
import reactor.core.publisher.Sinks
|
import reactor.core.publisher.Sinks
|
||||||
import java.nio.file.Path
|
import java.nio.file.Path
|
||||||
|
import kotlin.time.Duration.Companion.milliseconds
|
||||||
|
import kotlin.time.toJavaDuration
|
||||||
|
|
||||||
@Service
|
@Service
|
||||||
class GameService(
|
class GameService(
|
||||||
@@ -44,10 +46,13 @@ class GameService(
|
|||||||
/* Websockets */
|
/* Websockets */
|
||||||
private val gameEvents = Sinks.many().multicast().onBackpressureBuffer<GameEvent>(1024, false)
|
private val gameEvents = Sinks.many().multicast().onBackpressureBuffer<GameEvent>(1024, false)
|
||||||
|
|
||||||
fun subscribe(): Flux<GameEvent> {
|
fun subscribe(): Flux<List<GameEvent>> {
|
||||||
log.debug { "New subscription for gameUpdates" }
|
log.debug { "New subscription for gameUpdates" }
|
||||||
return gameEvents.asFlux()
|
return gameEvents.asFlux()
|
||||||
.doOnSubscribe { log.debug { "Subscriber added to gameEvents [${gameEvents.currentSubscriberCount()}]" } }
|
.buffer(100.milliseconds.toJavaDuration())
|
||||||
|
.doOnSubscribe {
|
||||||
|
log.debug { "Subscriber added to gameEvents [${gameEvents.currentSubscriberCount()}]" }
|
||||||
|
}
|
||||||
.doFinally {
|
.doFinally {
|
||||||
log.debug { "Subscriber removed from gameEvents with signal type $it [${gameEvents.currentSubscriberCount()}]" }
|
log.debug { "Subscriber removed from gameEvents with signal type $it [${gameEvents.currentSubscriberCount()}]" }
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -15,7 +15,7 @@ import reactor.core.publisher.Flux
|
|||||||
class LibraryEndpoint(
|
class LibraryEndpoint(
|
||||||
private val libraryService: LibraryService
|
private val libraryService: LibraryService
|
||||||
) {
|
) {
|
||||||
fun subscribe(): Flux<LibraryEvent> {
|
fun subscribe(): Flux<List<LibraryEvent>> {
|
||||||
return LibraryService.subscribe()
|
return LibraryService.subscribe()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -15,7 +15,9 @@ import java.util.concurrent.Callable
|
|||||||
import java.util.concurrent.ConcurrentHashMap
|
import java.util.concurrent.ConcurrentHashMap
|
||||||
import java.util.concurrent.Executors
|
import java.util.concurrent.Executors
|
||||||
import java.util.concurrent.atomic.AtomicInteger
|
import java.util.concurrent.atomic.AtomicInteger
|
||||||
|
import kotlin.time.Duration.Companion.milliseconds
|
||||||
import kotlin.time.measureTimedValue
|
import kotlin.time.measureTimedValue
|
||||||
|
import kotlin.time.toJavaDuration
|
||||||
|
|
||||||
@Service
|
@Service
|
||||||
class LibraryService(
|
class LibraryService(
|
||||||
@@ -32,10 +34,13 @@ class LibraryService(
|
|||||||
/* Websockets */
|
/* Websockets */
|
||||||
private val libraryEvents = Sinks.many().multicast().onBackpressureBuffer<LibraryEvent>(1024, false)
|
private val libraryEvents = Sinks.many().multicast().onBackpressureBuffer<LibraryEvent>(1024, false)
|
||||||
|
|
||||||
fun subscribe(): Flux<LibraryEvent> {
|
fun subscribe(): Flux<List<LibraryEvent>> {
|
||||||
log.debug { "New subscription for libraryEvents" }
|
log.debug { "New subscription for libraryEvents" }
|
||||||
return libraryEvents.asFlux()
|
return libraryEvents.asFlux()
|
||||||
.doOnSubscribe { log.debug { "Subscriber added to libraryEvents [${libraryEvents.currentSubscriberCount()}]" } }
|
.buffer(100.milliseconds.toJavaDuration())
|
||||||
|
.doOnSubscribe {
|
||||||
|
log.debug { "Subscriber added to libraryEvents [${libraryEvents.currentSubscriberCount()}]" }
|
||||||
|
}
|
||||||
.doFinally {
|
.doFinally {
|
||||||
log.debug { "Subscriber removed from libraryEvents with signal type $it [${libraryEvents.currentSubscriberCount()}]" }
|
log.debug { "Subscriber removed from libraryEvents with signal type $it [${libraryEvents.currentSubscriberCount()}]" }
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user