mirror of
https://github.com/BrenBroZAYT/gameyfin.git
synced 2026-06-17 08:15:44 +00:00
Improve game matching algorithm
This commit is contained in:
@@ -55,7 +55,6 @@ dependencies {
|
|||||||
implementation("org.springframework.boot:spring-boot-starter-data-jpa")
|
implementation("org.springframework.boot:spring-boot-starter-data-jpa")
|
||||||
implementation("com.github.paulcwarren:spring-content-fs-boot-starter:3.0.15")
|
implementation("com.github.paulcwarren:spring-content-fs-boot-starter:3.0.15")
|
||||||
implementation("commons-io:commons-io:2.18.0")
|
implementation("commons-io:commons-io:2.18.0")
|
||||||
implementation("org.apache.tika:tika-core:3.1.0")
|
|
||||||
|
|
||||||
// SSO
|
// SSO
|
||||||
implementation("org.springframework.boot:spring-boot-starter-oauth2-client")
|
implementation("org.springframework.boot:spring-boot-starter-oauth2-client")
|
||||||
@@ -70,6 +69,10 @@ dependencies {
|
|||||||
implementation(project(":plugin-api"))
|
implementation(project(":plugin-api"))
|
||||||
ksp("care.better.pf4j:pf4j-kotlin-symbol-processing:${rootProject.extra["pf4jKspVersion"]}")
|
ksp("care.better.pf4j:pf4j-kotlin-symbol-processing:${rootProject.extra["pf4jKspVersion"]}")
|
||||||
|
|
||||||
|
// Utils
|
||||||
|
implementation("org.apache.tika:tika-core:3.1.0")
|
||||||
|
implementation("me.xdrop:fuzzywuzzy:1.4.0")
|
||||||
|
|
||||||
// Development
|
// Development
|
||||||
developmentOnly("org.springframework.boot:spring-boot-devtools")
|
developmentOnly("org.springframework.boot:spring-boot-devtools")
|
||||||
annotationProcessor("org.springframework.boot:spring-boot-configuration-processor")
|
annotationProcessor("org.springframework.boot:spring-boot-configuration-processor")
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ import org.springframework.web.context.request.RequestContextHolder
|
|||||||
import org.springframework.web.context.request.ServletRequestAttributes
|
import org.springframework.web.context.request.ServletRequestAttributes
|
||||||
import java.io.InputStream
|
import java.io.InputStream
|
||||||
|
|
||||||
|
|
||||||
class Utils {
|
class Utils {
|
||||||
companion object {
|
companion object {
|
||||||
private val tika = Tika()
|
private val tika = Tika()
|
||||||
@@ -49,3 +50,6 @@ class Utils {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Suppress("UNCHECKED_CAST")
|
||||||
|
fun <K, V> Map<K, V?>.filterValuesNotNull() = filterValues { it != null } as Map<K, V>
|
||||||
@@ -1,5 +1,6 @@
|
|||||||
package de.grimsi.gameyfin.games
|
package de.grimsi.gameyfin.games
|
||||||
|
|
||||||
|
import de.grimsi.gameyfin.core.filterValuesNotNull
|
||||||
import de.grimsi.gameyfin.core.plugins.management.PluginManagementService
|
import de.grimsi.gameyfin.core.plugins.management.PluginManagementService
|
||||||
import de.grimsi.gameyfin.games.dto.GameDto
|
import de.grimsi.gameyfin.games.dto.GameDto
|
||||||
import de.grimsi.gameyfin.games.dto.GameMetadataDto
|
import de.grimsi.gameyfin.games.dto.GameMetadataDto
|
||||||
@@ -14,6 +15,7 @@ import io.github.oshai.kotlinlogging.KotlinLogging
|
|||||||
import kotlinx.coroutines.async
|
import kotlinx.coroutines.async
|
||||||
import kotlinx.coroutines.coroutineScope
|
import kotlinx.coroutines.coroutineScope
|
||||||
import kotlinx.coroutines.runBlocking
|
import kotlinx.coroutines.runBlocking
|
||||||
|
import me.xdrop.fuzzywuzzy.FuzzySearch
|
||||||
import org.pf4j.PluginManager
|
import org.pf4j.PluginManager
|
||||||
import org.springframework.data.repository.findByIdOrNull
|
import org.springframework.data.repository.findByIdOrNull
|
||||||
import org.springframework.stereotype.Service
|
import org.springframework.stereotype.Service
|
||||||
@@ -43,17 +45,29 @@ class GameService(
|
|||||||
}
|
}
|
||||||
|
|
||||||
fun createFromFile(path: Path): GameDto {
|
fun createFromFile(path: Path): GameDto {
|
||||||
val metadataResults = queryPlugins(path.fileName.toString())
|
val query = path.fileName.toString()
|
||||||
val validResults = metadataResults.filterValues { it != null }
|
|
||||||
|
// Step 0: Query all metadata plugins for metadata on the provided game title
|
||||||
|
val metadataResults = queryPlugins(query)
|
||||||
|
|
||||||
|
// Step 1: Filter out invalid (empty) results
|
||||||
|
val validResults = metadataResults.filterValuesNotNull()
|
||||||
if (validResults.isEmpty()) {
|
if (validResults.isEmpty()) {
|
||||||
throw NoMatchException("Could not match game at $path")
|
throw NoMatchException("Could not match game at $path")
|
||||||
}
|
}
|
||||||
|
|
||||||
val sortedResults = validResults.entries.sortedByDescending {
|
// Step 2: Filter results to find the best matching title
|
||||||
|
val filteredResults = filterResults(query, validResults)
|
||||||
|
|
||||||
|
// Step 3: Sort results by plugin priority
|
||||||
|
val sortedResults = filteredResults.entries.sortedByDescending {
|
||||||
pluginManagementService.getPluginManagementEntry(it.key.javaClass).priority
|
pluginManagementService.getPluginManagementEntry(it.key.javaClass).priority
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Step 4: Merge results into a single Game entity
|
||||||
val mergedGame = mergeResults(sortedResults, path)
|
val mergedGame = mergeResults(sortedResults, path)
|
||||||
|
|
||||||
|
// Step 5: Save the new game
|
||||||
val savedGame = createOrUpdate(mergedGame)
|
val savedGame = createOrUpdate(mergedGame)
|
||||||
|
|
||||||
return toDto(savedGame)
|
return toDto(savedGame)
|
||||||
@@ -94,6 +108,21 @@ class GameService(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Determines the closest matching title from the results and filters out any other results
|
||||||
|
*/
|
||||||
|
private fun filterResults(
|
||||||
|
originalQuery: String,
|
||||||
|
results: Map<GameMetadataProvider, GameMetadata>
|
||||||
|
): Map<GameMetadataProvider, GameMetadata> {
|
||||||
|
val availableTitles = results.map { it.value.title }
|
||||||
|
val bestMatchingTitle = FuzzySearch.extractOne(originalQuery, availableTitles).string
|
||||||
|
|
||||||
|
log.info { "Best matching title: '$bestMatchingTitle' for '$originalQuery' determined from $availableTitles" }
|
||||||
|
|
||||||
|
return results.filter { it.value.title == bestMatchingTitle }
|
||||||
|
}
|
||||||
|
|
||||||
private fun mergeResults(results: List<Map.Entry<GameMetadataProvider, GameMetadata?>>, path: Path): Game {
|
private fun mergeResults(results: List<Map.Entry<GameMetadataProvider, GameMetadata?>>, path: Path): Game {
|
||||||
val mergedGame = Game(path = path.toString())
|
val mergedGame = Game(path = path.toString())
|
||||||
val metadataMap = mutableMapOf<String, FieldMetadata>()
|
val metadataMap = mutableMapOf<String, FieldMetadata>()
|
||||||
|
|||||||
Reference in New Issue
Block a user