mirror of
https://github.com/BrenBroZAYT/gameyfin.git
synced 2026-06-16 16:20:04 +00:00
Implement field metadata
Implement result merging for GameMetadata
This commit is contained in:
+1
-1
@@ -10,5 +10,5 @@ data class PluginManagementEntry(
|
|||||||
|
|
||||||
var enabled: Boolean = true,
|
var enabled: Boolean = true,
|
||||||
|
|
||||||
var priority: Int = Int.MAX_VALUE
|
var priority: Int = 0
|
||||||
)
|
)
|
||||||
@@ -44,18 +44,19 @@ class GameService(
|
|||||||
|
|
||||||
fun createFromFile(path: Path): GameDto {
|
fun createFromFile(path: Path): GameDto {
|
||||||
val metadataResults = queryPlugins(path.fileName.toString())
|
val metadataResults = queryPlugins(path.fileName.toString())
|
||||||
|
val validResults = metadataResults.filterValues { it != null }
|
||||||
val (plugin, metadata) = metadataResults.entries.firstOrNull { it.value != null }
|
if (validResults.isEmpty()) {
|
||||||
?: throw NoMatchException("Could not match game at $path")
|
throw NoMatchException("Could not match game at $path")
|
||||||
|
|
||||||
if (metadata == null) {
|
|
||||||
throw NoMatchException("Plugin ${plugin.javaClass} returned invalid metadata for game at $path")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
var game = toEntity(metadata, path, plugin)
|
val sortedResults = validResults.entries.sortedByDescending {
|
||||||
game = createOrUpdate(game)
|
pluginManagementService.getPluginManagementEntry(it.key.javaClass).priority
|
||||||
|
}
|
||||||
|
|
||||||
return toDto(game)
|
val mergedGame = mergeResults(sortedResults, path)
|
||||||
|
val savedGame = createOrUpdate(mergedGame)
|
||||||
|
|
||||||
|
return toDto(savedGame)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun getAllGames(): Collection<GameDto> {
|
fun getAllGames(): Collection<GameDto> {
|
||||||
@@ -93,12 +94,112 @@ class GameService(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun mergeResults(results: List<Map.Entry<GameMetadataProvider, GameMetadata?>>, path: Path): Game {
|
||||||
|
val mergedGame = Game(path = path.toString())
|
||||||
|
val metadataMap = mutableMapOf<String, FieldMetadata>()
|
||||||
|
|
||||||
|
// Sort results by plugin priority
|
||||||
|
val sortedResults = results.sortedByDescending {
|
||||||
|
pluginManagementService.getPluginManagementEntry(it.key.javaClass).priority
|
||||||
|
}
|
||||||
|
|
||||||
|
sortedResults.forEach { (provider, metadata) ->
|
||||||
|
val sourcePlugin = pluginManagementService.getPluginManagementEntry(provider.javaClass)
|
||||||
|
metadata?.let {
|
||||||
|
it.title.takeIf { it.isNotBlank() }?.let { title ->
|
||||||
|
if (!metadataMap.containsKey("title")) {
|
||||||
|
mergedGame.title = title
|
||||||
|
metadataMap["title"] = FieldMetadata(sourcePlugin)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
it.description?.takeIf { it.isNotBlank() }?.let { description ->
|
||||||
|
if (!metadataMap.containsKey("summary")) {
|
||||||
|
mergedGame.summary = description
|
||||||
|
metadataMap["summary"] = FieldMetadata(sourcePlugin)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
it.coverUrl?.let { coverUrl ->
|
||||||
|
if (!metadataMap.containsKey("coverImage")) {
|
||||||
|
mergedGame.coverImage = downloadAndPersist(coverUrl, ImageType.COVER)
|
||||||
|
metadataMap["coverImage"] = FieldMetadata(sourcePlugin)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
it.release?.let { release ->
|
||||||
|
if (!metadataMap.containsKey("release")) {
|
||||||
|
mergedGame.release = release
|
||||||
|
metadataMap["release"] = FieldMetadata(sourcePlugin)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
it.publishedBy?.takeIf { it.isNotEmpty() }?.let { publishedBy ->
|
||||||
|
if (!metadataMap.containsKey("publishers")) {
|
||||||
|
mergedGame.publishers =
|
||||||
|
publishedBy.map { name -> toEntity(name, CompanyType.PUBLISHER) }.toSet()
|
||||||
|
metadataMap["publishers"] = FieldMetadata(sourcePlugin)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
it.developedBy?.takeIf { it.isNotEmpty() }?.let { developedBy ->
|
||||||
|
if (!metadataMap.containsKey("developers")) {
|
||||||
|
mergedGame.developers =
|
||||||
|
developedBy.map { name -> toEntity(name, CompanyType.DEVELOPER) }.toSet()
|
||||||
|
metadataMap["developers"] = FieldMetadata(sourcePlugin)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
it.genres?.takeIf { it.isNotEmpty() }?.let { genres ->
|
||||||
|
if (!metadataMap.containsKey("genres")) {
|
||||||
|
mergedGame.genres = genres
|
||||||
|
metadataMap["genres"] = FieldMetadata(sourcePlugin)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
it.themes?.takeIf { it.isNotEmpty() }?.let { themes ->
|
||||||
|
if (!metadataMap.containsKey("themes")) {
|
||||||
|
mergedGame.themes = themes
|
||||||
|
metadataMap["themes"] = FieldMetadata(sourcePlugin)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
it.keywords?.takeIf { it.isNotEmpty() }?.let { keywords ->
|
||||||
|
if (!metadataMap.containsKey("keywords")) {
|
||||||
|
mergedGame.keywords = keywords
|
||||||
|
metadataMap["keywords"] = FieldMetadata(sourcePlugin)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
it.features?.takeIf { it.isNotEmpty() }?.let { features ->
|
||||||
|
if (!metadataMap.containsKey("features")) {
|
||||||
|
mergedGame.features = features
|
||||||
|
metadataMap["features"] = FieldMetadata(sourcePlugin)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
it.perspectives?.takeIf { it.isNotEmpty() }?.let { perspectives ->
|
||||||
|
if (!metadataMap.containsKey("perspectives")) {
|
||||||
|
mergedGame.perspectives = perspectives
|
||||||
|
metadataMap["perspectives"] = FieldMetadata(sourcePlugin)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
it.screenshotUrls?.takeIf { it.isNotEmpty() }?.let { screenshotUrls ->
|
||||||
|
if (!metadataMap.containsKey("images")) {
|
||||||
|
mergedGame.images =
|
||||||
|
screenshotUrls.map { url -> downloadAndPersist(url, ImageType.SCREENSHOT) }.toSet()
|
||||||
|
metadataMap["images"] = FieldMetadata(sourcePlugin)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
it.videoUrls?.takeIf { it.isNotEmpty() }?.let { videoUrls ->
|
||||||
|
if (!metadataMap.containsKey("videoUrls")) {
|
||||||
|
mergedGame.videoUrls = videoUrls
|
||||||
|
metadataMap["videoUrls"] = FieldMetadata(sourcePlugin)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
mergedGame.metadata = metadataMap
|
||||||
|
return mergedGame
|
||||||
|
}
|
||||||
|
|
||||||
private fun toDto(game: Game): GameDto {
|
private fun toDto(game: Game): GameDto {
|
||||||
val gameId = game.id ?: throw IllegalArgumentException("Game ID is null")
|
val gameId = game.id ?: throw IllegalArgumentException("Game ID is null")
|
||||||
|
|
||||||
return GameDto(
|
return GameDto(
|
||||||
id = gameId,
|
id = gameId,
|
||||||
title = game.title,
|
title = game.title!!,
|
||||||
coverId = game.coverImage?.id,
|
coverId = game.coverImage?.id,
|
||||||
comment = game.comment,
|
comment = game.comment,
|
||||||
summary = game.summary,
|
summary = game.summary,
|
||||||
@@ -128,28 +229,6 @@ class GameService(
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun toEntity(metadata: GameMetadata, path: Path, source: GameMetadataProvider): Game {
|
|
||||||
val sourcePlugin = pluginManagementService.getPluginManagementEntry(source.javaClass)
|
|
||||||
|
|
||||||
return Game(
|
|
||||||
title = metadata.title,
|
|
||||||
summary = metadata.description,
|
|
||||||
coverImage = metadata.coverUrl?.let { downloadAndPersist(it, ImageType.COVER) },
|
|
||||||
release = metadata.release,
|
|
||||||
publishers = metadata.publishedBy?.map { toEntity(it, CompanyType.PUBLISHER) }?.toSet(),
|
|
||||||
developers = metadata.developedBy?.map { toEntity(it, CompanyType.DEVELOPER) }?.toSet(),
|
|
||||||
genres = metadata.genres,
|
|
||||||
themes = metadata.themes,
|
|
||||||
keywords = metadata.keywords,
|
|
||||||
features = metadata.features,
|
|
||||||
perspectives = metadata.perspectives,
|
|
||||||
images = metadata.screenshotUrls?.map { downloadAndPersist(it, ImageType.SCREENSHOT) }?.toSet(),
|
|
||||||
videoUrls = metadata.videoUrls,
|
|
||||||
path = path.toString(),
|
|
||||||
metadata = mapOf("title" to FieldMetadata(sourcePlugin))
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun toEntity(companyName: String, companyType: CompanyType): Company {
|
private fun toEntity(companyName: String, companyType: CompanyType): Company {
|
||||||
companyRepository.findByNameAndType(companyName, companyType)?.let { return it }
|
companyRepository.findByNameAndType(companyName, companyType)?.let { return it }
|
||||||
val company = Company(name = companyName, type = companyType)
|
val company = Company(name = companyName, type = companyType)
|
||||||
|
|||||||
@@ -14,10 +14,10 @@ class Game(
|
|||||||
@GeneratedValue(strategy = GenerationType.AUTO)
|
@GeneratedValue(strategy = GenerationType.AUTO)
|
||||||
var id: Long? = null,
|
var id: Long? = null,
|
||||||
|
|
||||||
val title: String,
|
var title: String? = null,
|
||||||
|
|
||||||
@OneToOne(cascade = [CascadeType.MERGE])
|
@OneToOne(cascade = [CascadeType.MERGE])
|
||||||
val coverImage: Image? = null,
|
var coverImage: Image? = null,
|
||||||
|
|
||||||
@Lob
|
@Lob
|
||||||
@Column(columnDefinition = "CLOB")
|
@Column(columnDefinition = "CLOB")
|
||||||
@@ -25,40 +25,40 @@ class Game(
|
|||||||
|
|
||||||
@Lob
|
@Lob
|
||||||
@Column(columnDefinition = "CLOB")
|
@Column(columnDefinition = "CLOB")
|
||||||
val summary: String? = null,
|
var summary: String? = null,
|
||||||
|
|
||||||
val release: Instant? = null,
|
var release: Instant? = null,
|
||||||
|
|
||||||
@ManyToMany(cascade = [CascadeType.MERGE])
|
@ManyToMany(cascade = [CascadeType.MERGE])
|
||||||
val publishers: Set<Company>? = null,
|
var publishers: Set<Company>? = null,
|
||||||
|
|
||||||
@ManyToMany(cascade = [CascadeType.MERGE])
|
@ManyToMany(cascade = [CascadeType.MERGE])
|
||||||
val developers: Set<Company>? = null,
|
var developers: Set<Company>? = null,
|
||||||
|
|
||||||
|
@ElementCollection(targetClass = Genre::class)
|
||||||
|
var genres: Set<Genre>? = null,
|
||||||
|
|
||||||
|
@ElementCollection(targetClass = Theme::class)
|
||||||
|
var themes: Set<Theme>? = null,
|
||||||
|
|
||||||
@ElementCollection
|
@ElementCollection
|
||||||
val genres: Set<Genre>? = null,
|
var keywords: Set<String>? = null,
|
||||||
|
|
||||||
@ElementCollection
|
@ElementCollection(targetClass = GameFeature::class)
|
||||||
val themes: Set<Theme>? = null,
|
var features: Set<GameFeature>? = null,
|
||||||
|
|
||||||
@ElementCollection
|
@ElementCollection(targetClass = PlayerPerspective::class)
|
||||||
val keywords: Set<String>? = null,
|
var perspectives: Set<PlayerPerspective>? = null,
|
||||||
|
|
||||||
@ElementCollection
|
|
||||||
val features: Set<GameFeature>? = null,
|
|
||||||
|
|
||||||
@ElementCollection
|
|
||||||
val perspectives: Set<PlayerPerspective>? = null,
|
|
||||||
|
|
||||||
@OneToMany(cascade = [CascadeType.MERGE])
|
@OneToMany(cascade = [CascadeType.MERGE])
|
||||||
val images: Set<Image>? = null,
|
var images: Set<Image>? = null,
|
||||||
|
|
||||||
@ElementCollection
|
@ElementCollection
|
||||||
val videoUrls: Set<URI>? = null,
|
var videoUrls: Set<URI>? = null,
|
||||||
|
|
||||||
@Column(unique = true)
|
@Column(unique = true)
|
||||||
val path: String,
|
val path: String,
|
||||||
|
|
||||||
@OneToMany(cascade = [CascadeType.ALL], orphanRemoval = true)
|
@OneToMany(cascade = [CascadeType.ALL], orphanRemoval = true)
|
||||||
val metadata: Map<String, FieldMetadata> = emptyMap()
|
var metadata: Map<String, FieldMetadata> = emptyMap()
|
||||||
)
|
)
|
||||||
+4
@@ -93,7 +93,11 @@ class UserPreferencesService(
|
|||||||
userPreference.value = castedValue.toString()
|
userPreference.value = castedValue.toString()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
userPreferenceRepository.save(userPreference)
|
userPreferenceRepository.save(userPreference)
|
||||||
|
} catch (e: Exception) {
|
||||||
|
log.warn { "Error saving user preference '$key': ${e.message}" }
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
Reference in New Issue
Block a user