From eb14007190baf49312f8652b2d0dd4d33ba7dfd9 Mon Sep 17 00:00:00 2001 From: grimsi <9295182+grimsi@users.noreply.github.com> Date: Sat, 21 Dec 2024 23:43:38 +0100 Subject: [PATCH] Update Plugin API (and plugins) Fix issue with persisting new games --- .../de/grimsi/gameyfin/games/GameDto.kt | 6 -- .../de/grimsi/gameyfin/games/GameService.kt | 57 ++++++++++++------- .../de/grimsi/gameyfin/games/dto/GameDto.kt | 22 +++++++ .../de/grimsi/gameyfin/games/entities/Game.kt | 9 ++- .../entities/{Screenshot.kt => Image.kt} | 11 +++- ...otContentStore.kt => ImageContentStore.kt} | 4 +- .../games/repositories/ImageRepository.kt | 9 +++ .../repositories/ScreenshotRepository.kt | 9 --- .../gameyfin/libraries/LibraryEndpoint.kt | 4 +- .../gameyfin/libraries/LibraryService.kt | 4 +- .../pluginapi/gamemetadata/GameMetadata.kt | 1 + .../gameyfin/plugins/igdb/IgdbPlugin.kt | 1 + .../de/grimsi/gameyfin/plugins/igdb/Mapper.kt | 6 +- .../gameyfin/plugins/steam/SteamPlugin.kt | 2 + 14 files changed, 96 insertions(+), 49 deletions(-) delete mode 100644 gameyfin/src/main/kotlin/de/grimsi/gameyfin/games/GameDto.kt create mode 100644 gameyfin/src/main/kotlin/de/grimsi/gameyfin/games/dto/GameDto.kt rename gameyfin/src/main/kotlin/de/grimsi/gameyfin/games/entities/{Screenshot.kt => Image.kt} (89%) rename gameyfin/src/main/kotlin/de/grimsi/gameyfin/games/repositories/{ScreenshotContentStore.kt => ImageContentStore.kt} (58%) create mode 100644 gameyfin/src/main/kotlin/de/grimsi/gameyfin/games/repositories/ImageRepository.kt delete mode 100644 gameyfin/src/main/kotlin/de/grimsi/gameyfin/games/repositories/ScreenshotRepository.kt diff --git a/gameyfin/src/main/kotlin/de/grimsi/gameyfin/games/GameDto.kt b/gameyfin/src/main/kotlin/de/grimsi/gameyfin/games/GameDto.kt deleted file mode 100644 index 1ad634c..0000000 --- a/gameyfin/src/main/kotlin/de/grimsi/gameyfin/games/GameDto.kt +++ /dev/null @@ -1,6 +0,0 @@ -package de.grimsi.gameyfin.games - -data class GameDto( - val id: Long, - val title: String, -) \ No newline at end of file diff --git a/gameyfin/src/main/kotlin/de/grimsi/gameyfin/games/GameService.kt b/gameyfin/src/main/kotlin/de/grimsi/gameyfin/games/GameService.kt index 126ccbf..c0063f7 100644 --- a/gameyfin/src/main/kotlin/de/grimsi/gameyfin/games/GameService.kt +++ b/gameyfin/src/main/kotlin/de/grimsi/gameyfin/games/GameService.kt @@ -1,14 +1,12 @@ package de.grimsi.gameyfin.games import de.grimsi.gameyfin.core.plugins.management.PluginManagementService -import de.grimsi.gameyfin.games.entities.Company -import de.grimsi.gameyfin.games.entities.CompanyType -import de.grimsi.gameyfin.games.entities.Game -import de.grimsi.gameyfin.games.entities.Screenshot +import de.grimsi.gameyfin.games.dto.GameDto +import de.grimsi.gameyfin.games.entities.* import de.grimsi.gameyfin.games.repositories.CompanyRepository import de.grimsi.gameyfin.games.repositories.GameRepository -import de.grimsi.gameyfin.games.repositories.ScreenshotContentStore -import de.grimsi.gameyfin.games.repositories.ScreenshotRepository +import de.grimsi.gameyfin.games.repositories.ImageContentStore +import de.grimsi.gameyfin.games.repositories.ImageRepository import de.grimsi.gameyfin.pluginapi.gamemetadata.GameMetadata import de.grimsi.gameyfin.pluginapi.gamemetadata.GameMetadataProvider import io.github.oshai.kotlinlogging.KotlinLogging @@ -28,8 +26,8 @@ class GameService( private val pluginManagementService: PluginManagementService, private val gameRepository: GameRepository, private val companyRepository: CompanyRepository, - private val screenshotRepository: ScreenshotRepository, - private val screenshotContentStore: ScreenshotContentStore + private val imageRepository: ImageRepository, + private val imageContentStore: ImageContentStore ) { private val log = KotlinLogging.logger {} @@ -43,7 +41,7 @@ class GameService( return gameRepository.save(game) } - fun createFromFile(path: Path): Game { + fun createFromFile(path: Path): GameDto { val metadataResults: Map = runBlocking { coroutineScope { metadataPlugins.associateWith { @@ -66,9 +64,10 @@ class GameService( throw NoMatchException("Plugin ${plugin.javaClass} returned invalid metadata for game at $path") } - val game = toEntity(metadata, path, plugin) - - return createOrUpdate(game) + var game = toEntity(metadata, path, plugin) + game = createOrUpdate(game) + + return toDto(game) } fun getAllGames(): Collection { @@ -89,7 +88,21 @@ class GameService( return GameDto( id = gameId, - title = game.title + title = game.title, + coverImageUrl = game.coverImage.contentId, + comment = game.comment, + summary = game.summary, + release = game.release, + publishers = game.publishers.map { it.name }, + developers = game.developers.map { it.name }, + genres = game.genres.map { it.name }, + themes = game.themes.map { it.name }, + keywords = game.keywords.toList(), + features = game.features.map { it.name }, + perspectives = game.perspectives.map { it.name }, + images = game.images.mapNotNull { it.contentId }, + videoUrls = game.videoUrls.map { it.toString() }, + source = game.source.pluginId ) } @@ -97,6 +110,7 @@ class GameService( return Game( title = metadata.title, summary = metadata.description, + coverImage = downloadAndPersist(metadata.coverUrl, ImageType.COVER), release = metadata.release, publishers = metadata.publishedBy.map { toEntity(it, CompanyType.PUBLISHER) }.toSet(), developers = metadata.developedBy.map { toEntity(it, CompanyType.DEVELOPER) }.toSet(), @@ -105,7 +119,7 @@ class GameService( keywords = metadata.keywords, features = metadata.features, perspectives = metadata.perspectives, - screenshots = metadata.screenshotUrls.map { downloadAndPersist(it) }.toSet(), + images = metadata.screenshotUrls.map { downloadAndPersist(it, ImageType.SCREENSHOT) }.toSet(), videoUrls = metadata.videoUrls, path = path.toString(), source = pluginManagementService.getPluginManagementEntry(source.javaClass) @@ -118,15 +132,14 @@ class GameService( return companyRepository.save(company) } - private fun downloadAndPersist(screenshotUrl: URL): Screenshot { - screenshotRepository.findByOriginalUrl(screenshotUrl)?.let { return it } + private fun downloadAndPersist(imageUrl: URL, type: ImageType): Image { + imageRepository.findByOriginalUrl(imageUrl)?.let { return it } - val screenshot = Screenshot(originalUrl = screenshotUrl) - screenshotUrl.openStream().use { input -> - val mimeType = URLConnection.guessContentTypeFromStream(input) - screenshot.mimeType = mimeType - screenshotContentStore.setContent(screenshot, input) + val image = Image(originalUrl = imageUrl, type = type) + imageUrl.openStream().use { input -> + image.mimeType = URLConnection.guessContentTypeFromStream(input) + imageContentStore.setContent(image, input) } - return screenshotRepository.save(screenshot) + return imageRepository.save(image) } } \ No newline at end of file diff --git a/gameyfin/src/main/kotlin/de/grimsi/gameyfin/games/dto/GameDto.kt b/gameyfin/src/main/kotlin/de/grimsi/gameyfin/games/dto/GameDto.kt new file mode 100644 index 0000000..e4e8905 --- /dev/null +++ b/gameyfin/src/main/kotlin/de/grimsi/gameyfin/games/dto/GameDto.kt @@ -0,0 +1,22 @@ +package de.grimsi.gameyfin.games.dto + +import java.time.Instant + +class GameDto( + val id: Long, + val title: String, + val coverImageUrl: String?, + val comment: String?, + val summary: String?, + val release: Instant?, + val publishers: List?, + val developers: List?, + val genres: List?, + val themes: List?, + val keywords: List?, + val features: List?, + val perspectives: List?, + val images: List?, + val videoUrls: List?, + val source: String? +) \ No newline at end of file diff --git a/gameyfin/src/main/kotlin/de/grimsi/gameyfin/games/entities/Game.kt b/gameyfin/src/main/kotlin/de/grimsi/gameyfin/games/entities/Game.kt index f59c7d1..479418b 100644 --- a/gameyfin/src/main/kotlin/de/grimsi/gameyfin/games/entities/Game.kt +++ b/gameyfin/src/main/kotlin/de/grimsi/gameyfin/games/entities/Game.kt @@ -17,6 +17,9 @@ class Game( val title: String, + @OneToOne(cascade = [CascadeType.MERGE]) + val coverImage: Image, + @Lob @Column(columnDefinition = "CLOB") val comment: String? = null, @@ -27,10 +30,10 @@ class Game( val release: Instant, - @OneToMany(cascade = [CascadeType.MERGE]) + @ManyToMany(cascade = [CascadeType.MERGE]) val publishers: Set, - @OneToMany(cascade = [CascadeType.MERGE]) + @ManyToMany(cascade = [CascadeType.MERGE]) val developers: Set, @ElementCollection @@ -49,7 +52,7 @@ class Game( val perspectives: Set, @OneToMany(cascade = [CascadeType.MERGE]) - val screenshots: Set, + val images: Set, @ElementCollection val videoUrls: Set, diff --git a/gameyfin/src/main/kotlin/de/grimsi/gameyfin/games/entities/Screenshot.kt b/gameyfin/src/main/kotlin/de/grimsi/gameyfin/games/entities/Image.kt similarity index 89% rename from gameyfin/src/main/kotlin/de/grimsi/gameyfin/games/entities/Screenshot.kt rename to gameyfin/src/main/kotlin/de/grimsi/gameyfin/games/entities/Image.kt index c85ee2a..f0a8acf 100644 --- a/gameyfin/src/main/kotlin/de/grimsi/gameyfin/games/entities/Screenshot.kt +++ b/gameyfin/src/main/kotlin/de/grimsi/gameyfin/games/entities/Image.kt @@ -11,13 +11,15 @@ import org.springframework.content.commons.annotations.MimeType import java.net.URL @Entity -class Screenshot( +class Image( @Id @GeneratedValue(strategy = GenerationType.AUTO) var id: Long? = null, val originalUrl: URL, + val type: ImageType, + @ContentId @Nullable var contentId: String? = null, @@ -29,4 +31,9 @@ class Screenshot( @MimeType @Nullable var mimeType: String? = null -) \ No newline at end of file +) + +enum class ImageType { + COVER, + SCREENSHOT +} \ No newline at end of file diff --git a/gameyfin/src/main/kotlin/de/grimsi/gameyfin/games/repositories/ScreenshotContentStore.kt b/gameyfin/src/main/kotlin/de/grimsi/gameyfin/games/repositories/ImageContentStore.kt similarity index 58% rename from gameyfin/src/main/kotlin/de/grimsi/gameyfin/games/repositories/ScreenshotContentStore.kt rename to gameyfin/src/main/kotlin/de/grimsi/gameyfin/games/repositories/ImageContentStore.kt index f1e2ced..f793d3a 100644 --- a/gameyfin/src/main/kotlin/de/grimsi/gameyfin/games/repositories/ScreenshotContentStore.kt +++ b/gameyfin/src/main/kotlin/de/grimsi/gameyfin/games/repositories/ImageContentStore.kt @@ -1,8 +1,8 @@ package de.grimsi.gameyfin.games.repositories -import de.grimsi.gameyfin.games.entities.Screenshot +import de.grimsi.gameyfin.games.entities.Image import org.springframework.content.commons.store.ContentStore import org.springframework.stereotype.Repository @Repository -interface ScreenshotContentStore : ContentStore \ No newline at end of file +interface ImageContentStore : ContentStore \ No newline at end of file diff --git a/gameyfin/src/main/kotlin/de/grimsi/gameyfin/games/repositories/ImageRepository.kt b/gameyfin/src/main/kotlin/de/grimsi/gameyfin/games/repositories/ImageRepository.kt new file mode 100644 index 0000000..f0d134b --- /dev/null +++ b/gameyfin/src/main/kotlin/de/grimsi/gameyfin/games/repositories/ImageRepository.kt @@ -0,0 +1,9 @@ +package de.grimsi.gameyfin.games.repositories + +import de.grimsi.gameyfin.games.entities.Image +import org.springframework.data.jpa.repository.JpaRepository +import java.net.URL + +interface ImageRepository : JpaRepository { + fun findByOriginalUrl(originalUrl: URL): Image? +} \ No newline at end of file diff --git a/gameyfin/src/main/kotlin/de/grimsi/gameyfin/games/repositories/ScreenshotRepository.kt b/gameyfin/src/main/kotlin/de/grimsi/gameyfin/games/repositories/ScreenshotRepository.kt deleted file mode 100644 index 132b94b..0000000 --- a/gameyfin/src/main/kotlin/de/grimsi/gameyfin/games/repositories/ScreenshotRepository.kt +++ /dev/null @@ -1,9 +0,0 @@ -package de.grimsi.gameyfin.games.repositories - -import de.grimsi.gameyfin.games.entities.Screenshot -import org.springframework.data.jpa.repository.JpaRepository -import java.net.URL - -interface ScreenshotRepository : JpaRepository { - fun findByOriginalUrl(originalUrl: URL): Screenshot? -} \ No newline at end of file diff --git a/gameyfin/src/main/kotlin/de/grimsi/gameyfin/libraries/LibraryEndpoint.kt b/gameyfin/src/main/kotlin/de/grimsi/gameyfin/libraries/LibraryEndpoint.kt index d05e2d1..3dc9912 100644 --- a/gameyfin/src/main/kotlin/de/grimsi/gameyfin/libraries/LibraryEndpoint.kt +++ b/gameyfin/src/main/kotlin/de/grimsi/gameyfin/libraries/LibraryEndpoint.kt @@ -2,7 +2,7 @@ package de.grimsi.gameyfin.libraries import com.vaadin.hilla.Endpoint import de.grimsi.gameyfin.core.Role -import de.grimsi.gameyfin.games.entities.Game +import de.grimsi.gameyfin.games.dto.GameDto import jakarta.annotation.security.RolesAllowed @Endpoint @@ -20,7 +20,7 @@ class LibraryEndpoint( } @RolesAllowed(Role.Names.ADMIN) - fun test(testString: String): Game { + fun test(testString: String): GameDto { return libraryService.test(testString) } } \ No newline at end of file diff --git a/gameyfin/src/main/kotlin/de/grimsi/gameyfin/libraries/LibraryService.kt b/gameyfin/src/main/kotlin/de/grimsi/gameyfin/libraries/LibraryService.kt index 4232b58..0406e47 100644 --- a/gameyfin/src/main/kotlin/de/grimsi/gameyfin/libraries/LibraryService.kt +++ b/gameyfin/src/main/kotlin/de/grimsi/gameyfin/libraries/LibraryService.kt @@ -3,7 +3,7 @@ package de.grimsi.gameyfin.libraries import de.grimsi.gameyfin.config.ConfigProperties import de.grimsi.gameyfin.config.ConfigService import de.grimsi.gameyfin.games.GameService -import de.grimsi.gameyfin.games.entities.Game +import de.grimsi.gameyfin.games.dto.GameDto import org.springframework.data.repository.findByIdOrNull import org.springframework.stereotype.Service import java.nio.file.Path @@ -18,7 +18,7 @@ class LibraryService( private val gameService: GameService, private val config: ConfigService ) { - fun test(testString: String): Game { + fun test(testString: String): GameDto { return gameService.createFromFile(Path(testString)) } diff --git a/plugin-api/src/main/kotlin/de/grimsi/gameyfin/pluginapi/gamemetadata/GameMetadata.kt b/plugin-api/src/main/kotlin/de/grimsi/gameyfin/pluginapi/gamemetadata/GameMetadata.kt index c389173..a360ac3 100644 --- a/plugin-api/src/main/kotlin/de/grimsi/gameyfin/pluginapi/gamemetadata/GameMetadata.kt +++ b/plugin-api/src/main/kotlin/de/grimsi/gameyfin/pluginapi/gamemetadata/GameMetadata.kt @@ -6,6 +6,7 @@ import java.time.Instant class GameMetadata( val title: String, val description: String, + val coverUrl: URL, val release: Instant, val userRating: Int?, val criticRating: Int?, diff --git a/plugins/igdb/src/main/kotlin/de/grimsi/gameyfin/plugins/igdb/IgdbPlugin.kt b/plugins/igdb/src/main/kotlin/de/grimsi/gameyfin/plugins/igdb/IgdbPlugin.kt index 81bfa72..cc718f5 100644 --- a/plugins/igdb/src/main/kotlin/de/grimsi/gameyfin/plugins/igdb/IgdbPlugin.kt +++ b/plugins/igdb/src/main/kotlin/de/grimsi/gameyfin/plugins/igdb/IgdbPlugin.kt @@ -122,6 +122,7 @@ class IgdbPlugin(wrapper: PluginWrapper) : GameyfinPlugin(wrapper) { return GameMetadata( title = game.name, description = game.summary, + coverUrl = Mapper.cover(game.cover), release = Instant.ofEpochSecond(game.firstReleaseDate.seconds), userRating = game.rating.toInt(), criticRating = game.aggregatedRating.toInt(), diff --git a/plugins/igdb/src/main/kotlin/de/grimsi/gameyfin/plugins/igdb/Mapper.kt b/plugins/igdb/src/main/kotlin/de/grimsi/gameyfin/plugins/igdb/Mapper.kt index 76b5d73..5657f57 100644 --- a/plugins/igdb/src/main/kotlin/de/grimsi/gameyfin/plugins/igdb/Mapper.kt +++ b/plugins/igdb/src/main/kotlin/de/grimsi/gameyfin/plugins/igdb/Mapper.kt @@ -95,7 +95,11 @@ class Mapper { } fun screenshot(screenshot: proto.Screenshot): URL { - return URI(imageBuilder(screenshot.imageId, ImageSize.SCREENSHOT_HUGE, ImageType.PNG)).toURL() + return URI(imageBuilder(screenshot.imageId, ImageSize.FHD, ImageType.PNG)).toURL() + } + + fun cover(cover: proto.Cover): URL { + return URI(imageBuilder(cover.imageId, ImageSize.COVER_BIG, ImageType.PNG)).toURL() } fun video(video: proto.GameVideo): URL { diff --git a/plugins/steam/src/main/kotlin/de/grimsi/gameyfin/plugins/steam/SteamPlugin.kt b/plugins/steam/src/main/kotlin/de/grimsi/gameyfin/plugins/steam/SteamPlugin.kt index 6dd1a0c..c3fc5d4 100644 --- a/plugins/steam/src/main/kotlin/de/grimsi/gameyfin/plugins/steam/SteamPlugin.kt +++ b/plugins/steam/src/main/kotlin/de/grimsi/gameyfin/plugins/steam/SteamPlugin.kt @@ -18,6 +18,7 @@ import org.pf4j.Extension import org.pf4j.PluginWrapper import org.slf4j.Logger import org.slf4j.LoggerFactory +import java.net.URI import java.net.URLEncoder import java.nio.charset.StandardCharsets import java.time.Instant @@ -109,6 +110,7 @@ class SteamPlugin(wrapper: PluginWrapper) : GameyfinPlugin(wrapper) { val metadata = GameMetadata( title = string(game, "name"), description = string(game, "detailed_description"), + coverUrl = URI("").toURL(), release = date(game["release_date"]?.jsonObject["date"]?.jsonPrimitive?.content!!), userRating = 0, criticRating = 0,