Update Plugin API (and plugins)

Fix issue with persisting new games
This commit is contained in:
grimsi
2024-12-21 23:43:38 +01:00
parent 890748fb7c
commit eb14007190
14 changed files with 96 additions and 49 deletions
@@ -1,6 +0,0 @@
package de.grimsi.gameyfin.games
data class GameDto(
val id: Long,
val title: String,
)
@@ -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<GameMetadataProvider, GameMetadata?> = 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<GameDto> {
@@ -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)
}
}
@@ -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<String>?,
val developers: List<String>?,
val genres: List<String>?,
val themes: List<String>?,
val keywords: List<String>?,
val features: List<String>?,
val perspectives: List<String>?,
val images: List<String>?,
val videoUrls: List<String>?,
val source: String?
)
@@ -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<Company>,
@OneToMany(cascade = [CascadeType.MERGE])
@ManyToMany(cascade = [CascadeType.MERGE])
val developers: Set<Company>,
@ElementCollection
@@ -49,7 +52,7 @@ class Game(
val perspectives: Set<PlayerPerspective>,
@OneToMany(cascade = [CascadeType.MERGE])
val screenshots: Set<Screenshot>,
val images: Set<Image>,
@ElementCollection
val videoUrls: Set<URL>,
@@ -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
)
)
enum class ImageType {
COVER,
SCREENSHOT
}
@@ -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<Screenshot, String>
interface ImageContentStore : ContentStore<Image, String>
@@ -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<Image, Long> {
fun findByOriginalUrl(originalUrl: URL): Image?
}
@@ -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<Screenshot, Long> {
fun findByOriginalUrl(originalUrl: URL): Screenshot?
}
@@ -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)
}
}
@@ -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))
}
@@ -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?,
@@ -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(),
@@ -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 {
@@ -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,