mirror of
https://github.com/BrenBroZAYT/gameyfin.git
synced 2026-06-16 08:15:48 +00:00
Added detailed library scan result
Small layout fixes in game detail view
This commit is contained in:
+1
-1
@@ -7,7 +7,7 @@
|
|||||||
<parent>
|
<parent>
|
||||||
<artifactId>gameyfin</artifactId>
|
<artifactId>gameyfin</artifactId>
|
||||||
<groupId>de.grimsi</groupId>
|
<groupId>de.grimsi</groupId>
|
||||||
<version>1.1.4-RC1</version>
|
<version>1.2.0</version>
|
||||||
</parent>
|
</parent>
|
||||||
|
|
||||||
<artifactId>gameyfin-backend</artifactId>
|
<artifactId>gameyfin-backend</artifactId>
|
||||||
|
|||||||
@@ -0,0 +1,10 @@
|
|||||||
|
package de.grimsi.gameyfin.dto;
|
||||||
|
|
||||||
|
import lombok.Data;
|
||||||
|
|
||||||
|
@Data
|
||||||
|
public class ImageDownloadResultDto {
|
||||||
|
private int coverDownloads;
|
||||||
|
private int screenshotDownloads;
|
||||||
|
private int companyLogoDownloads;
|
||||||
|
}
|
||||||
@@ -0,0 +1,13 @@
|
|||||||
|
package de.grimsi.gameyfin.dto;
|
||||||
|
|
||||||
|
import lombok.Builder;
|
||||||
|
import lombok.Data;
|
||||||
|
|
||||||
|
@Data
|
||||||
|
@Builder
|
||||||
|
public class LibraryScanResult {
|
||||||
|
private int newGames;
|
||||||
|
private int deletedGames;
|
||||||
|
private int newUnmappableFiles;
|
||||||
|
private int totalGames;
|
||||||
|
}
|
||||||
@@ -0,0 +1,21 @@
|
|||||||
|
package de.grimsi.gameyfin.dto;
|
||||||
|
|
||||||
|
import lombok.AllArgsConstructor;
|
||||||
|
import lombok.Builder;
|
||||||
|
import lombok.Data;
|
||||||
|
import lombok.NoArgsConstructor;
|
||||||
|
|
||||||
|
@Data
|
||||||
|
@Builder
|
||||||
|
@NoArgsConstructor
|
||||||
|
@AllArgsConstructor
|
||||||
|
public class LibraryScanResultDto {
|
||||||
|
private int newGames;
|
||||||
|
private int deletedGames;
|
||||||
|
private int newUnmappableFiles;
|
||||||
|
private int totalGames;
|
||||||
|
private int coverDownloads;
|
||||||
|
private int screenshotDownloads;
|
||||||
|
private int companyLogoDownloads;
|
||||||
|
private int scanDuration;
|
||||||
|
}
|
||||||
@@ -1,5 +1,8 @@
|
|||||||
package de.grimsi.gameyfin.rest;
|
package de.grimsi.gameyfin.rest;
|
||||||
|
|
||||||
|
import de.grimsi.gameyfin.dto.ImageDownloadResultDto;
|
||||||
|
import de.grimsi.gameyfin.dto.LibraryScanResult;
|
||||||
|
import de.grimsi.gameyfin.dto.LibraryScanResultDto;
|
||||||
import de.grimsi.gameyfin.service.DownloadService;
|
import de.grimsi.gameyfin.service.DownloadService;
|
||||||
import de.grimsi.gameyfin.service.ImageService;
|
import de.grimsi.gameyfin.service.ImageService;
|
||||||
import de.grimsi.gameyfin.service.LibraryService;
|
import de.grimsi.gameyfin.service.LibraryService;
|
||||||
@@ -7,6 +10,7 @@ import lombok.RequiredArgsConstructor;
|
|||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
import org.springframework.http.MediaType;
|
import org.springframework.http.MediaType;
|
||||||
import org.springframework.security.access.prepost.PreAuthorize;
|
import org.springframework.security.access.prepost.PreAuthorize;
|
||||||
|
import org.springframework.util.StopWatch;
|
||||||
import org.springframework.web.bind.annotation.GetMapping;
|
import org.springframework.web.bind.annotation.GetMapping;
|
||||||
import org.springframework.web.bind.annotation.RequestMapping;
|
import org.springframework.web.bind.annotation.RequestMapping;
|
||||||
import org.springframework.web.bind.annotation.RequestParam;
|
import org.springframework.web.bind.annotation.RequestParam;
|
||||||
@@ -29,19 +33,45 @@ public class LibraryController {
|
|||||||
private final ImageService imageService;
|
private final ImageService imageService;
|
||||||
|
|
||||||
@GetMapping(value = "/scan", produces = MediaType.APPLICATION_JSON_VALUE)
|
@GetMapping(value = "/scan", produces = MediaType.APPLICATION_JSON_VALUE)
|
||||||
public void scanLibrary(@RequestParam(value = "download_images", defaultValue = "true") boolean downloadImages) {
|
public LibraryScanResultDto scanLibrary(@RequestParam(value = "download_images", defaultValue = "true") boolean downloadImages) {
|
||||||
libraryService.scanGameLibrary();
|
StopWatch stopWatch = new StopWatch();
|
||||||
|
stopWatch.start();
|
||||||
|
|
||||||
if(downloadImages) downloadImages();
|
LibraryScanResultDto lscDto = new LibraryScanResultDto();
|
||||||
|
|
||||||
|
LibraryScanResult lsc = libraryService.scanGameLibrary();
|
||||||
|
lscDto.setNewGames(lsc.getNewGames());
|
||||||
|
lscDto.setDeletedGames(lsc.getDeletedGames());
|
||||||
|
lscDto.setNewUnmappableFiles(lsc.getNewUnmappableFiles());
|
||||||
|
lscDto.setTotalGames(lsc.getTotalGames());
|
||||||
|
|
||||||
|
if(downloadImages) {
|
||||||
|
ImageDownloadResultDto idrDto = downloadImages();
|
||||||
|
|
||||||
|
lscDto.setCoverDownloads(idrDto.getCoverDownloads());
|
||||||
|
lscDto.setScreenshotDownloads(idrDto.getScreenshotDownloads());
|
||||||
|
lscDto.setCompanyLogoDownloads(idrDto.getCompanyLogoDownloads());
|
||||||
|
}
|
||||||
|
|
||||||
|
stopWatch.stop();
|
||||||
|
lscDto.setScanDuration((int) stopWatch.getTotalTimeSeconds());
|
||||||
|
|
||||||
|
log.info("Library scan completed in {} seconds.", (int) stopWatch.getTotalTimeSeconds());
|
||||||
|
|
||||||
|
return lscDto;
|
||||||
}
|
}
|
||||||
|
|
||||||
@GetMapping(value = "/download-images")
|
@GetMapping(value = "/download-images")
|
||||||
public void downloadImages() {
|
public ImageDownloadResultDto downloadImages() {
|
||||||
imageService.downloadGameCoversFromIgdb();
|
ImageDownloadResultDto idrDto = new ImageDownloadResultDto();
|
||||||
imageService.downloadGameScreenshotsFromIgdb();
|
|
||||||
imageService.downloadCompanyLogosFromIgdb();
|
idrDto.setCoverDownloads(imageService.downloadGameCoversFromIgdb());
|
||||||
|
idrDto.setScreenshotDownloads(imageService.downloadGameScreenshotsFromIgdb());
|
||||||
|
idrDto.setCompanyLogoDownloads(imageService.downloadCompanyLogosFromIgdb());
|
||||||
|
|
||||||
log.info("Downloading images completed.");
|
log.info("Downloading images completed.");
|
||||||
|
|
||||||
|
return idrDto;
|
||||||
}
|
}
|
||||||
|
|
||||||
@GetMapping(value = "/files", produces = MediaType.APPLICATION_JSON_VALUE)
|
@GetMapping(value = "/files", produces = MediaType.APPLICATION_JSON_VALUE)
|
||||||
|
|||||||
@@ -42,7 +42,7 @@ public class ImageService {
|
|||||||
igdbImageClient = webclientBuilder.baseUrl(IgdbApiProperties.IMAGES_BASE_URL).build();
|
igdbImageClient = webclientBuilder.baseUrl(IgdbApiProperties.IMAGES_BASE_URL).build();
|
||||||
}
|
}
|
||||||
|
|
||||||
public void downloadGameCoversFromIgdb() {
|
public int downloadGameCoversFromIgdb() {
|
||||||
StopWatch stopWatch = new StopWatch();
|
StopWatch stopWatch = new StopWatch();
|
||||||
|
|
||||||
log.info("Starting game cover download...");
|
log.info("Starting game cover download...");
|
||||||
@@ -57,9 +57,10 @@ public class ImageService {
|
|||||||
stopWatch.stop();
|
stopWatch.stop();
|
||||||
|
|
||||||
log.info("Downloaded {} covers in {} seconds.", downloadCount, (int) stopWatch.getTotalTimeSeconds());
|
log.info("Downloaded {} covers in {} seconds.", downloadCount, (int) stopWatch.getTotalTimeSeconds());
|
||||||
|
return downloadCount;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void downloadGameScreenshotsFromIgdb() {
|
public int downloadGameScreenshotsFromIgdb() {
|
||||||
StopWatch stopWatch = new StopWatch();
|
StopWatch stopWatch = new StopWatch();
|
||||||
|
|
||||||
log.info("Starting game screenshot download...");
|
log.info("Starting game screenshot download...");
|
||||||
@@ -74,9 +75,10 @@ public class ImageService {
|
|||||||
stopWatch.stop();
|
stopWatch.stop();
|
||||||
|
|
||||||
log.info("Downloaded {} screenshots in {} seconds.", downloadCount, (int) stopWatch.getTotalTimeSeconds());
|
log.info("Downloaded {} screenshots in {} seconds.", downloadCount, (int) stopWatch.getTotalTimeSeconds());
|
||||||
|
return downloadCount;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void downloadCompanyLogosFromIgdb() {
|
public int downloadCompanyLogosFromIgdb() {
|
||||||
StopWatch stopWatch = new StopWatch();
|
StopWatch stopWatch = new StopWatch();
|
||||||
|
|
||||||
log.info("Starting company logo download...");
|
log.info("Starting company logo download...");
|
||||||
@@ -93,6 +95,7 @@ public class ImageService {
|
|||||||
stopWatch.stop();
|
stopWatch.stop();
|
||||||
|
|
||||||
log.info("Downloaded {} company logos in {} seconds.", downloadCount, (int) stopWatch.getTotalTimeSeconds());
|
log.info("Downloaded {} company logos in {} seconds.", downloadCount, (int) stopWatch.getTotalTimeSeconds());
|
||||||
|
return downloadCount;
|
||||||
}
|
}
|
||||||
|
|
||||||
private int saveImagesIntoCache(MultiValueMap<String, String> entityToImageIds, String imageSize, String imageType, String entityType) {
|
private int saveImagesIntoCache(MultiValueMap<String, String> entityToImageIds, String imageSize, String imageType, String entityType) {
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ package de.grimsi.gameyfin.service;
|
|||||||
|
|
||||||
import com.igdb.proto.Igdb;
|
import com.igdb.proto.Igdb;
|
||||||
import de.grimsi.gameyfin.dto.AutocompleteSuggestionDto;
|
import de.grimsi.gameyfin.dto.AutocompleteSuggestionDto;
|
||||||
|
import de.grimsi.gameyfin.dto.LibraryScanResult;
|
||||||
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.igdb.IgdbWrapper;
|
||||||
@@ -57,7 +58,7 @@ public class LibraryService {
|
|||||||
return gamefiles;
|
return gamefiles;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void scanGameLibrary() {
|
public LibraryScanResult scanGameLibrary() {
|
||||||
StopWatch stopWatch = new StopWatch();
|
StopWatch stopWatch = new StopWatch();
|
||||||
|
|
||||||
log.info("Starting scan...");
|
log.info("Starting scan...");
|
||||||
@@ -120,6 +121,13 @@ public class LibraryService {
|
|||||||
|
|
||||||
log.info("Scan finished in {} seconds: Found {} new games, deleted {} games, could not map {} files/folders, {} games total.",
|
log.info("Scan finished in {} seconds: Found {} new games, deleted {} games, could not map {} files/folders, {} games total.",
|
||||||
(int) stopWatch.getTotalTimeSeconds(), newDetectedGames.size(), deletedGames.size() + deletedUnmappableFiles.size(), newUnmappedFilesCounter.get(), detectedGameRepository.count());
|
(int) stopWatch.getTotalTimeSeconds(), newDetectedGames.size(), deletedGames.size() + deletedUnmappableFiles.size(), newUnmappedFilesCounter.get(), detectedGameRepository.count());
|
||||||
|
|
||||||
|
return LibraryScanResult.builder()
|
||||||
|
.newGames(newDetectedGames.size())
|
||||||
|
.deletedGames(deletedGames.size() + deletedUnmappableFiles.size())
|
||||||
|
.newUnmappableFiles(newUnmappedFilesCounter.get())
|
||||||
|
.totalGames((int) detectedGameRepository.count())
|
||||||
|
.build();
|
||||||
}
|
}
|
||||||
|
|
||||||
public List<AutocompleteSuggestionDto> getAutocompleteSuggestions(String searchTerm, int limit) {
|
public List<AutocompleteSuggestionDto> getAutocompleteSuggestions(String searchTerm, int limit) {
|
||||||
|
|||||||
+1
-1
@@ -5,7 +5,7 @@
|
|||||||
<parent>
|
<parent>
|
||||||
<artifactId>gameyfin</artifactId>
|
<artifactId>gameyfin</artifactId>
|
||||||
<groupId>de.grimsi</groupId>
|
<groupId>de.grimsi</groupId>
|
||||||
<version>1.1.4-RC1</version>
|
<version>1.2.0</version>
|
||||||
</parent>
|
</parent>
|
||||||
<modelVersion>4.0.0</modelVersion>
|
<modelVersion>4.0.0</modelVersion>
|
||||||
|
|
||||||
|
|||||||
@@ -1,8 +1,11 @@
|
|||||||
import {Observable} from "rxjs";
|
import {Observable} from "rxjs";
|
||||||
import {HttpResponse} from "@angular/common/http";
|
import {LibraryScanResultDto} from "../models/dtos/LibraryScanResultDto";
|
||||||
|
import {ImageDownloadResultDto} from "../models/dtos/ImageDownloadResultDto";
|
||||||
|
|
||||||
export interface LibraryApi {
|
export interface LibraryApi {
|
||||||
scanLibrary(): Observable<HttpResponse<Response>>;
|
scanLibrary(): Observable<LibraryScanResultDto>;
|
||||||
downloadImages(): Observable<HttpResponse<Response>>;
|
|
||||||
|
downloadImages(): Observable<ImageDownloadResultDto>;
|
||||||
|
|
||||||
getFiles(): Observable<string[]>;
|
getFiles(): Observable<string[]>;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -17,13 +17,11 @@
|
|||||||
<p>{{game.summary}}</p>
|
<p>{{game.summary}}</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div *ngIf="game.companies !== undefined && game.companies.length > 0">
|
<div *ngIf="companiesWithLogo.length > 0">
|
||||||
<h2>Developed by</h2>
|
<h2>Developed by</h2>
|
||||||
<div fxLayout="row wrap" fxLayoutGap="8px grid">
|
<div fxLayout="row wrap" fxLayoutGap="8px grid">
|
||||||
<div *ngFor="let company of game.companies">
|
<div *ngFor="let company of companiesWithLogo">
|
||||||
<img *ngIf="company.logoId !== undefined && company.logoId.length > 0" style="height: 52px;"
|
<img style="height: 52px;" src="v1/images/{{company.logoId}}" alt="{{company.name}}" [matTooltip]="company.name">
|
||||||
src="v1/images/{{company.logoId}}" alt="{{company.name}}">
|
|
||||||
<span *ngIf="company.logoId.length > 0">{{company.name}}</span>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ import {Component, HostListener} from '@angular/core';
|
|||||||
import {ActivatedRoute, Params, Router} from "@angular/router";
|
import {ActivatedRoute, Params, Router} from "@angular/router";
|
||||||
import {DetectedGameDto} from "../../models/dtos/DetectedGameDto";
|
import {DetectedGameDto} from "../../models/dtos/DetectedGameDto";
|
||||||
import {GamesService} from "../../services/games.service";
|
import {GamesService} from "../../services/games.service";
|
||||||
|
import {CompanyDto} from "../../models/dtos/CompanyDto";
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'app-game-detail-view',
|
selector: 'app-game-detail-view',
|
||||||
@@ -12,13 +13,20 @@ export class GameDetailViewComponent {
|
|||||||
|
|
||||||
game!: DetectedGameDto;
|
game!: DetectedGameDto;
|
||||||
|
|
||||||
|
companiesWithLogo: CompanyDto[]= [];
|
||||||
|
|
||||||
gridColumnCount: number;
|
gridColumnCount: number;
|
||||||
|
|
||||||
constructor(private route: ActivatedRoute,
|
constructor(private route: ActivatedRoute,
|
||||||
private router: Router,
|
private router: Router,
|
||||||
private gamesService: GamesService) {
|
private gamesService: GamesService) {
|
||||||
this.gamesService.getGame(this.route.snapshot.params['slug']).subscribe({
|
this.gamesService.getGame(this.route.snapshot.params['slug']).subscribe({
|
||||||
next: game => this.game = game,
|
next: game => {
|
||||||
|
this.game = game;
|
||||||
|
if(game.companies !== undefined) {
|
||||||
|
this.companiesWithLogo = game.companies.filter(c => c.logoId !== undefined && c.logoId.length > 0);
|
||||||
|
}
|
||||||
|
},
|
||||||
error: error => {
|
error: error => {
|
||||||
if (error.status === 404) {
|
if (error.status === 404) {
|
||||||
this.router.navigate(['/library']);
|
this.router.navigate(['/library']);
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
import {Component} from '@angular/core';
|
import {Component} from '@angular/core';
|
||||||
import {LibraryService} from "../../services/library.service";
|
import {LibraryService} from "../../services/library.service";
|
||||||
import {MatSnackBar} from '@angular/material/snack-bar';
|
import {MatSnackBar} from '@angular/material/snack-bar';
|
||||||
import {timeInterval} from "rxjs";
|
|
||||||
import {Router} from "@angular/router";
|
import {Router} from "@angular/router";
|
||||||
import {GamesService} from "../../services/games.service";
|
import {GamesService} from "../../services/games.service";
|
||||||
import {ThemingService} from "../../services/theming.service";
|
import {ThemingService} from "../../services/theming.service";
|
||||||
@@ -26,11 +25,27 @@ export class HeaderComponent {
|
|||||||
}
|
}
|
||||||
|
|
||||||
scanLibrary(): void {
|
scanLibrary(): void {
|
||||||
this.libraryService.scanLibrary().pipe(timeInterval()).subscribe({
|
this.libraryService.scanLibrary().subscribe({
|
||||||
next: value => {
|
next: result => {
|
||||||
// Refresh the current page "angular style"
|
// Refresh the current page "angular style"
|
||||||
this.router.navigate([this.router.url]).then(() =>
|
this.router.navigate([this.router.url]).then(() => {
|
||||||
this.snackBar.open(`Library scan completed in ${Math.trunc(value.interval / 1000)} seconds.`, undefined, {duration: 5000})
|
const snackBarDuration: number = 10000;
|
||||||
|
|
||||||
|
let snackbarContent: string = 'Library scan completed in ' + result.scanDuration + ' seconds:\n' +
|
||||||
|
'- ' + result.newGames + ' new games\n' +
|
||||||
|
'- ' + result.deletedGames + ' games removed\n' +
|
||||||
|
'- ' + result.newUnmappableFiles + ' files/folders could not be mapped\n' +
|
||||||
|
'- ' + result.totalGames + ' games currently in your library';
|
||||||
|
|
||||||
|
if (result.companyLogoDownloads !== undefined && result.coverDownloads !== undefined && result.screenshotDownloads !== undefined) {
|
||||||
|
snackbarContent = snackbarContent.concat('\n' +
|
||||||
|
'- ' + result.coverDownloads + ' covers downloaded\n' +
|
||||||
|
'- ' + result.screenshotDownloads + ' screenshots downloaded\n' +
|
||||||
|
'- ' + result.companyLogoDownloads + ' company logos downloaded');
|
||||||
|
}
|
||||||
|
|
||||||
|
this.snackBar.open(snackbarContent, undefined, {duration: snackBarDuration});
|
||||||
|
}
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
error: error => this.snackBar.open(`Error while scanning library: ${error.error.message}`, undefined, {duration: 5000})
|
error: error => this.snackBar.open(`Error while scanning library: ${error.error.message}`, undefined, {duration: 5000})
|
||||||
|
|||||||
@@ -0,0 +1,5 @@
|
|||||||
|
export class ImageDownloadResultDto {
|
||||||
|
coverDownloads!: number;
|
||||||
|
screenshotDownloads!: number;
|
||||||
|
companyLogoDownloads!: number;
|
||||||
|
}
|
||||||
@@ -0,0 +1,10 @@
|
|||||||
|
export class LibraryScanResultDto {
|
||||||
|
newGames!: number;
|
||||||
|
deletedGames!: number;
|
||||||
|
newUnmappableFiles!: number;
|
||||||
|
totalGames!: number;
|
||||||
|
coverDownloads!: number;
|
||||||
|
screenshotDownloads!: number;
|
||||||
|
companyLogoDownloads!: number;
|
||||||
|
scanDuration!: number;
|
||||||
|
}
|
||||||
@@ -1,7 +1,9 @@
|
|||||||
import {Injectable} from '@angular/core';
|
import {Injectable} from '@angular/core';
|
||||||
import {HttpClient, HttpResponse} from "@angular/common/http";
|
import {HttpClient} from "@angular/common/http";
|
||||||
import {Observable} from "rxjs";
|
import {Observable} from "rxjs";
|
||||||
import {LibraryApi} from "../api/LibraryApi";
|
import {LibraryApi} from "../api/LibraryApi";
|
||||||
|
import {LibraryScanResultDto} from "../models/dtos/LibraryScanResultDto";
|
||||||
|
import {ImageDownloadResultDto} from "../models/dtos/ImageDownloadResultDto";
|
||||||
|
|
||||||
@Injectable({
|
@Injectable({
|
||||||
providedIn: 'root'
|
providedIn: 'root'
|
||||||
@@ -13,12 +15,12 @@ export class LibraryService implements LibraryApi {
|
|||||||
constructor(private http: HttpClient) {
|
constructor(private http: HttpClient) {
|
||||||
}
|
}
|
||||||
|
|
||||||
scanLibrary(): Observable<HttpResponse<Response>> {
|
scanLibrary(): Observable<LibraryScanResultDto> {
|
||||||
return this.http.get<HttpResponse<Response>>(`${this.apiPath}/scan`);
|
return this.http.get<LibraryScanResultDto>(`${this.apiPath}/scan`);
|
||||||
}
|
}
|
||||||
|
|
||||||
downloadImages(): Observable<HttpResponse<Response>> {
|
downloadImages(): Observable<ImageDownloadResultDto> {
|
||||||
return this.http.get<HttpResponse<Response>>(`${this.apiPath}/download-images`);
|
return this.http.get<ImageDownloadResultDto>(`${this.apiPath}/download-images`);
|
||||||
}
|
}
|
||||||
|
|
||||||
getFiles(): Observable<string[]> {
|
getFiles(): Observable<string[]> {
|
||||||
|
|||||||
@@ -5,7 +5,7 @@
|
|||||||
|
|
||||||
<groupId>de.grimsi</groupId>
|
<groupId>de.grimsi</groupId>
|
||||||
<artifactId>gameyfin</artifactId>
|
<artifactId>gameyfin</artifactId>
|
||||||
<version>1.1.4-RC1</version>
|
<version>1.2.0</version>
|
||||||
<name>gameyfin</name>
|
<name>gameyfin</name>
|
||||||
<description>gameyfin</description>
|
<description>gameyfin</description>
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user