mirror of
https://github.com/BrenBroZAYT/gameyfin.git
synced 2026-06-15 16:20:03 +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("com.github.paulcwarren:spring-content-fs-boot-starter:3.0.15")
|
||||
implementation("commons-io:commons-io:2.18.0")
|
||||
implementation("org.apache.tika:tika-core:3.1.0")
|
||||
|
||||
// SSO
|
||||
implementation("org.springframework.boot:spring-boot-starter-oauth2-client")
|
||||
@@ -70,6 +69,10 @@ dependencies {
|
||||
implementation(project(":plugin-api"))
|
||||
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
|
||||
developmentOnly("org.springframework.boot:spring-boot-devtools")
|
||||
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 java.io.InputStream
|
||||
|
||||
|
||||
class Utils {
|
||||
companion object {
|
||||
private val tika = Tika()
|
||||
@@ -48,4 +49,7 @@ class Utils {
|
||||
.body(inputStreamResource)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@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
|
||||
|
||||
import de.grimsi.gameyfin.core.filterValuesNotNull
|
||||
import de.grimsi.gameyfin.core.plugins.management.PluginManagementService
|
||||
import de.grimsi.gameyfin.games.dto.GameDto
|
||||
import de.grimsi.gameyfin.games.dto.GameMetadataDto
|
||||
@@ -14,6 +15,7 @@ import io.github.oshai.kotlinlogging.KotlinLogging
|
||||
import kotlinx.coroutines.async
|
||||
import kotlinx.coroutines.coroutineScope
|
||||
import kotlinx.coroutines.runBlocking
|
||||
import me.xdrop.fuzzywuzzy.FuzzySearch
|
||||
import org.pf4j.PluginManager
|
||||
import org.springframework.data.repository.findByIdOrNull
|
||||
import org.springframework.stereotype.Service
|
||||
@@ -43,17 +45,29 @@ class GameService(
|
||||
}
|
||||
|
||||
fun createFromFile(path: Path): GameDto {
|
||||
val metadataResults = queryPlugins(path.fileName.toString())
|
||||
val validResults = metadataResults.filterValues { it != null }
|
||||
val query = path.fileName.toString()
|
||||
|
||||
// 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()) {
|
||||
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
|
||||
}
|
||||
|
||||
// Step 4: Merge results into a single Game entity
|
||||
val mergedGame = mergeResults(sortedResults, path)
|
||||
|
||||
// Step 5: Save the new game
|
||||
val savedGame = createOrUpdate(mergedGame)
|
||||
|
||||
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 {
|
||||
val mergedGame = Game(path = path.toString())
|
||||
val metadataMap = mutableMapOf<String, FieldMetadata>()
|
||||
|
||||
Reference in New Issue
Block a user