mirror of
https://github.com/BrenBroZAYT/gameyfin.git
synced 2026-06-16 16:20:04 +00:00
+1
-1
@@ -265,4 +265,4 @@
|
|||||||
"disableUsageStatistics": true,
|
"disableUsageStatistics": true,
|
||||||
"hash": "962eccc3fa0735d5234901be4f9e384096113c45bec22564a53688096d62aef4"
|
"hash": "962eccc3fa0735d5234901be4f9e384096113c45bec22564a53688096d62aef4"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -12,19 +12,19 @@ import org.gameyfin.app.games.extensions.toUserDto
|
|||||||
class GameEntityListener {
|
class GameEntityListener {
|
||||||
@PostPersist
|
@PostPersist
|
||||||
fun created(game: Game) {
|
fun created(game: Game) {
|
||||||
GameService.Companion.emitUser(GameUserEvent.Created(game.toUserDto()))
|
GameService.emitUser(GameUserEvent.Created(game.toUserDto()))
|
||||||
GameService.Companion.emitAdmin(GameAdminEvent.Created(game.toAdminDto()))
|
GameService.emitAdmin(GameAdminEvent.Created(game.toAdminDto()))
|
||||||
}
|
}
|
||||||
|
|
||||||
@PostUpdate
|
@PostUpdate
|
||||||
fun updated(game: Game) {
|
fun updated(game: Game) {
|
||||||
GameService.Companion.emitUser(GameUserEvent.Updated(game.toUserDto()))
|
GameService.emitUser(GameUserEvent.Updated(game.toUserDto()))
|
||||||
GameService.Companion.emitAdmin(GameAdminEvent.Updated(game.toAdminDto()))
|
GameService.emitAdmin(GameAdminEvent.Updated(game.toAdminDto()))
|
||||||
}
|
}
|
||||||
|
|
||||||
@PostRemove
|
@PostRemove
|
||||||
fun deleted(game: Game) {
|
fun deleted(game: Game) {
|
||||||
GameService.Companion.emitUser(GameUserEvent.Deleted(game.id!!))
|
GameService.emitUser(GameUserEvent.Deleted(game.id!!))
|
||||||
GameService.Companion.emitAdmin(GameAdminEvent.Deleted(game.id!!))
|
GameService.emitAdmin(GameAdminEvent.Deleted(game.id!!))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1,15 +1,13 @@
|
|||||||
package org.gameyfin.app.games.entities
|
package org.gameyfin.app.games.entities
|
||||||
|
|
||||||
import jakarta.persistence.Entity
|
import jakarta.persistence.*
|
||||||
import jakarta.persistence.GeneratedValue
|
|
||||||
import jakarta.persistence.GenerationType
|
|
||||||
import jakarta.persistence.Id
|
|
||||||
import org.springframework.content.commons.annotations.ContentId
|
import org.springframework.content.commons.annotations.ContentId
|
||||||
import org.springframework.content.commons.annotations.ContentLength
|
import org.springframework.content.commons.annotations.ContentLength
|
||||||
import org.springframework.content.commons.annotations.MimeType
|
import org.springframework.content.commons.annotations.MimeType
|
||||||
import java.net.URL
|
import java.net.URL
|
||||||
|
|
||||||
@Entity
|
@Entity
|
||||||
|
@EntityListeners(ImageEntityListener::class)
|
||||||
class Image(
|
class Image(
|
||||||
@Id
|
@Id
|
||||||
@GeneratedValue(strategy = GenerationType.AUTO)
|
@GeneratedValue(strategy = GenerationType.AUTO)
|
||||||
|
|||||||
@@ -0,0 +1,27 @@
|
|||||||
|
package org.gameyfin.app.games.entities
|
||||||
|
|
||||||
|
import jakarta.persistence.PostRemove
|
||||||
|
import org.gameyfin.app.media.ImageService
|
||||||
|
import org.springframework.context.ApplicationContext
|
||||||
|
import org.springframework.context.ApplicationContextAware
|
||||||
|
import org.springframework.stereotype.Component
|
||||||
|
|
||||||
|
@Component
|
||||||
|
class ImageEntityListener : ApplicationContextAware {
|
||||||
|
companion object {
|
||||||
|
private lateinit var applicationContext: ApplicationContext
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun setApplicationContext(context: ApplicationContext) {
|
||||||
|
applicationContext = context
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun getImageService(): ImageService {
|
||||||
|
return applicationContext.getBean(ImageService::class.java)
|
||||||
|
}
|
||||||
|
|
||||||
|
@PostRemove
|
||||||
|
fun deleted(image: Image) {
|
||||||
|
getImageService().deleteFile(image)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -4,6 +4,7 @@ import io.github.oshai.kotlinlogging.KotlinLogging
|
|||||||
import org.gameyfin.app.core.filesystem.FilesystemService
|
import org.gameyfin.app.core.filesystem.FilesystemService
|
||||||
import org.gameyfin.app.games.GameService
|
import org.gameyfin.app.games.GameService
|
||||||
import org.gameyfin.app.games.entities.Game
|
import org.gameyfin.app.games.entities.Game
|
||||||
|
import org.gameyfin.app.games.entities.Image
|
||||||
import org.gameyfin.app.libraries.dto.LibraryScanProgress
|
import org.gameyfin.app.libraries.dto.LibraryScanProgress
|
||||||
import org.gameyfin.app.libraries.dto.LibraryScanStatus
|
import org.gameyfin.app.libraries.dto.LibraryScanStatus
|
||||||
import org.gameyfin.app.libraries.dto.LibraryScanStep
|
import org.gameyfin.app.libraries.dto.LibraryScanStep
|
||||||
@@ -13,6 +14,7 @@ import org.gameyfin.app.media.ImageService
|
|||||||
import org.springframework.stereotype.Service
|
import org.springframework.stereotype.Service
|
||||||
import reactor.core.publisher.Flux
|
import reactor.core.publisher.Flux
|
||||||
import reactor.core.publisher.Sinks
|
import reactor.core.publisher.Sinks
|
||||||
|
import java.net.URL
|
||||||
import java.nio.file.Path
|
import java.nio.file.Path
|
||||||
import java.time.Instant
|
import java.time.Instant
|
||||||
import java.util.concurrent.Callable
|
import java.util.concurrent.Callable
|
||||||
@@ -331,39 +333,56 @@ class LibraryScanService(
|
|||||||
private fun downloadImages(games: List<Game>, progress: LibraryScanProgress): DownloadImagesResult {
|
private fun downloadImages(games: List<Game>, progress: LibraryScanProgress): DownloadImagesResult {
|
||||||
val completedImageDownload = AtomicInteger(0)
|
val completedImageDownload = AtomicInteger(0)
|
||||||
|
|
||||||
val imageDownloadTasks = games.map { game ->
|
// Collect all images from all games in the batch
|
||||||
Callable<Game?> {
|
val allImages = games.flatMap { game ->
|
||||||
|
val images = mutableListOf<Image>()
|
||||||
|
game.coverImage?.let { images.add(it) }
|
||||||
|
game.headerImage?.let { images.add(it) }
|
||||||
|
images.addAll(game.images)
|
||||||
|
images
|
||||||
|
}
|
||||||
|
|
||||||
|
// Deduplicate by originalUrl
|
||||||
|
val uniqueImages = allImages
|
||||||
|
.filter { it.originalUrl != null }
|
||||||
|
.distinctBy { it.originalUrl }
|
||||||
|
|
||||||
|
// Map to track which Image entity was used for download per originalUrl
|
||||||
|
val downloadedImageMap = ConcurrentHashMap<URL, Image>()
|
||||||
|
|
||||||
|
// Download each unique image in parallel
|
||||||
|
val imageDownloadTasks = uniqueImages.map { image ->
|
||||||
|
Callable {
|
||||||
try {
|
try {
|
||||||
game.coverImage?.let {
|
imageService.downloadIfNew(image)
|
||||||
imageService.downloadIfNew(it)
|
image.originalUrl?.let { url ->
|
||||||
completedImageDownload.andIncrement
|
downloadedImageMap[url] = image
|
||||||
}
|
}
|
||||||
|
|
||||||
game.headerImage?.let {
|
|
||||||
imageService.downloadIfNew(it)
|
|
||||||
completedImageDownload.andIncrement
|
|
||||||
}
|
|
||||||
|
|
||||||
game.images.map {
|
|
||||||
imageService.downloadIfNew(it)
|
|
||||||
completedImageDownload.andIncrement
|
|
||||||
}
|
|
||||||
|
|
||||||
game
|
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
log.error { "Error downloading images for game '${game.title}' (${game.id}): ${e.message}" }
|
log.error { "Error downloading image '${image.originalUrl}': ${e.message}" }
|
||||||
log.debug(e) {}
|
log.debug(e) {}
|
||||||
null
|
|
||||||
} finally {
|
} finally {
|
||||||
progress.currentStep.current = completedImageDownload.get()
|
progress.currentStep.current = completedImageDownload.incrementAndGet()
|
||||||
emit(progress)
|
emit(progress)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
executor.invokeAll(imageDownloadTasks)
|
||||||
|
|
||||||
val gamesWithImages = executor.invokeAll(imageDownloadTasks).mapNotNull { it.get() }
|
// After downloads, associate the contentId with all other Image entities in the batch with the same originalUrl
|
||||||
|
for ((url, downloadedImage) in downloadedImageMap) {
|
||||||
|
val contentId = downloadedImage.contentId
|
||||||
|
if (contentId != null) {
|
||||||
|
allImages.filter { it.originalUrl.toString() == url.toString() && it !== downloadedImage }
|
||||||
|
.forEach { image ->
|
||||||
|
imageService.downloadIfNew(image)
|
||||||
|
progress.currentStep.current = completedImageDownload.incrementAndGet()
|
||||||
|
emit(progress)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return DownloadImagesResult(gamesWithImages = gamesWithImages)
|
return DownloadImagesResult(gamesWithImages = games)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun calculateFileSizes(games: List<Game>, progress: LibraryScanProgress): CalculateFilesizesResult {
|
private fun calculateFileSizes(games: List<Game>, progress: LibraryScanProgress): CalculateFilesizesResult {
|
||||||
|
|||||||
Reference in New Issue
Block a user