diff --git a/backend/pom.xml b/backend/pom.xml index 6066cdd..85270e6 100644 --- a/backend/pom.xml +++ b/backend/pom.xml @@ -7,7 +7,7 @@ gameyfin de.grimsi - 1.1.1 + 1.1.2 gameyfin-backend diff --git a/backend/src/main/java/de/grimsi/gameyfin/mapper/GameMapper.java b/backend/src/main/java/de/grimsi/gameyfin/mapper/GameMapper.java index 70c9c5f..a61e068 100644 --- a/backend/src/main/java/de/grimsi/gameyfin/mapper/GameMapper.java +++ b/backend/src/main/java/de/grimsi/gameyfin/mapper/GameMapper.java @@ -19,6 +19,7 @@ import java.nio.file.FileSystem; import java.nio.file.FileSystems; import java.nio.file.Files; import java.nio.file.Path; +import java.time.Instant; import java.util.List; import java.util.stream.Stream; @@ -53,6 +54,7 @@ public class GameMapper { .playerPerspectives(PlayerPerspectiveMapper.toPlayerPerspectives(g.getPlayerPerspectivesList())) .path(path.toString()) .diskSize(calculateDiskSize(g, path)) + .addedToLibrary(Instant.now()) .build(); } diff --git a/backend/src/main/java/de/grimsi/gameyfin/service/DownloadService.java b/backend/src/main/java/de/grimsi/gameyfin/service/DownloadService.java index 4e3013e..ecf6dea 100644 --- a/backend/src/main/java/de/grimsi/gameyfin/service/DownloadService.java +++ b/backend/src/main/java/de/grimsi/gameyfin/service/DownloadService.java @@ -116,7 +116,7 @@ public class DownloadService { MultiValueMap gameToImageIds = new LinkedMultiValueMap<>( detectedGameRepository.findAll().stream() - .collect(Collectors.toMap(DetectedGame::getTitle, g -> Collections.singletonList(g.getCoverId())))); + .collect(Collectors.toMap(DetectedGame::getSlug, g -> Collections.singletonList(g.getCoverId())))); int downloadCount = downloadImagesIntoCache(gameToImageIds, IgdbApiProperties.COVER_IMAGE_SIZE, "cover", "game"); @@ -133,7 +133,7 @@ public class DownloadService { MultiValueMap gamesToImageIds = new LinkedMultiValueMap<>( detectedGameRepository.findAll().stream() - .collect(Collectors.toMap(DetectedGame::getTitle, DetectedGame::getScreenshotIds))); + .collect(Collectors.toMap(DetectedGame::getSlug, DetectedGame::getScreenshotIds))); int downloadCount = downloadImagesIntoCache(gamesToImageIds, IgdbApiProperties.SCREENSHOT_IMAGE_SIZE, "screenshot", "game"); @@ -150,7 +150,7 @@ public class DownloadService { Map> companyToLogoIdMap = detectedGameRepository.findAll().stream() .flatMap(g -> g.getCompanies().stream()) - .collect(Collectors.toMap(Company::getName, c -> Collections.singletonList(c.getLogoId()), (c1, c2) -> c1)); + .collect(Collectors.toMap(Company::getSlug, c -> Collections.singletonList(c.getLogoId()), (c1, c2) -> c1)); MultiValueMap companiesToLogoIds = new LinkedMultiValueMap<>(companyToLogoIdMap); @@ -215,12 +215,24 @@ public class DownloadService { String imgUrl = "t_%s/%s".formatted(imageSize, imgFileName); if (Files.exists(Path.of(cacheFolderPath, imgFileName))) { - log.debug("{} for {} '{}' already downloaded ({}), skipping.", - imageType.substring(0, 1).toUpperCase() + imageType.substring(1).toLowerCase(), - entityType, - entry.getKey(), - imgFileName); - return; + + Path existingImageFile = Path.of(cacheFolderPath, imgFileName); + + try { + if(Files.size(existingImageFile) == 0L) { + log.info("File '{}' is corrupt, retrying download...", imgFileName); + Files.delete(existingImageFile); + } else { + log.debug("{} for {} '{}' already downloaded ({}), skipping.", + imageType.substring(0, 1).toUpperCase() + imageType.substring(1).toLowerCase(), + entityType, + entry.getKey(), + imgFileName); + return; + } + } catch (IOException e) { + log.error("Error while checking file '{}'.", existingImageFile); + } } Flux dataBuffer = igdbImageClient.get() diff --git a/backend/src/main/java/de/grimsi/gameyfin/service/LibraryService.java b/backend/src/main/java/de/grimsi/gameyfin/service/LibraryService.java index f5c7308..7e5dd67 100644 --- a/backend/src/main/java/de/grimsi/gameyfin/service/LibraryService.java +++ b/backend/src/main/java/de/grimsi/gameyfin/service/LibraryService.java @@ -19,6 +19,7 @@ import java.nio.file.Files; import java.nio.file.Path; import java.util.*; import java.util.concurrent.atomic.AtomicInteger; +import java.util.stream.Collectors; import java.util.stream.Stream; import static de.grimsi.gameyfin.util.FilenameUtil.getFilenameWithoutExtension; @@ -83,11 +84,17 @@ public class LibraryService { .toList(); // For each new game, load the info from IGDB - // If a game is not found on IGDB, add it to the list of unmapped files so we won't query the API later on for the same path + // If a game is not found on IGDB, add it to the list of unmapped files, so we won't query the API later on for the same path // If a game is not found on IGDB, blacklist the path, so we won't query the API later for the same path List newDetectedGames = gameFiles.parallelStream() .map(p -> { Optional optionalGame = igdbWrapper.searchForGameByTitle(getFilenameWithoutExtension(p)); + + if(optionalGame.isPresent() && detectedGameRepository.existsBySlug(optionalGame.get().getSlug())) { + log.warn("Game with slug '{}' already exists in database", optionalGame.get().getSlug()); + optionalGame = Optional.empty(); + } + return optionalGame.map(game -> Map.entry(p, game)).or(() -> { unmappableFileRepository.save(new UnmappableFile(p.toString())); newUnmappedFilesCounter.getAndIncrement(); @@ -99,7 +106,11 @@ public class LibraryService { .map(Optional::get) .peek(e -> log.info("Mapped file '{}' to game '{}' (slug: {})", e.getKey(), e.getValue().getName(), e.getValue().getSlug())) .map(e -> GameMapper.toDetectedGame(e.getValue(), e.getKey())) - .toList(); + .collect(Collectors.toList()); + + List duplicateGames = getDuplicates(newDetectedGames); + newUnmappedFilesCounter.getAndAdd(duplicateGames.size()); + newDetectedGames.removeAll(duplicateGames); newDetectedGames = detectedGameRepository.saveAll(newDetectedGames); @@ -112,4 +123,13 @@ public class LibraryService { public List getAutocompleteSuggestions(String searchTerm, int limit) { return igdbWrapper.findPossibleMatchingTitles(searchTerm, limit); } + + private List getDuplicates(List gamesToFilter) { + return gamesToFilter.stream().filter(g -> Collections.frequency(gamesToFilter, g) >1) + .peek(d -> { + log.warn("Found duplicate for game '{}' under path '{}'. Mapping must be done manually.", d.getTitle(), d.getPath()); + unmappableFileRepository.save(new UnmappableFile(d.getPath())); + }) + .toList(); + } } diff --git a/frontend/pom.xml b/frontend/pom.xml index 1b7f07a..da6ec3d 100644 --- a/frontend/pom.xml +++ b/frontend/pom.xml @@ -5,7 +5,7 @@ gameyfin de.grimsi - 1.1.1 + 1.1.2 4.0.0 diff --git a/pom.xml b/pom.xml index 041f3dc..92cf638 100644 --- a/pom.xml +++ b/pom.xml @@ -5,7 +5,7 @@ de.grimsi gameyfin - 1.1.1 + 1.1.2 gameyfin gameyfin