mirror of
https://github.com/BrenBroZAYT/gameyfin.git
synced 2026-06-16 08:15:48 +00:00
Refactor structure of Game metadata
This commit is contained in:
@@ -77,7 +77,7 @@ function LibraryManagementLayout({getConfig, formik}: any) {
|
|||||||
removeLibrary={removeLibrary} key={library.name}/>
|
removeLibrary={removeLibrary} key={library.name}/>
|
||||||
)}
|
)}
|
||||||
</div> :
|
</div> :
|
||||||
"No libraries configured. Add your first library!"
|
<p className="mt-4 text-center text-default-500">No libraries found</p>
|
||||||
}
|
}
|
||||||
|
|
||||||
<LibraryCreationModal
|
<LibraryCreationModal
|
||||||
|
|||||||
@@ -32,7 +32,7 @@ export default function LibraryManagementGames({library}: LibraryManagementGames
|
|||||||
{new Date(item.createdAt).toLocaleString()}
|
{new Date(item.createdAt).toLocaleString()}
|
||||||
</TableCell>
|
</TableCell>
|
||||||
<TableCell>
|
<TableCell>
|
||||||
{item.path}
|
{item.metadata.path}
|
||||||
</TableCell>
|
</TableCell>
|
||||||
<TableCell className="flex flex-row gap-2">
|
<TableCell className="flex flex-row gap-2">
|
||||||
<Button isIconOnly size="sm" isDisabled={true}><CheckCircle/></Button>
|
<Button isIconOnly size="sm" isDisabled={true}><CheckCircle/></Button>
|
||||||
|
|||||||
@@ -66,7 +66,7 @@ export default function GameView() {
|
|||||||
<p className="text-foreground/60">{game.release !== undefined ? new Date(game.release).getFullYear() : "unknown"}</p>
|
<p className="text-foreground/60">{game.release !== undefined ? new Date(game.release).getFullYear() : "unknown"}</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{downloadOptions && <ComboButton description={humanFileSize(game.fileSize)}
|
{downloadOptions && <ComboButton description={humanFileSize(game.metadata.fileSize)}
|
||||||
options={downloadOptions}
|
options={downloadOptions}
|
||||||
preferredOptionKey="preferred-download-method"
|
preferredOptionKey="preferred-download-method"
|
||||||
/>}
|
/>}
|
||||||
|
|||||||
@@ -21,7 +21,7 @@ class DownloadEndpoint(
|
|||||||
@RequestParam provider: String
|
@RequestParam provider: String
|
||||||
): ResponseEntity<StreamingResponseBody> {
|
): ResponseEntity<StreamingResponseBody> {
|
||||||
val game = gameService.getById(gameId)
|
val game = gameService.getById(gameId)
|
||||||
val download = downloadService.getDownload(game.path, provider)
|
val download = downloadService.getDownload(game.metadata.path, provider)
|
||||||
|
|
||||||
return when (download) {
|
return when (download) {
|
||||||
is FileDownload -> {
|
is FileDownload -> {
|
||||||
|
|||||||
@@ -110,7 +110,7 @@ class FilesystemService(
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Get all paths already in the library as game files or as unmatched paths
|
// Get all paths already in the library as game files or as unmatched paths
|
||||||
val currentLibraryGamePaths = library.games.map { Path(it.path) }
|
val currentLibraryGamePaths = library.games.map { Path(it.metadata.path) }
|
||||||
val currentLibraryUnmatchedPaths = library.unmatchedPaths.map { Path(it) }
|
val currentLibraryUnmatchedPaths = library.unmatchedPaths.map { Path(it) }
|
||||||
val allCurrentLibraryPaths = currentLibraryGamePaths + currentLibraryUnmatchedPaths
|
val allCurrentLibraryPaths = currentLibraryGamePaths + currentLibraryUnmatchedPaths
|
||||||
|
|
||||||
|
|||||||
@@ -7,14 +7,10 @@ import de.grimsi.gameyfin.core.filterValuesNotNull
|
|||||||
import de.grimsi.gameyfin.core.plugins.PluginService
|
import de.grimsi.gameyfin.core.plugins.PluginService
|
||||||
import de.grimsi.gameyfin.core.plugins.management.PluginManagementEntry
|
import de.grimsi.gameyfin.core.plugins.management.PluginManagementEntry
|
||||||
import de.grimsi.gameyfin.core.replaceRomanNumerals
|
import de.grimsi.gameyfin.core.replaceRomanNumerals
|
||||||
import de.grimsi.gameyfin.games.dto.GameDto
|
import de.grimsi.gameyfin.games.dto.*
|
||||||
import de.grimsi.gameyfin.games.dto.GameEvent
|
|
||||||
import de.grimsi.gameyfin.games.dto.GameMetadataDto
|
|
||||||
import de.grimsi.gameyfin.games.dto.GameUpdateDto
|
|
||||||
import de.grimsi.gameyfin.games.entities.*
|
import de.grimsi.gameyfin.games.entities.*
|
||||||
import de.grimsi.gameyfin.games.repositories.GameRepository
|
import de.grimsi.gameyfin.games.repositories.GameRepository
|
||||||
import de.grimsi.gameyfin.libraries.Library
|
import de.grimsi.gameyfin.libraries.Library
|
||||||
import de.grimsi.gameyfin.pluginapi.gamemetadata.GameMetadata
|
|
||||||
import de.grimsi.gameyfin.pluginapi.gamemetadata.GameMetadataProvider
|
import de.grimsi.gameyfin.pluginapi.gamemetadata.GameMetadataProvider
|
||||||
import io.github.oshai.kotlinlogging.KotlinLogging
|
import io.github.oshai.kotlinlogging.KotlinLogging
|
||||||
import kotlinx.coroutines.async
|
import kotlinx.coroutines.async
|
||||||
@@ -31,6 +27,7 @@ import reactor.core.publisher.Sinks
|
|||||||
import java.nio.file.Path
|
import java.nio.file.Path
|
||||||
import kotlin.time.Duration.Companion.milliseconds
|
import kotlin.time.Duration.Companion.milliseconds
|
||||||
import kotlin.time.toJavaDuration
|
import kotlin.time.toJavaDuration
|
||||||
|
import de.grimsi.gameyfin.pluginapi.gamemetadata.GameMetadata as PluginApiMetadata
|
||||||
|
|
||||||
@Service
|
@Service
|
||||||
class GameService(
|
class GameService(
|
||||||
@@ -125,7 +122,7 @@ class GameService(
|
|||||||
}
|
}
|
||||||
|
|
||||||
fun getAllByPaths(paths: List<String>): List<Game> {
|
fun getAllByPaths(paths: List<String>): List<Game> {
|
||||||
return gameRepository.findAllByPathIn(paths)
|
return gameRepository.findAllByMetadata_PathIn(paths)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun getById(id: Long): Game {
|
fun getById(id: Long): Game {
|
||||||
@@ -137,7 +134,7 @@ class GameService(
|
|||||||
* Runs the queries concurrently and asynchronously
|
* Runs the queries concurrently and asynchronously
|
||||||
* @return A map of metadata plugins and their respective results
|
* @return A map of metadata plugins and their respective results
|
||||||
*/
|
*/
|
||||||
private fun queryPlugins(gameTitle: String): Map<GameMetadataProvider, GameMetadata?> {
|
private fun queryPlugins(gameTitle: String): Map<GameMetadataProvider, PluginApiMetadata?> {
|
||||||
return runBlocking {
|
return runBlocking {
|
||||||
coroutineScope {
|
coroutineScope {
|
||||||
metadataPlugins.associateWith {
|
metadataPlugins.associateWith {
|
||||||
@@ -160,8 +157,8 @@ class GameService(
|
|||||||
*/
|
*/
|
||||||
private fun filterResults(
|
private fun filterResults(
|
||||||
originalQuery: String,
|
originalQuery: String,
|
||||||
results: Map<GameMetadataProvider, GameMetadata>
|
results: Map<GameMetadataProvider, PluginApiMetadata>
|
||||||
): Map<GameMetadataProvider, GameMetadata> {
|
): Map<GameMetadataProvider, PluginApiMetadata> {
|
||||||
val providerToTitle = results.entries.associate {
|
val providerToTitle = results.entries.associate {
|
||||||
pluginManager.whichPlugin(it.key.javaClass).pluginId to it.value.title
|
pluginManager.whichPlugin(it.key.javaClass).pluginId to it.value.title
|
||||||
}
|
}
|
||||||
@@ -183,12 +180,12 @@ class GameService(
|
|||||||
* The plugin with the highest possible priority is used as the source for each field
|
* The plugin with the highest possible priority is used as the source for each field
|
||||||
*/
|
*/
|
||||||
private fun mergeResults(
|
private fun mergeResults(
|
||||||
results: Map<GameMetadataProvider, GameMetadata?>,
|
results: Map<GameMetadataProvider, PluginApiMetadata?>,
|
||||||
path: Path,
|
path: Path,
|
||||||
library: Library
|
library: Library
|
||||||
): Game {
|
): Game {
|
||||||
val mergedGame = Game(path = path.toString(), library = library)
|
val mergedGame = Game(path = path, library = library)
|
||||||
val metadataMap = mutableMapOf<String, FieldMetadata>()
|
val metadataMap = mutableMapOf<String, GameFieldMetadata>()
|
||||||
val originalIdsMap = mutableMapOf<PluginManagementEntry, String>()
|
val originalIdsMap = mutableMapOf<PluginManagementEntry, String>()
|
||||||
|
|
||||||
// Cache the plugin management entries for each provider
|
// Cache the plugin management entries for each provider
|
||||||
@@ -209,81 +206,81 @@ class GameService(
|
|||||||
metadata.title.takeIf { it.isNotBlank() }?.let { title ->
|
metadata.title.takeIf { it.isNotBlank() }?.let { title ->
|
||||||
if (!metadataMap.containsKey("title")) {
|
if (!metadataMap.containsKey("title")) {
|
||||||
mergedGame.title = title
|
mergedGame.title = title
|
||||||
metadataMap["title"] = FieldMetadata(sourcePlugin)
|
metadataMap["title"] = GameFieldMetadata(source = sourcePlugin)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
metadata.description?.takeIf { it.isNotBlank() }?.let { description ->
|
metadata.description?.takeIf { it.isNotBlank() }?.let { description ->
|
||||||
if (!metadataMap.containsKey("summary")) {
|
if (!metadataMap.containsKey("summary")) {
|
||||||
mergedGame.summary = description
|
mergedGame.summary = description
|
||||||
metadataMap["summary"] = FieldMetadata(sourcePlugin)
|
metadataMap["summary"] = GameFieldMetadata(source = sourcePlugin)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
metadata.coverUrl?.let { coverUrl ->
|
metadata.coverUrl?.let { coverUrl ->
|
||||||
if (!metadataMap.containsKey("coverImage")) {
|
if (!metadataMap.containsKey("coverImage")) {
|
||||||
mergedGame.coverImage = Image(originalUrl = coverUrl.toURL(), type = ImageType.COVER)
|
mergedGame.coverImage = Image(originalUrl = coverUrl.toURL(), type = ImageType.COVER)
|
||||||
metadataMap["coverImage"] = FieldMetadata(sourcePlugin)
|
metadataMap["coverImage"] = GameFieldMetadata(source = sourcePlugin)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
metadata.release?.let { release ->
|
metadata.release?.let { release ->
|
||||||
if (!metadataMap.containsKey("release")) {
|
if (!metadataMap.containsKey("release")) {
|
||||||
mergedGame.release = release
|
mergedGame.release = release
|
||||||
metadataMap["release"] = FieldMetadata(sourcePlugin)
|
metadataMap["release"] = GameFieldMetadata(source = sourcePlugin)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
metadata.userRating?.let { userRating ->
|
metadata.userRating?.let { userRating ->
|
||||||
if (!metadataMap.containsKey("userRating")) {
|
if (!metadataMap.containsKey("userRating")) {
|
||||||
mergedGame.userRating = userRating
|
mergedGame.userRating = userRating
|
||||||
metadataMap["userRating"] = FieldMetadata(sourcePlugin)
|
metadataMap["userRating"] = GameFieldMetadata(source = sourcePlugin)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
metadata.criticRating?.let { criticRating ->
|
metadata.criticRating?.let { criticRating ->
|
||||||
if (!metadataMap.containsKey("criticRating")) {
|
if (!metadataMap.containsKey("criticRating")) {
|
||||||
mergedGame.criticRating = criticRating
|
mergedGame.criticRating = criticRating
|
||||||
metadataMap["criticRating"] = FieldMetadata(sourcePlugin)
|
metadataMap["criticRating"] = GameFieldMetadata(source = sourcePlugin)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
metadata.publishedBy?.takeIf { it.isNotEmpty() }?.let { publishedBy ->
|
metadata.publishedBy?.takeIf { it.isNotEmpty() }?.let { publishedBy ->
|
||||||
if (!metadataMap.containsKey("publishers")) {
|
if (!metadataMap.containsKey("publishers")) {
|
||||||
mergedGame.publishers =
|
mergedGame.publishers =
|
||||||
publishedBy.map { Company(name = it, type = CompanyType.PUBLISHER) }
|
publishedBy.map { Company(name = it, type = CompanyType.PUBLISHER) }
|
||||||
metadataMap["publishers"] = FieldMetadata(sourcePlugin)
|
metadataMap["publishers"] = GameFieldMetadata(source = sourcePlugin)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
metadata.developedBy?.takeIf { it.isNotEmpty() }?.let { developedBy ->
|
metadata.developedBy?.takeIf { it.isNotEmpty() }?.let { developedBy ->
|
||||||
if (!metadataMap.containsKey("developers")) {
|
if (!metadataMap.containsKey("developers")) {
|
||||||
mergedGame.developers =
|
mergedGame.developers =
|
||||||
developedBy.map { Company(name = it, type = CompanyType.DEVELOPER) }
|
developedBy.map { Company(name = it, type = CompanyType.DEVELOPER) }
|
||||||
metadataMap["developers"] = FieldMetadata(sourcePlugin)
|
metadataMap["developers"] = GameFieldMetadata(source = sourcePlugin)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
metadata.genres?.takeIf { it.isNotEmpty() }?.let { genres ->
|
metadata.genres?.takeIf { it.isNotEmpty() }?.let { genres ->
|
||||||
if (!metadataMap.containsKey("genres")) {
|
if (!metadataMap.containsKey("genres")) {
|
||||||
mergedGame.genres = genres.toList()
|
mergedGame.genres = genres.toList()
|
||||||
metadataMap["genres"] = FieldMetadata(sourcePlugin)
|
metadataMap["genres"] = GameFieldMetadata(source = sourcePlugin)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
metadata.themes?.takeIf { it.isNotEmpty() }?.let { themes ->
|
metadata.themes?.takeIf { it.isNotEmpty() }?.let { themes ->
|
||||||
if (!metadataMap.containsKey("themes")) {
|
if (!metadataMap.containsKey("themes")) {
|
||||||
mergedGame.themes = themes.toList()
|
mergedGame.themes = themes.toList()
|
||||||
metadataMap["themes"] = FieldMetadata(sourcePlugin)
|
metadataMap["themes"] = GameFieldMetadata(source = sourcePlugin)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
metadata.keywords?.takeIf { it.isNotEmpty() }?.let { keywords ->
|
metadata.keywords?.takeIf { it.isNotEmpty() }?.let { keywords ->
|
||||||
if (!metadataMap.containsKey("keywords")) {
|
if (!metadataMap.containsKey("keywords")) {
|
||||||
mergedGame.keywords = keywords.toList()
|
mergedGame.keywords = keywords.toList()
|
||||||
metadataMap["keywords"] = FieldMetadata(sourcePlugin)
|
metadataMap["keywords"] = GameFieldMetadata(source = sourcePlugin)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
metadata.features?.takeIf { it.isNotEmpty() }?.let { features ->
|
metadata.features?.takeIf { it.isNotEmpty() }?.let { features ->
|
||||||
if (!metadataMap.containsKey("features")) {
|
if (!metadataMap.containsKey("features")) {
|
||||||
mergedGame.features = features.toList()
|
mergedGame.features = features.toList()
|
||||||
metadataMap["features"] = FieldMetadata(sourcePlugin)
|
metadataMap["features"] = GameFieldMetadata(source = sourcePlugin)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
metadata.perspectives?.takeIf { it.isNotEmpty() }?.let { perspectives ->
|
metadata.perspectives?.takeIf { it.isNotEmpty() }?.let { perspectives ->
|
||||||
if (!metadataMap.containsKey("perspectives")) {
|
if (!metadataMap.containsKey("perspectives")) {
|
||||||
mergedGame.perspectives = perspectives.toList()
|
mergedGame.perspectives = perspectives.toList()
|
||||||
metadataMap["perspectives"] = FieldMetadata(sourcePlugin)
|
metadataMap["perspectives"] = GameFieldMetadata(source = sourcePlugin)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
metadata.screenshotUrls?.takeIf { it.isNotEmpty() }?.let { screenshotUrls ->
|
metadata.screenshotUrls?.takeIf { it.isNotEmpty() }?.let { screenshotUrls ->
|
||||||
@@ -291,20 +288,20 @@ class GameService(
|
|||||||
mergedGame.images = runBlocking {
|
mergedGame.images = runBlocking {
|
||||||
screenshotUrls.map { Image(originalUrl = it.toURL(), type = ImageType.SCREENSHOT) }
|
screenshotUrls.map { Image(originalUrl = it.toURL(), type = ImageType.SCREENSHOT) }
|
||||||
}
|
}
|
||||||
metadataMap["images"] = FieldMetadata(sourcePlugin)
|
metadataMap["images"] = GameFieldMetadata(source = sourcePlugin)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
metadata.videoUrls?.takeIf { it.isNotEmpty() }?.let { videoUrls ->
|
metadata.videoUrls?.takeIf { it.isNotEmpty() }?.let { videoUrls ->
|
||||||
if (!metadataMap.containsKey("videoUrls")) {
|
if (!metadataMap.containsKey("videoUrls")) {
|
||||||
mergedGame.videoUrls = videoUrls.toList()
|
mergedGame.videoUrls = videoUrls.toList()
|
||||||
metadataMap["videoUrls"] = FieldMetadata(sourcePlugin)
|
metadataMap["videoUrls"] = GameFieldMetadata(source = sourcePlugin)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
mergedGame.metadata = metadataMap
|
mergedGame.metadata.fields = metadataMap
|
||||||
mergedGame.originalIds = originalIdsMap
|
mergedGame.metadata.originalIds = originalIdsMap
|
||||||
|
|
||||||
return mergedGame
|
return mergedGame
|
||||||
}
|
}
|
||||||
@@ -320,30 +317,29 @@ class GameService(
|
|||||||
|
|
||||||
fun Game.toDto(): GameDto {
|
fun Game.toDto(): GameDto {
|
||||||
// Helper functions
|
// Helper functions
|
||||||
fun toDto(metadata: FieldMetadata): GameMetadataDto {
|
fun toDto(fieldMetadata: GameFieldMetadata): GameFieldMetadataDto {
|
||||||
return GameMetadataDto(
|
return GameFieldMetadataDto(
|
||||||
source = metadata.source.pluginId,
|
source = fieldMetadata.source.pluginId,
|
||||||
lastUpdated = metadata.lastUpdated
|
updatedAt = fieldMetadata.updatedAt!!
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun toDto(metadata: Map<String, FieldMetadata>): Map<String, GameMetadataDto> {
|
fun toDto(metadata: GameMetadata): GameMetadataDto {
|
||||||
return metadata.mapValues { toDto(it.value) }
|
return GameMetadataDto(
|
||||||
|
fileSize = metadata.fileSize ?: 0L,
|
||||||
|
downloadCount = metadata.downloadCount,
|
||||||
|
path = metadata.path,
|
||||||
|
fields = metadata.fields.mapValues { toDto(it.value) },
|
||||||
|
originalIds = metadata.originalIds.mapKeys { it.key.pluginId }
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
val id = this.id ?: throw IllegalArgumentException("ID is null")
|
|
||||||
val createdAt = this.createdAt ?: throw IllegalArgumentException("creation timestamp is null")
|
|
||||||
val updatedAt = this.updatedAt ?: throw IllegalArgumentException("update timestamp is null")
|
|
||||||
val libraryId = this.library.id ?: throw IllegalArgumentException("library ID is null")
|
|
||||||
val title = this.title ?: throw IllegalArgumentException("title is null")
|
|
||||||
|
|
||||||
return GameDto(
|
return GameDto(
|
||||||
id = id,
|
id = id!!,
|
||||||
createdAt = createdAt,
|
createdAt = createdAt!!,
|
||||||
updatedAt = updatedAt,
|
updatedAt = updatedAt!!,
|
||||||
libraryId = libraryId,
|
libraryId = this.library.id!!,
|
||||||
title = title,
|
title = title!!,
|
||||||
coverId = this.coverImage?.id,
|
coverId = this.coverImage?.id,
|
||||||
comment = this.comment,
|
comment = this.comment,
|
||||||
summary = this.summary,
|
summary = this.summary,
|
||||||
@@ -359,9 +355,6 @@ fun Game.toDto(): GameDto {
|
|||||||
perspectives = this.perspectives?.map { it.name },
|
perspectives = this.perspectives?.map { it.name },
|
||||||
imageIds = this.images.mapNotNull { it.id },
|
imageIds = this.images.mapNotNull { it.id },
|
||||||
videoUrls = this.videoUrls.map { it.toString() },
|
videoUrls = this.videoUrls.map { it.toString() },
|
||||||
path = this.path,
|
metadata = toDto(this.metadata)
|
||||||
fileSize = this.fileSize ?: 0L,
|
|
||||||
metadata = toDto(this.metadata),
|
|
||||||
originalIds = this.originalIds.mapKeys { it.key.pluginId }
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@@ -1,7 +1,9 @@
|
|||||||
package de.grimsi.gameyfin.games.dto
|
package de.grimsi.gameyfin.games.dto
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.annotation.JsonInclude
|
||||||
import java.time.Instant
|
import java.time.Instant
|
||||||
|
|
||||||
|
@JsonInclude(JsonInclude.Include.NON_NULL)
|
||||||
class GameDto(
|
class GameDto(
|
||||||
val id: Long,
|
val id: Long,
|
||||||
val createdAt: Instant,
|
val createdAt: Instant,
|
||||||
@@ -23,8 +25,5 @@ class GameDto(
|
|||||||
val perspectives: List<String>?,
|
val perspectives: List<String>?,
|
||||||
val imageIds: List<Long>?,
|
val imageIds: List<Long>?,
|
||||||
val videoUrls: List<String>?,
|
val videoUrls: List<String>?,
|
||||||
val path: String,
|
val metadata: GameMetadataDto
|
||||||
val fileSize: Long,
|
|
||||||
val metadata: Map<String, GameMetadataDto>,
|
|
||||||
val originalIds: Map<String, String>
|
|
||||||
)
|
)
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
package de.grimsi.gameyfin.games.dto
|
||||||
|
|
||||||
|
import java.time.Instant
|
||||||
|
|
||||||
|
class GameFieldMetadataDto(
|
||||||
|
val source: String,
|
||||||
|
val updatedAt: Instant
|
||||||
|
)
|
||||||
@@ -1,8 +1,12 @@
|
|||||||
package de.grimsi.gameyfin.games.dto
|
package de.grimsi.gameyfin.games.dto
|
||||||
|
|
||||||
import java.time.Instant
|
import com.fasterxml.jackson.annotation.JsonInclude
|
||||||
|
|
||||||
|
@JsonInclude(JsonInclude.Include.NON_NULL)
|
||||||
class GameMetadataDto(
|
class GameMetadataDto(
|
||||||
val source: String,
|
val path: String?,
|
||||||
val lastUpdated: Instant
|
val fileSize: Long,
|
||||||
|
val fields: Map<String, GameFieldMetadataDto>?,
|
||||||
|
val originalIds: Map<String, String>?,
|
||||||
|
val downloadCount: Int
|
||||||
)
|
)
|
||||||
@@ -1,6 +1,5 @@
|
|||||||
package de.grimsi.gameyfin.games.entities
|
package de.grimsi.gameyfin.games.entities
|
||||||
|
|
||||||
import de.grimsi.gameyfin.core.plugins.management.PluginManagementEntry
|
|
||||||
import de.grimsi.gameyfin.libraries.Library
|
import de.grimsi.gameyfin.libraries.Library
|
||||||
import de.grimsi.gameyfin.pluginapi.gamemetadata.GameFeature
|
import de.grimsi.gameyfin.pluginapi.gamemetadata.GameFeature
|
||||||
import de.grimsi.gameyfin.pluginapi.gamemetadata.Genre
|
import de.grimsi.gameyfin.pluginapi.gamemetadata.Genre
|
||||||
@@ -10,6 +9,7 @@ import jakarta.persistence.*
|
|||||||
import org.hibernate.annotations.CreationTimestamp
|
import org.hibernate.annotations.CreationTimestamp
|
||||||
import org.hibernate.annotations.UpdateTimestamp
|
import org.hibernate.annotations.UpdateTimestamp
|
||||||
import java.net.URI
|
import java.net.URI
|
||||||
|
import java.nio.file.Path
|
||||||
import java.time.Instant
|
import java.time.Instant
|
||||||
|
|
||||||
@Entity
|
@Entity
|
||||||
@@ -76,16 +76,8 @@ class Game(
|
|||||||
@ElementCollection
|
@ElementCollection
|
||||||
var videoUrls: List<URI> = emptyList(),
|
var videoUrls: List<URI> = emptyList(),
|
||||||
|
|
||||||
@Column(unique = true)
|
@Embedded
|
||||||
val path: String,
|
var metadata: GameMetadata
|
||||||
|
) {
|
||||||
var fileSize: Long? = null,
|
constructor(path: Path, library: Library) : this(library = library, metadata = GameMetadata(path = path.toString()))
|
||||||
|
}
|
||||||
@OneToMany(cascade = [CascadeType.ALL], orphanRemoval = true)
|
|
||||||
var metadata: Map<String, FieldMetadata> = emptyMap(),
|
|
||||||
|
|
||||||
@ElementCollection
|
|
||||||
var originalIds: Map<PluginManagementEntry, String> = emptyMap(),
|
|
||||||
|
|
||||||
var downloadCount: Int = 0
|
|
||||||
)
|
|
||||||
+5
-5
@@ -2,10 +2,11 @@ package de.grimsi.gameyfin.games.entities
|
|||||||
|
|
||||||
import de.grimsi.gameyfin.core.plugins.management.PluginManagementEntry
|
import de.grimsi.gameyfin.core.plugins.management.PluginManagementEntry
|
||||||
import jakarta.persistence.*
|
import jakarta.persistence.*
|
||||||
|
import org.hibernate.annotations.UpdateTimestamp
|
||||||
import java.time.Instant
|
import java.time.Instant
|
||||||
|
|
||||||
@Entity
|
@Entity
|
||||||
class FieldMetadata(
|
class GameFieldMetadata(
|
||||||
@Id
|
@Id
|
||||||
@GeneratedValue(strategy = GenerationType.AUTO)
|
@GeneratedValue(strategy = GenerationType.AUTO)
|
||||||
var id: Long? = null,
|
var id: Long? = null,
|
||||||
@@ -13,7 +14,6 @@ class FieldMetadata(
|
|||||||
@ManyToOne
|
@ManyToOne
|
||||||
val source: PluginManagementEntry,
|
val source: PluginManagementEntry,
|
||||||
|
|
||||||
val lastUpdated: Instant
|
@UpdateTimestamp
|
||||||
) {
|
var updatedAt: Instant? = Instant.now()
|
||||||
constructor(source: PluginManagementEntry) : this(null, source, Instant.now())
|
)
|
||||||
}
|
|
||||||
@@ -0,0 +1,20 @@
|
|||||||
|
package de.grimsi.gameyfin.games.entities
|
||||||
|
|
||||||
|
import de.grimsi.gameyfin.core.plugins.management.PluginManagementEntry
|
||||||
|
import jakarta.persistence.*
|
||||||
|
|
||||||
|
@Embeddable
|
||||||
|
class GameMetadata(
|
||||||
|
@Column(unique = true)
|
||||||
|
val path: String,
|
||||||
|
|
||||||
|
var fileSize: Long? = null,
|
||||||
|
|
||||||
|
@OneToMany(cascade = [CascadeType.ALL], orphanRemoval = true, fetch = FetchType.EAGER)
|
||||||
|
var fields: Map<String, GameFieldMetadata> = emptyMap(),
|
||||||
|
|
||||||
|
@ElementCollection
|
||||||
|
var originalIds: Map<PluginManagementEntry, String> = emptyMap(),
|
||||||
|
|
||||||
|
var downloadCount: Int = 0
|
||||||
|
)
|
||||||
+2
-2
@@ -1,6 +1,6 @@
|
|||||||
package de.grimsi.gameyfin.games.repositories
|
package de.grimsi.gameyfin.games.repositories
|
||||||
|
|
||||||
import de.grimsi.gameyfin.games.entities.FieldMetadata
|
import de.grimsi.gameyfin.games.entities.GameFieldMetadata
|
||||||
import org.springframework.data.jpa.repository.JpaRepository
|
import org.springframework.data.jpa.repository.JpaRepository
|
||||||
|
|
||||||
interface FieldMetadataRepository : JpaRepository<FieldMetadata, Long>
|
interface FieldMetadataRepository : JpaRepository<GameFieldMetadata, Long>
|
||||||
@@ -5,8 +5,8 @@ import org.springframework.data.domain.Limit
|
|||||||
import org.springframework.data.jpa.repository.JpaRepository
|
import org.springframework.data.jpa.repository.JpaRepository
|
||||||
|
|
||||||
interface GameRepository : JpaRepository<Game, Long> {
|
interface GameRepository : JpaRepository<Game, Long> {
|
||||||
fun findByPath(path: String): Game?
|
fun findByMetadata_Path(path: String): Game?
|
||||||
fun findAllByPathIn(paths: List<String>): List<Game>
|
fun findAllByMetadata_PathIn(paths: List<String>): List<Game>
|
||||||
fun findByOrderByCreatedAtDesc(limit: Limit): List<Game>
|
fun findByOrderByCreatedAtDesc(limit: Limit): List<Game>
|
||||||
fun findByOrderByUpdatedAtDesc(limit: Limit): List<Game>
|
fun findByOrderByUpdatedAtDesc(limit: Limit): List<Game>
|
||||||
}
|
}
|
||||||
@@ -256,9 +256,9 @@ class LibraryService(
|
|||||||
|
|
||||||
val calculateFileSizeTask = gamesWithImages.map { game ->
|
val calculateFileSizeTask = gamesWithImages.map { game ->
|
||||||
Callable {
|
Callable {
|
||||||
game.path.let { path ->
|
game.metadata.path.let { path ->
|
||||||
val fileSize = filesystemService.calculateFileSize(path)
|
val fileSize = filesystemService.calculateFileSize(path)
|
||||||
game.fileSize = fileSize
|
game.metadata.fileSize = fileSize
|
||||||
|
|
||||||
progress.currentStep.current = calculatedFileSize.incrementAndGet()
|
progress.currentStep.current = calculatedFileSize.incrementAndGet()
|
||||||
emit(progress)
|
emit(progress)
|
||||||
@@ -324,21 +324,19 @@ class LibraryService(
|
|||||||
name = library.name,
|
name = library.name,
|
||||||
directories = library.directories.map {
|
directories = library.directories.map {
|
||||||
DirectoryMapping(internalPath = it.internalPath, externalPath = it.externalPath)
|
DirectoryMapping(internalPath = it.internalPath, externalPath = it.externalPath)
|
||||||
}.toMutableList()
|
}.toMutableList(),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun Library.toDto(): LibraryDto {
|
fun Library.toDto(): LibraryDto {
|
||||||
val libraryId = this.id ?: throw IllegalArgumentException("Library ID is null")
|
|
||||||
|
|
||||||
val statsDto = LibraryStatsDto(
|
val statsDto = LibraryStatsDto(
|
||||||
gamesCount = this.games.size,
|
gamesCount = this.games.size,
|
||||||
downloadedGamesCount = this.games.sumOf { it.downloadCount }
|
downloadedGamesCount = this.games.sumOf { it.metadata.downloadCount }
|
||||||
)
|
)
|
||||||
|
|
||||||
return LibraryDto(
|
return LibraryDto(
|
||||||
id = libraryId,
|
id = this.id!!,
|
||||||
name = this.name,
|
name = this.name,
|
||||||
directories = this.directories.map { DirectoryMappingDto(it.internalPath, it.externalPath) },
|
directories = this.directories.map { DirectoryMappingDto(it.internalPath, it.externalPath) },
|
||||||
games = this.games.mapNotNull { it.id },
|
games = this.games.mapNotNull { it.id },
|
||||||
|
|||||||
Reference in New Issue
Block a user