Implement entity listeners for Game and Library

This commit is contained in:
grimsi
2025-05-26 10:03:44 +02:00
parent fe561c42b4
commit daa8b7ee6c
8 changed files with 116 additions and 79 deletions
@@ -15,7 +15,7 @@ class GameEndpoint(
private val gameService: GameService
) {
fun subscribe(): Flux<GameEvent> {
return gameService.subscribe()
return GameService.subscribe()
}
fun getAll(): List<GameDto> = gameService.getAll()
@@ -25,5 +25,4 @@ class GameEndpoint(
@RolesAllowed(Role.Names.ADMIN)
fun deleteGame(gameId: Long) = gameService.delete(gameId)
}
@@ -17,7 +17,6 @@ import de.grimsi.gameyfin.libraries.Library
import de.grimsi.gameyfin.pluginapi.gamemetadata.GameMetadata
import de.grimsi.gameyfin.pluginapi.gamemetadata.GameMetadataProvider
import io.github.oshai.kotlinlogging.KotlinLogging
import jakarta.persistence.EntityManager
import kotlinx.coroutines.async
import kotlinx.coroutines.coroutineScope
import kotlinx.coroutines.runBlocking
@@ -37,26 +36,31 @@ class GameService(
private val pluginService: PluginService,
private val config: ConfigService,
private val companyService: CompanyService,
private val gameRepository: GameRepository,
private val entityManager: EntityManager
private val gameRepository: GameRepository
) {
companion object {
private val log = KotlinLogging.logger {}
/* Websockets */
private val gameEvents = Sinks.many().multicast().onBackpressureBuffer<GameEvent>(1024, false)
fun subscribe(): Flux<GameEvent> {
log.debug { "New subscription for gameUpdates" }
return gameEvents.asFlux()
.doOnSubscribe { log.debug { "Subscriber added to gameEvents [${gameEvents.currentSubscriberCount()}]" } }
.doFinally {
log.debug { "Subscriber removed from gameEvents with signal type $it [${gameEvents.currentSubscriberCount()}]" }
}
}
fun emit(event: GameEvent) {
gameEvents.tryEmitNext(event)
}
}
private val metadataPlugins: List<GameMetadataProvider>
get() = pluginManager.getExtensions(GameMetadataProvider::class.java)
private val gameEvents = Sinks.many().multicast().onBackpressureBuffer<GameEvent>(1024, false)
fun subscribe(): Flux<GameEvent> {
log.debug { "New subscription for gameUpdates" }
return gameEvents.asFlux()
.doOnSubscribe { log.debug { "Subscriber added to gameEvents [${gameEvents.currentSubscriberCount()}]" } }
.doFinally {
log.debug { "Subscriber removed from gameEvents with signal type $it [${gameEvents.currentSubscriberCount()}]" }
}
}
fun getAll(): List<GameDto> {
val entities = gameRepository.findAll()
@@ -73,16 +77,7 @@ class GameService(
game
}
val games = gameRepository.saveAll(gamesToBePersisted)
// force flush to populate creation and update timestamp
entityManager.flush()
games.forEach { game ->
val gameDto = game.toDto()
gameEvents.tryEmitNext(GameEvent.Created(gameDto))
}
return games
return gameRepository.saveAll(gamesToBePersisted)
}
fun update(gameUpdateDto: GameUpdateDto) {
@@ -94,18 +89,11 @@ class GameService(
gameUpdateDto.comment?.let { existingGame.comment = it }
gameUpdateDto.summary?.let { existingGame.summary = it }
val updatedGame = gameRepository.save(existingGame)
val updatedGameDto = updatedGame.toDto()
gameEvents.tryEmitNext(GameEvent.Updated(updatedGameDto))
gameRepository.save(existingGame)
}
fun delete(gameId: Long) {
gameRepository.deleteById(gameId)
gameEvents.tryEmitNext(GameEvent.Deleted(gameId))
}
fun emitDeletionEvent(gameId: Long) {
gameEvents.tryEmitNext(GameEvent.Deleted(gameId))
}
fun matchFromFile(path: Path, library: Library): Game? {
@@ -13,6 +13,7 @@ import java.net.URI
import java.time.Instant
@Entity
@EntityListeners(GameEntityListener::class)
class Game(
@Id
@GeneratedValue(strategy = GenerationType.AUTO)
@@ -0,0 +1,28 @@
package de.grimsi.gameyfin.games.entities
import de.grimsi.gameyfin.games.GameService
import de.grimsi.gameyfin.games.dto.GameEvent
import de.grimsi.gameyfin.games.toDto
import jakarta.persistence.PostPersist
import jakarta.persistence.PostRemove
import jakarta.persistence.PostUpdate
class GameEntityListener {
@PostPersist
fun created(game: Game) {
val event = GameEvent.Created(game.toDto())
GameService.emit(event)
}
@PostUpdate
fun updated(game: Game) {
val event = GameEvent.Updated(game.toDto())
GameService.emit(event)
}
@PostRemove
fun deleted(game: Game) {
val event = GameEvent.Deleted(game.id!!)
GameService.emit(event)
}
}
@@ -0,0 +1,29 @@
package de.grimsi.gameyfin.games.entities
import de.grimsi.gameyfin.libraries.Library
import de.grimsi.gameyfin.libraries.LibraryService
import de.grimsi.gameyfin.libraries.dto.LibraryEvent
import de.grimsi.gameyfin.libraries.toDto
import jakarta.persistence.PostPersist
import jakarta.persistence.PostRemove
import jakarta.persistence.PostUpdate
class LibraryEntityListener {
@PostPersist
fun created(library: Library) {
val event = LibraryEvent.Created(library.toDto())
LibraryService.emit(event)
}
@PostUpdate
fun updated(library: Library) {
val event = LibraryEvent.Updated(library.toDto())
LibraryService.emit(event)
}
@PostRemove
fun deleted(library: Library) {
val event = LibraryEvent.Deleted(library.id!!)
LibraryService.emit(event)
}
}
@@ -1,9 +1,11 @@
package de.grimsi.gameyfin.libraries
import de.grimsi.gameyfin.games.entities.Game
import de.grimsi.gameyfin.games.entities.LibraryEntityListener
import jakarta.persistence.*
@Entity
@EntityListeners(LibraryEntityListener::class)
class Library(
@Id
@GeneratedValue(strategy = GenerationType.AUTO)
@@ -16,7 +16,7 @@ class LibraryEndpoint(
private val libraryService: LibraryService
) {
fun subscribe(): Flux<LibraryEvent> {
return libraryService.subscribe()
return LibraryService.subscribe()
}
fun getAll() = libraryService.getAll()
@@ -28,25 +28,31 @@ class LibraryService(
companion object {
private val log = KotlinLogging.logger {}
private val executor = Executors.newVirtualThreadPerTaskExecutor()
/* Websockets */
private val libraryEvents = Sinks.many().multicast().onBackpressureBuffer<LibraryEvent>(1024, false)
fun subscribe(): Flux<LibraryEvent> {
log.debug { "New subscription for libraryEvents" }
return libraryEvents.asFlux()
.doOnSubscribe { log.debug { "Subscriber added to libraryEvents [${libraryEvents.currentSubscriberCount()}]" } }
.doFinally {
log.debug { "Subscriber removed from libraryEvents with signal type $it [${libraryEvents.currentSubscriberCount()}]" }
}
}
fun emit(event: LibraryEvent) {
libraryEvents.tryEmitNext(event)
}
}
private val libraryEvents = Sinks.many().multicast().onBackpressureBuffer<LibraryEvent>(1024, false)
fun subscribe(): Flux<LibraryEvent> {
log.debug { "New subscription for libraryEvents" }
return libraryEvents.asFlux()
.doOnSubscribe { log.debug { "Subscriber added to libraryEvents [${libraryEvents.currentSubscriberCount()}]" } }
.doFinally {
log.debug { "Subscriber removed from libraryEvents with signal type $it [${libraryEvents.currentSubscriberCount()}]" }
}
}
/**
* Retrieves all libraries from the repository.
*/
fun getAll(): List<LibraryDto> {
val entities = libraryRepository.findAll()
return entities.map { toDto(it) }
return entities.map { it.toDto() }
}
/**
@@ -57,8 +63,6 @@ class LibraryService(
*/
fun create(library: LibraryDto) {
val entity = libraryRepository.save(toEntity(library))
val libraryDto = toDto(entity)
libraryEvents.tryEmitNext(LibraryEvent.Created(libraryDto))
}
/**
@@ -81,9 +85,7 @@ class LibraryService(
)
}
val updatedLibrary = libraryRepository.save(existingLibrary)
val updatedLibraryDto = toDto(updatedLibrary)
libraryEvents.tryEmitNext(LibraryEvent.Updated(updatedLibraryDto))
libraryRepository.save(existingLibrary)
}
/**
@@ -92,13 +94,7 @@ class LibraryService(
* @param libraryId: ID of the library to delete.
*/
fun delete(libraryId: Long) {
val gameIds = libraryRepository.findByIdOrNull(libraryId)?.games?.mapNotNull { it.id }
?: throw IllegalArgumentException("Library with ID $libraryId not found")
libraryRepository.deleteById(libraryId)
libraryEvents.tryEmitNext(LibraryEvent.Deleted(libraryId))
gameIds.forEach { gameService.emitDeletionEvent(it) }
}
/**
@@ -256,29 +252,6 @@ class LibraryService(
}
}
/**
* Converts a Library entity to a LibraryDto.
*
* @param library: The Library entity to convert.
* @return The converted LibraryDto.
*/
private fun toDto(library: Library): LibraryDto {
val libraryId = library.id ?: throw IllegalArgumentException("Library ID is null")
val statsDto = LibraryStatsDto(
gamesCount = library.games.size,
downloadedGamesCount = library.games.sumOf { it.downloadCount }
)
return LibraryDto(
id = libraryId,
name = library.name,
directories = library.directories.map { DirectoryMappingDto(it.internalPath, it.externalPath) },
games = library.games.mapNotNull { it.id },
stats = statsDto
)
}
/**
* Adds a collection of games to the library.
*
@@ -306,4 +279,21 @@ class LibraryService(
}.toMutableList()
)
}
}
fun Library.toDto(): LibraryDto {
val libraryId = this.id ?: throw IllegalArgumentException("Library ID is null")
val statsDto = LibraryStatsDto(
gamesCount = this.games.size,
downloadedGamesCount = this.games.sumOf { it.downloadCount }
)
return LibraryDto(
id = libraryId,
name = this.name,
directories = this.directories.map { DirectoryMappingDto(it.internalPath, it.externalPath) },
games = this.games.mapNotNull { it.id },
stats = statsDto
)
}