mirror of
https://github.com/BrenBroZAYT/gameyfin.git
synced 2026-06-16 08:15:48 +00:00
Implement manual mapping for files that could not be automatically mapped
This commit is contained in:
@@ -21,8 +21,6 @@ import java.util.Optional;
|
|||||||
@Service
|
@Service
|
||||||
public class IgdbWrapper {
|
public class IgdbWrapper {
|
||||||
|
|
||||||
private static final int MAIN_GAME_CATEGORY_VALUE = 0;
|
|
||||||
|
|
||||||
@Value("${gameyfin.igdb.api.client-id}")
|
@Value("${gameyfin.igdb.api.client-id}")
|
||||||
private String clientId;
|
private String clientId;
|
||||||
|
|
||||||
@@ -70,7 +68,21 @@ public class IgdbWrapper {
|
|||||||
public Optional<Igdb.Game> getGameById(Long id) {
|
public Optional<Igdb.Game> getGameById(Long id) {
|
||||||
Igdb.GameResult gameResult = igdbApiClient.post()
|
Igdb.GameResult gameResult = igdbApiClient.post()
|
||||||
.uri("games.pb")
|
.uri("games.pb")
|
||||||
.bodyValue("fields *; where id = %d & category = %d; limit 1;".formatted(id, MAIN_GAME_CATEGORY_VALUE))
|
.bodyValue("fields *; where id = %d; limit 1;".formatted(id))
|
||||||
|
.retrieve()
|
||||||
|
.bodyToMono(Igdb.GameResult.class)
|
||||||
|
.transformDeferred(RateLimiterOperator.of(WebClientConfig.IGDB_RATE_LIMITER))
|
||||||
|
.block();
|
||||||
|
|
||||||
|
if (gameResult == null) return Optional.empty();
|
||||||
|
|
||||||
|
return Optional.of(gameResult.getGames(0));
|
||||||
|
}
|
||||||
|
|
||||||
|
public Optional<Igdb.Game> getGameBySlug(String slug) {
|
||||||
|
Igdb.GameResult gameResult = igdbApiClient.post()
|
||||||
|
.uri("games.pb")
|
||||||
|
.bodyValue("fields *; where slug = \"%s\"; limit 1;".formatted(slug))
|
||||||
.retrieve()
|
.retrieve()
|
||||||
.bodyToMono(Igdb.GameResult.class)
|
.bodyToMono(Igdb.GameResult.class)
|
||||||
.transformDeferred(RateLimiterOperator.of(WebClientConfig.IGDB_RATE_LIMITER))
|
.transformDeferred(RateLimiterOperator.of(WebClientConfig.IGDB_RATE_LIMITER))
|
||||||
@@ -84,7 +96,7 @@ public class IgdbWrapper {
|
|||||||
public Optional<Igdb.Game> searchForGameByTitle(String searchTerm) {
|
public Optional<Igdb.Game> searchForGameByTitle(String searchTerm) {
|
||||||
Igdb.GameResult gameResult = igdbApiClient.post()
|
Igdb.GameResult gameResult = igdbApiClient.post()
|
||||||
.uri("games.pb")
|
.uri("games.pb")
|
||||||
.bodyValue("fields *; search \"%s\"; where platforms = (%s) & category = %d;".formatted(searchTerm, preferredPlatforms, MAIN_GAME_CATEGORY_VALUE))
|
.bodyValue("fields *; search \"%s\"; where platforms = (%s);".formatted(searchTerm, preferredPlatforms))
|
||||||
.retrieve()
|
.retrieve()
|
||||||
.bodyToMono(Igdb.GameResult.class)
|
.bodyToMono(Igdb.GameResult.class)
|
||||||
.transformDeferred(RateLimiterOperator.of(WebClientConfig.IGDB_RATE_LIMITER))
|
.transformDeferred(RateLimiterOperator.of(WebClientConfig.IGDB_RATE_LIMITER))
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ package de.grimsi.gameyfin.repositories;
|
|||||||
import de.grimsi.gameyfin.entities.UnmappableFile;
|
import de.grimsi.gameyfin.entities.UnmappableFile;
|
||||||
import org.springframework.data.jpa.repository.JpaRepository;
|
import org.springframework.data.jpa.repository.JpaRepository;
|
||||||
|
|
||||||
public interface UnmappableFileRepository extends JpaRepository<UnmappableFile, String> {
|
public interface UnmappableFileRepository extends JpaRepository<UnmappableFile, Long> {
|
||||||
|
|
||||||
boolean existsByPath(String path);
|
boolean existsByPath(String path);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -11,9 +11,7 @@ import de.grimsi.gameyfin.util.ProtobufUtils;
|
|||||||
import org.springframework.beans.factory.annotation.Autowired;
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
import org.springframework.http.HttpStatus;
|
import org.springframework.http.HttpStatus;
|
||||||
import org.springframework.http.MediaType;
|
import org.springframework.http.MediaType;
|
||||||
import org.springframework.web.bind.annotation.GetMapping;
|
import org.springframework.web.bind.annotation.*;
|
||||||
import org.springframework.web.bind.annotation.PathVariable;
|
|
||||||
import org.springframework.web.bind.annotation.RestController;
|
|
||||||
import org.springframework.web.server.ResponseStatusException;
|
import org.springframework.web.server.ResponseStatusException;
|
||||||
|
|
||||||
import java.nio.file.Path;
|
import java.nio.file.Path;
|
||||||
@@ -64,7 +62,7 @@ public class GameyfinDevController {
|
|||||||
return gameService.getAllDetectedGames();
|
return gameService.getAllDetectedGames();
|
||||||
}
|
}
|
||||||
|
|
||||||
@GetMapping(value = "/dev/startScan", produces = MediaType.APPLICATION_JSON_VALUE)
|
@GetMapping(value = "/dev/scan", produces = MediaType.APPLICATION_JSON_VALUE)
|
||||||
public void scanLibrary() {
|
public void scanLibrary() {
|
||||||
filesystemService.scanGameLibrary();
|
filesystemService.scanGameLibrary();
|
||||||
}
|
}
|
||||||
@@ -74,4 +72,10 @@ public class GameyfinDevController {
|
|||||||
return gameService.getAllUnmappedFiles();
|
return gameService.getAllUnmappedFiles();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@PostMapping(value = "/dev/unmappedFiles/{unmappedGameId}/mapTo/{igdbSlug}", produces = MediaType.APPLICATION_JSON_VALUE)
|
||||||
|
public DetectedGame mapGameManually(@PathVariable Long unmappedGameId, @PathVariable String igdbSlug) {
|
||||||
|
return gameService.mapUnmappedFile(unmappedGameId, igdbSlug);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -73,7 +73,7 @@ public class FilesystemService {
|
|||||||
.toList();
|
.toList();
|
||||||
|
|
||||||
// For each new game, load the info from IGDB
|
// For each new game, load the info from IGDB
|
||||||
// If a game is not found on IGDB, blacklist the path 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
|
||||||
List<DetectedGame> newDetectedGames = gameFiles.parallelStream()
|
List<DetectedGame> newDetectedGames = gameFiles.parallelStream()
|
||||||
.map(p -> {
|
.map(p -> {
|
||||||
Optional<Igdb.Game> optionalGame = igdbWrapper.searchForGameByTitle(getFilename(p));
|
Optional<Igdb.Game> optionalGame = igdbWrapper.searchForGameByTitle(getFilename(p));
|
||||||
@@ -91,7 +91,7 @@ public class FilesystemService {
|
|||||||
|
|
||||||
newDetectedGames = detectedGameRepository.saveAll(newDetectedGames);
|
newDetectedGames = detectedGameRepository.saveAll(newDetectedGames);
|
||||||
|
|
||||||
log.info("Scan finished: Found {} new games, deleted {} games, backlisted {} files/folders, {} games total.", newDetectedGames.size(), "NOT_IMPLEMENTED_YET", newBlacklistCounter.get(), detectedGameRepository.count());
|
log.info("Scan finished: Found {} new games, deleted {} games, could not map {} files/folders, {} games total.", newDetectedGames.size(), "NOT_IMPLEMENTED_YET", newBlacklistCounter.get(), detectedGameRepository.count());
|
||||||
}
|
}
|
||||||
|
|
||||||
private String getFilename(Path p) {
|
private String getFilename(Path p) {
|
||||||
|
|||||||
@@ -1,17 +1,26 @@
|
|||||||
package de.grimsi.gameyfin.service;
|
package de.grimsi.gameyfin.service;
|
||||||
|
|
||||||
|
import com.igdb.proto.Igdb;
|
||||||
import de.grimsi.gameyfin.entities.DetectedGame;
|
import de.grimsi.gameyfin.entities.DetectedGame;
|
||||||
import de.grimsi.gameyfin.entities.UnmappableFile;
|
import de.grimsi.gameyfin.entities.UnmappableFile;
|
||||||
|
import de.grimsi.gameyfin.igdb.IgdbWrapper;
|
||||||
|
import de.grimsi.gameyfin.mapper.GameMapper;
|
||||||
import de.grimsi.gameyfin.repositories.UnmappableFileRepository;
|
import de.grimsi.gameyfin.repositories.UnmappableFileRepository;
|
||||||
import de.grimsi.gameyfin.repositories.DetectedGameRepository;
|
import de.grimsi.gameyfin.repositories.DetectedGameRepository;
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
|
import org.springframework.http.HttpStatus;
|
||||||
import org.springframework.stereotype.Service;
|
import org.springframework.stereotype.Service;
|
||||||
|
import org.springframework.web.server.ResponseStatusException;
|
||||||
|
|
||||||
|
import java.nio.file.Path;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
@Service
|
@Service
|
||||||
public class GameService {
|
public class GameService {
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private IgdbWrapper igdbWrapper;
|
||||||
|
|
||||||
@Autowired
|
@Autowired
|
||||||
private DetectedGameRepository detectedGameRepository;
|
private DetectedGameRepository detectedGameRepository;
|
||||||
|
|
||||||
@@ -25,4 +34,19 @@ public class GameService {
|
|||||||
public List<UnmappableFile> getAllUnmappedFiles() {
|
public List<UnmappableFile> getAllUnmappedFiles() {
|
||||||
return unmappableFileRepository.findAll();
|
return unmappableFileRepository.findAll();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public DetectedGame mapUnmappedFile(Long unmappedGameId, String igdbSlug) {
|
||||||
|
UnmappableFile unmappableFile = unmappableFileRepository.findById(unmappedGameId)
|
||||||
|
.orElseThrow(() -> new ResponseStatusException(HttpStatus.NOT_FOUND, "Unmapped file with id '%d' does not exist.".formatted(unmappedGameId)));
|
||||||
|
|
||||||
|
Igdb.Game igdbGame = igdbWrapper.getGameBySlug(igdbSlug)
|
||||||
|
.orElseThrow(() -> new ResponseStatusException(HttpStatus.NOT_FOUND, "Game with slug '%s' does not exist on IGDB.".formatted(igdbSlug)));
|
||||||
|
|
||||||
|
DetectedGame game = GameMapper.toDetectedGame(igdbGame, Path.of(unmappableFile.getPath()));
|
||||||
|
game = detectedGameRepository.save(game);
|
||||||
|
|
||||||
|
unmappableFileRepository.delete(unmappableFile);
|
||||||
|
|
||||||
|
return game;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user