mirror of
https://github.com/BrenBroZAYT/gameyfin.git
synced 2026-06-13 16:40:01 +00:00
Various bugfixes and minor improvements
This commit is contained in:
@@ -23,22 +23,13 @@ export default function LibraryCreationModal({
|
||||
const [scanAfterCreation, setScanAfterCreation] = useState<boolean>(true);
|
||||
|
||||
async function createLibrary(library: LibraryDto) {
|
||||
try {
|
||||
await LibraryEndpoint.createLibrary(library as LibraryDto, scanAfterCreation);
|
||||
await LibraryEndpoint.createLibrary(library as LibraryDto, scanAfterCreation);
|
||||
|
||||
addToast({
|
||||
title: "New library created",
|
||||
description: `Library ${library.name} created!`,
|
||||
color: "success"
|
||||
});
|
||||
} catch (e) {
|
||||
addToast({
|
||||
title: "Error creating library",
|
||||
description: `Library ${library.name} could not be created!`,
|
||||
color: "warning"
|
||||
});
|
||||
throw "Error creating library: " + e;
|
||||
}
|
||||
addToast({
|
||||
title: "New library created",
|
||||
description: `Library ${library.name} created!`,
|
||||
color: "success"
|
||||
});
|
||||
}
|
||||
|
||||
return (
|
||||
|
||||
+4
-2
@@ -38,9 +38,11 @@ class GameyfinExtensionFinder(pluginManager: PluginManager) : LegacyExtensionFin
|
||||
result.add(extensionWrapper)
|
||||
log.debug { "Added extension '$className' with ordinal ${extensionWrapper.ordinal}" }
|
||||
} catch (e: ClassNotFoundException) {
|
||||
log.error(e) { e.message }
|
||||
log.error { "Error loading plugin: ${e.message}" }
|
||||
log.debug(e) {}
|
||||
} catch (e: NoClassDefFoundError) {
|
||||
log.error(e) { e.message }
|
||||
log.error { "Error loading plugin: ${e.message}" }
|
||||
log.debug(e) {}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+1
-1
@@ -215,7 +215,7 @@ class GameyfinPluginManager(
|
||||
|
||||
fun getPluginForExtension(extensionClass: Class<ExtensionPoint>): PluginWrapper? {
|
||||
return getPlugins().firstOrNull { pluginWrapper ->
|
||||
getExtensionTypeClasses(pluginWrapper.pluginId).any { it == extensionClass.javaClass }
|
||||
getExtensionClasses(pluginWrapper.pluginId).any { it == extensionClass }
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -12,6 +12,7 @@ import org.gameyfin.app.core.alphaNumeric
|
||||
import org.gameyfin.app.core.filesystem.FilesystemService
|
||||
import org.gameyfin.app.core.filterValuesNotNull
|
||||
import org.gameyfin.app.core.plugins.PluginService
|
||||
import org.gameyfin.app.core.plugins.management.GameyfinPluginDescriptor
|
||||
import org.gameyfin.app.core.plugins.management.GameyfinPluginManager
|
||||
import org.gameyfin.app.core.plugins.management.PluginManagementEntry
|
||||
import org.gameyfin.app.core.replaceRomanNumerals
|
||||
@@ -104,7 +105,8 @@ class GameService(
|
||||
imageService.downloadIfNew(it)
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
log.error(e) { "Error downloading images for game: ${e.message}" }
|
||||
log.error { "Error downloading images for game: ${e.message}" }
|
||||
log.debug(e) {}
|
||||
null
|
||||
}
|
||||
|
||||
@@ -449,7 +451,9 @@ class GameService(
|
||||
try {
|
||||
plugin.fetchByTitle(searchTerm, 10).map { plugin to it }
|
||||
} catch (e: Exception) {
|
||||
log.error(e) { "Error fetching metadata for searchterm '$searchTerm' with plugin ${plugin.javaClass.name}" }
|
||||
val pluginWrapper = pluginManager.getPluginForExtension(plugin.javaClass)
|
||||
log.warn { "Error fetching metadata for searchterm '$searchTerm' with plugin '${(pluginWrapper?.descriptor as GameyfinPluginDescriptor?)?.pluginName ?: pluginWrapper?.pluginId ?: plugin.javaClass.name}': ${e.message}" }
|
||||
log.debug(e) {}
|
||||
emptyList()
|
||||
}
|
||||
}
|
||||
@@ -561,7 +565,9 @@ class GameService(
|
||||
try {
|
||||
return@async plugin.fetchById(originalId)
|
||||
} catch (e: Exception) {
|
||||
log.error(e) { "Error fetching metadata for game [id: $originalId] with plugin ${plugin.javaClass.name}" }
|
||||
val pluginWrapper = pluginManager.getPluginForExtension(plugin.javaClass)
|
||||
log.warn { "Error fetching metadata for game [id: $originalId] with plugin '${(pluginWrapper?.descriptor as GameyfinPluginDescriptor?)?.pluginName ?: pluginWrapper?.pluginId ?: plugin.javaClass.name}': ${e.message}" }
|
||||
log.debug(e) {}
|
||||
null
|
||||
}
|
||||
}.await()
|
||||
@@ -638,8 +644,10 @@ class GameService(
|
||||
executor.submit<PluginApiMetadata?> {
|
||||
try {
|
||||
plugin.fetchByTitle(gameTitle).firstOrNull()
|
||||
} catch (_: Exception) {
|
||||
log.error { "Error fetching metadata for game title '$gameTitle' with plugin ${plugin.javaClass.name}" }
|
||||
} catch (e: Exception) {
|
||||
val pluginWrapper = pluginManager.getPluginForExtension(plugin.javaClass)
|
||||
log.warn { "Error fetching metadata for game title '$gameTitle' with plugin '${(pluginWrapper?.descriptor as GameyfinPluginDescriptor?)?.pluginName ?: pluginWrapper?.pluginId ?: plugin.javaClass.name}': ${e.message}" }
|
||||
log.debug(e) {}
|
||||
null
|
||||
}
|
||||
}
|
||||
|
||||
@@ -16,6 +16,13 @@ class Company(
|
||||
if (other !is Company) return false
|
||||
return name == other.name && type == other.type
|
||||
}
|
||||
|
||||
override fun hashCode(): Int {
|
||||
var result = id?.hashCode() ?: 0
|
||||
result = 31 * result + name.hashCode()
|
||||
result = 31 * result + type.hashCode()
|
||||
return result
|
||||
}
|
||||
}
|
||||
|
||||
enum class CompanyType {
|
||||
|
||||
@@ -33,6 +33,12 @@ class Image(
|
||||
if (other !is Image) return false
|
||||
return originalUrl.toString() == other.originalUrl.toString()
|
||||
}
|
||||
|
||||
override fun hashCode(): Int {
|
||||
var result = id?.hashCode() ?: 0
|
||||
result = 31 * result + originalUrl?.toString().hashCode()
|
||||
return result
|
||||
}
|
||||
}
|
||||
|
||||
enum class ImageType {
|
||||
|
||||
@@ -1,12 +1,6 @@
|
||||
package org.gameyfin.app.games.repositories
|
||||
|
||||
import org.gameyfin.app.games.entities.Game
|
||||
import org.springframework.data.domain.Limit
|
||||
import org.springframework.data.jpa.repository.JpaRepository
|
||||
|
||||
interface GameRepository : JpaRepository<Game, Long> {
|
||||
fun findByMetadata_Path(path: String): Game?
|
||||
fun findAllByMetadata_PathIn(paths: List<String>): List<Game>
|
||||
fun findByOrderByCreatedAtDesc(limit: Limit): List<Game>
|
||||
fun findByOrderByUpdatedAtDesc(limit: Limit): List<Game>
|
||||
}
|
||||
interface GameRepository : JpaRepository<Game, Long>
|
||||
@@ -1,9 +1,6 @@
|
||||
package org.gameyfin.app.libraries
|
||||
|
||||
import jakarta.persistence.Entity
|
||||
import jakarta.persistence.GeneratedValue
|
||||
import jakarta.persistence.GenerationType
|
||||
import jakarta.persistence.Id
|
||||
import jakarta.persistence.*
|
||||
|
||||
@Entity
|
||||
class DirectoryMapping(
|
||||
@@ -12,6 +9,7 @@ class DirectoryMapping(
|
||||
@GeneratedValue(strategy = GenerationType.AUTO)
|
||||
var id: Long? = null,
|
||||
|
||||
@Column(unique = true)
|
||||
var internalPath: String,
|
||||
|
||||
var externalPath: String? = null,
|
||||
|
||||
@@ -1,11 +1,9 @@
|
||||
package org.gameyfin.app.libraries
|
||||
|
||||
import org.springframework.data.jpa.repository.EntityGraph
|
||||
import org.springframework.data.jpa.repository.JpaRepository
|
||||
import org.springframework.data.jpa.repository.Query
|
||||
|
||||
interface LibraryRepository : JpaRepository<Library, Long> {
|
||||
@EntityGraph(attributePaths = ["games"])
|
||||
@Query("SELECT l FROM Library l ORDER BY function('RAND') LIMIT 1")
|
||||
fun findRandomLibrary(): Library?
|
||||
@Query("SELECT d.internalPath, l FROM Library l JOIN l.directories d WHERE d.internalPath IN :paths")
|
||||
fun findByPaths(paths: List<String>): List<Pair<String, Library>>
|
||||
}
|
||||
@@ -183,7 +183,8 @@ class LibraryScanService(
|
||||
)
|
||||
emit(progress)
|
||||
} catch (e: Exception) {
|
||||
log.error(e) { "Error during quick scan for library ${library.id}: ${e.message}" }
|
||||
log.error { "Error during quick scan for library ${library.id}: ${e.message}" }
|
||||
log.debug(e) {}
|
||||
progress.status = LibraryScanStatus.FAILED
|
||||
progress.finishedAt = Instant.now()
|
||||
emit(progress)
|
||||
@@ -282,7 +283,8 @@ class LibraryScanService(
|
||||
)
|
||||
emit(progress)
|
||||
} catch (e: Exception) {
|
||||
log.error(e) { "Error during full scan for library ${library.id}: ${e.message}" }
|
||||
log.error { "Error during full scan for library ${library.id}: ${e.message}" }
|
||||
log.debug(e) {}
|
||||
progress.status = LibraryScanStatus.FAILED
|
||||
progress.finishedAt = Instant.now()
|
||||
emit(progress)
|
||||
@@ -312,7 +314,8 @@ class LibraryScanService(
|
||||
|
||||
return@Callable game
|
||||
} catch (e: Exception) {
|
||||
log.error(e) { "Error processing game: ${e.message}" }
|
||||
log.error { "Error processing game: ${e.message}" }
|
||||
log.debug(e) {}
|
||||
newUnmatchedPaths.add(path.toString())
|
||||
|
||||
return@Callable null
|
||||
@@ -372,7 +375,8 @@ class LibraryScanService(
|
||||
|
||||
game
|
||||
} catch (e: Exception) {
|
||||
log.error(e) { "Error downloading images for game: ${e.message}" }
|
||||
log.error { "Error downloading images for game: ${e.message}" }
|
||||
log.debug(e) {}
|
||||
null
|
||||
} finally {
|
||||
progress.currentStep.current = completedImageDownload.get()
|
||||
@@ -437,7 +441,8 @@ class LibraryScanService(
|
||||
val game = gameService.update(game)
|
||||
return@Callable game
|
||||
} catch (e: Exception) {
|
||||
log.error(e) { "Error updating game with id '${game.id}': ${e.message}" }
|
||||
log.error { "Error updating game with id '${game.id}': ${e.message}" }
|
||||
log.debug(e) {}
|
||||
return@Callable null
|
||||
} finally {
|
||||
progress.currentStep.current = completedUpdates.incrementAndGet()
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
package org.gameyfin.app.libraries
|
||||
|
||||
import com.vaadin.hilla.exception.EndpointException
|
||||
import io.github.oshai.kotlinlogging.KotlinLogging
|
||||
import org.gameyfin.app.games.GameService
|
||||
import org.gameyfin.app.libraries.dto.LibraryDto
|
||||
@@ -19,7 +20,7 @@ class LibraryService(
|
||||
private val libraryRepository: LibraryRepository,
|
||||
private val libraryCoreService: LibraryCoreService,
|
||||
private val libraryScanService: LibraryScanService,
|
||||
private val gameService: GameService,
|
||||
private val gameService: GameService
|
||||
) {
|
||||
|
||||
companion object {
|
||||
@@ -70,6 +71,9 @@ class LibraryService(
|
||||
* @return The created or updated LibraryDto object.
|
||||
*/
|
||||
fun create(library: LibraryDto, scanAfterCreation: Boolean) {
|
||||
// Check for duplicate directories before creating a new library
|
||||
checkForDuplicateDirectories(library.directories.map { it.internalPath })
|
||||
|
||||
val newLibrary = libraryRepository.save(libraryCoreService.toEntity(library))
|
||||
|
||||
if (scanAfterCreation) {
|
||||
@@ -88,20 +92,49 @@ class LibraryService(
|
||||
val library = libraryRepository.findByIdOrNull(libraryUpdateDto.id)
|
||||
?: throw IllegalArgumentException("Library with ID $libraryUpdateDto.id not found")
|
||||
|
||||
// Update only non-null fields
|
||||
libraryUpdateDto.name?.let { library.name = it }
|
||||
libraryUpdateDto.directories?.let {
|
||||
library.directories.clear()
|
||||
library.directories.addAll(
|
||||
it.map { d -> DirectoryMapping(internalPath = d.internalPath, externalPath = d.externalPath) }
|
||||
libraryUpdateDto.directories?.let { updatedDirs ->
|
||||
checkForDuplicateDirectories(
|
||||
updatedDirs.map { it.internalPath },
|
||||
excludeLibraryId = library.id
|
||||
)
|
||||
|
||||
val existingMappings = library.directories.associateBy { it.internalPath }
|
||||
val updatedInternalPaths = updatedDirs.map { it.internalPath }.toSet()
|
||||
|
||||
// Remove mappings not present in the update
|
||||
val removedDirs = library.directories.filter { it.internalPath !in updatedInternalPaths }
|
||||
library.directories.removeAll(removedDirs)
|
||||
|
||||
// Remove all games within removed directories
|
||||
val removedDirPaths = removedDirs.map { it.internalPath }
|
||||
library.games.removeIf { game ->
|
||||
removedDirPaths.any { removedPath ->
|
||||
game.metadata.path.startsWith(removedPath)
|
||||
}
|
||||
}
|
||||
|
||||
// Update existing or add new directory mappings
|
||||
updatedDirs.forEach { dto ->
|
||||
val mapping = existingMappings[dto.internalPath]
|
||||
if (mapping != null) {
|
||||
mapping.externalPath = dto.externalPath // update fields
|
||||
} else {
|
||||
library.directories.add(
|
||||
DirectoryMapping(
|
||||
internalPath = dto.internalPath,
|
||||
externalPath = dto.externalPath
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
libraryUpdateDto.unmatchedPaths?.let {
|
||||
library.unmatchedPaths.clear()
|
||||
library.unmatchedPaths.addAll(it)
|
||||
}
|
||||
|
||||
library.updatedAt = Instant.now() // Force the EntityListener to trigger an update and update the timestamp
|
||||
library.updatedAt = Instant.now()
|
||||
libraryRepository.save(library)
|
||||
}
|
||||
|
||||
@@ -113,4 +146,17 @@ class LibraryService(
|
||||
fun delete(libraryId: Long) {
|
||||
libraryRepository.deleteById(libraryId)
|
||||
}
|
||||
|
||||
private fun checkForDuplicateDirectories(newLibraryFolders: List<String>, excludeLibraryId: Long? = null) {
|
||||
val alreadyConfiguredFolders = libraryRepository.findByPaths(newLibraryFolders)
|
||||
.filter { it.second.id != excludeLibraryId } // Exclude the current library if updating
|
||||
.map { Pair(it.first, it.second) } // Convert to Pair for easier error message formatting
|
||||
|
||||
if (alreadyConfiguredFolders.isNotEmpty()) {
|
||||
throw EndpointException(
|
||||
"The following directories are already mapped to another library: " +
|
||||
alreadyConfiguredFolders.joinToString(", ") { "${it.first} (${it.second.name})" }
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,18 +1,12 @@
|
||||
package org.gameyfin.app.messages
|
||||
|
||||
import org.gameyfin.app.messages.templates.MessageTemplates
|
||||
import org.gameyfin.app.users.UserService
|
||||
import io.github.oshai.kotlinlogging.KLogger
|
||||
import io.github.oshai.kotlinlogging.KotlinLogging
|
||||
import org.gameyfin.app.core.events.AccountDeletedEvent
|
||||
import org.gameyfin.app.core.events.AccountStatusChangedEvent
|
||||
import org.gameyfin.app.core.events.EmailNeedsConfirmationEvent
|
||||
import org.gameyfin.app.core.events.PasswordResetRequestEvent
|
||||
import org.gameyfin.app.core.events.RegistrationAttemptWithExistingEmailEvent
|
||||
import org.gameyfin.app.core.events.UserInvitationEvent
|
||||
import org.gameyfin.app.core.events.UserRegistrationWaitingForApprovalEvent
|
||||
import org.gameyfin.app.core.events.*
|
||||
import org.gameyfin.app.messages.providers.AbstractMessageProvider
|
||||
import org.gameyfin.app.messages.templates.MessageTemplateService
|
||||
import org.gameyfin.app.messages.templates.MessageTemplates
|
||||
import org.gameyfin.app.users.UserService
|
||||
import org.springframework.context.ApplicationContext
|
||||
import org.springframework.context.event.EventListener
|
||||
import org.springframework.scheduling.annotation.Async
|
||||
@@ -72,7 +66,8 @@ class MessageService(
|
||||
val template = templateService.getMessageTemplate(templateKey)
|
||||
sendNotification(user.email, "[Gameyfin] Test Notification", template, placeholders)
|
||||
} catch (e: Exception) {
|
||||
log.error(e) { "Failed to send test message" }
|
||||
log.error { "Failed to send test message: ${e.message}" }
|
||||
log.debug(e) {}
|
||||
return false
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user