Merge pull request #4 from grimsi/v1.0.1

Release version 1.0.1
This commit is contained in:
Simon
2022-08-10 22:53:58 +02:00
committed by GitHub
31 changed files with 1715 additions and 57 deletions
+4 -3
View File
@@ -1,3 +1,5 @@
<img src="https://raw.githubusercontent.com/grimsi/gameyfin/v1.0.1/assets/Gameyfin_Logo_White_Border.svg" data-canonical-src="https://gyazo.com/eb5c5741b6a9a16c692170a41a49c858.png" height="128px" alt="Gameyfin Logo"/>
# Gameyfin # Gameyfin
A simple game library manager. A simple game library manager.
Name and functionality inspired by [Jellyfin](https://jellyfin.org/). Name and functionality inspired by [Jellyfin](https://jellyfin.org/).
@@ -38,10 +40,9 @@ Since Gameyfin loads information from IGDB, you need to register yourself there.
### Bare metal ### Bare metal
1. Make sure you have a JRE or JDK with version 18 or greater installed 1. Make sure you have a JRE or JDK with version 18 or greater installed
2. Download the latest .jar and `gameyfin.properties` file from the releases page 2. Download the latest `gameyfin.jar` and `gameyfin.properties` file from the releases page
3. Edit the config options in the `gameyfin.properties` file 3. Edit the config options in the `gameyfin.properties` file
4. Use the following command to start Gameyfin: `java "-Dspring.config.name=application,database,gameyfin,secure" -jar <name of the downloaded jar file>.jar` 4. Use the following command to start Gameyfin: `java -jar gameyfin.jar`
(note the quotation marks around the first argument)
5. Open the address of your Gameyfin host in your browser, Gameyfin runs under port 8080 by default 5. Open the address of your Gameyfin host in your browser, Gameyfin runs under port 8080 by default
## Screenshots ## Screenshots
+1
View File
@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 450 414"><defs><style>.cls-1{fill:#fff;}</style></defs><title>Element 11</title><g id="Ebene_2" data-name="Ebene 2"><g id="Ebene_2-2" data-name="Ebene 2"><polygon class="cls-1" points="234 60.48 234 85.23 256 54.32 234 60.48"/><polygon class="cls-1" points="450 0 324 35.28 253.125 118.125 450 63 450 0"/><polygon class="cls-1" points="234 348.48 306 328.32 306 184.32 450 144 450 90 234 150.48 234 348.48"/><polygon class="cls-1" points="72 177.84 192 144.24 216 110.52 216 65.52 0 126 0 414 72 312.84 72 177.84"/><polygon class="cls-1" points="144 245.68 144 301.68 81 319.32 0 414 216 353.52 216 209.52 162 224.64 144 245.68"/></g></g></svg>

After

Width:  |  Height:  |  Size: 697 B

+1
View File
@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 451.94942 416.224"><defs><style>.cls-1{fill:#fff;stroke:#000;stroke-miterlimit:10;}</style></defs><title>Element 10</title><g id="Ebene_2" data-name="Ebene 2"><g id="Ebene_2-2" data-name="Ebene 2"><polygon class="cls-1" points="235.449 61.139 235.449 85.889 257.449 54.979 235.449 61.139"/><polygon class="cls-1" points="451.449 0.659 325.449 35.939 254.574 118.784 451.449 63.659 451.449 0.659"/><polygon class="cls-1" points="235.449 349.139 307.449 328.979 307.449 184.979 451.449 144.659 451.449 90.659 235.449 151.139 235.449 349.139"/><polygon class="cls-1" points="73.449 178.499 193.449 144.899 217.449 111.179 217.449 66.179 1.449 126.659 1.449 414.659 73.449 313.499 73.449 178.499"/><polygon class="cls-1" points="145.449 246.339 145.449 302.339 82.449 319.979 1.449 414.659 217.449 354.179 217.449 210.179 163.449 225.299 145.449 246.339"/></g></g></svg>

After

Width:  |  Height:  |  Size: 919 B

+1380
View File
File diff suppressed because it is too large Load Diff
+2 -1
View File
@@ -7,7 +7,7 @@
<parent> <parent>
<artifactId>gameyfin</artifactId> <artifactId>gameyfin</artifactId>
<groupId>de.grimsi</groupId> <groupId>de.grimsi</groupId>
<version>1.0.0</version> <version>1.0.1</version>
</parent> </parent>
<artifactId>gameyfin-backend</artifactId> <artifactId>gameyfin-backend</artifactId>
@@ -131,6 +131,7 @@
</dependencies> </dependencies>
<build> <build>
<finalName>gameyfin-${project.parent.version}</finalName>
<resources> <resources>
<resource> <resource>
<directory>${basedir}/src/main/resources</directory> <directory>${basedir}/src/main/resources</directory>
@@ -1,13 +1,16 @@
package de.grimsi.gameyfin; package de.grimsi.gameyfin;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.builder.SpringApplicationBuilder;
@SpringBootApplication @SpringBootApplication
public class GameyfinApplication { public class GameyfinApplication {
public static void main(String[] args) { public static void main(String[] args) {
SpringApplication.run(GameyfinApplication.class, args); new SpringApplicationBuilder(GameyfinApplication.class)
.properties("spring.config.name=application,gameyfin,database,secure")
.build()
.run(args);
} }
} }
@@ -5,7 +5,9 @@ import io.github.resilience4j.bulkhead.BulkheadConfig;
import io.github.resilience4j.ratelimiter.RateLimiter; import io.github.resilience4j.ratelimiter.RateLimiter;
import io.github.resilience4j.ratelimiter.RateLimiterConfig; import io.github.resilience4j.ratelimiter.RateLimiterConfig;
import io.netty.handler.logging.LogLevel; import io.netty.handler.logging.LogLevel;
import lombok.Getter;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.web.reactive.function.client.WebClientCustomizer; import org.springframework.boot.web.reactive.function.client.WebClientCustomizer;
import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Configuration;
import org.springframework.http.HttpHeaders; import org.springframework.http.HttpHeaders;
@@ -20,24 +22,32 @@ import reactor.netty.transport.logging.AdvancedByteBufFormat;
import java.time.Duration; import java.time.Duration;
@Slf4j @Slf4j
@Getter
@Configuration @Configuration
public class WebClientConfig implements WebClientCustomizer { public class WebClientConfig implements WebClientCustomizer {
// The IGDB API has a rate limit of 4 req/s private final RateLimiter igdbRateLimiter;
public static final RateLimiter IGDB_RATE_LIMITER = RateLimiter.of("igdb-rate-limiter", private final Bulkhead igdbConcurrencyLimiter;
RateLimiterConfig.custom()
.limitForPeriod(4)
.limitRefreshPeriod(Duration.ofSeconds(1))
.timeoutDuration(Duration.ofMinutes(1))
.build());
// According to the docs, there is a maximum of 8 concurrent requests, but in my tests the actual limit was 4
// and even then it sometimes failed, so I set it to 3 to be sure public WebClientConfig(@Value("${gameyfin.igdb.api.max-concurrent-requests}") int maxConcurrentRequestsToIgdb,
public static final Bulkhead IGDB_CONCURRENCY_LIMITER = Bulkhead.of("igdb-concurrency-limiter", @Value("${gameyfin.igdb.api.max-requests-per-second}") int maxRequestsPerSecondToIgdb) {
BulkheadConfig.custom()
.maxConcurrentCalls(2) log.info("IGDB API connection properties: max. {} req/s, max. {} concurrent requests", maxRequestsPerSecondToIgdb, maxConcurrentRequestsToIgdb);
.maxWaitDuration(Duration.ofMinutes(1))
.build()); igdbRateLimiter = RateLimiter.of("igdb-rate-limiter",
RateLimiterConfig.custom()
.limitForPeriod(maxRequestsPerSecondToIgdb)
.limitRefreshPeriod(Duration.ofSeconds(1))
.timeoutDuration(Duration.ofMinutes(1))
.build());
igdbConcurrencyLimiter = Bulkhead.of("igdb-concurrency-limiter",
BulkheadConfig.custom()
.maxConcurrentCalls(maxConcurrentRequestsToIgdb)
.maxWaitDuration(Duration.ofMinutes(1))
.build());
}
@Override @Override
public void customize(WebClient.Builder webClientBuilder) { public void customize(WebClient.Builder webClientBuilder) {
@@ -0,0 +1,7 @@
package de.grimsi.gameyfin.exceptions;
public class DownloadAbortedException extends RuntimeException {
public DownloadAbortedException() {
super();
}
}
@@ -38,6 +38,9 @@ public class IgdbWrapper {
@Autowired @Autowired
private WebClient.Builder webclientBuilder; private WebClient.Builder webclientBuilder;
@Autowired
private WebClientConfig webClientConfig;
private WebClient twitchApiClient; private WebClient twitchApiClient;
private WebClient igdbApiClient; private WebClient igdbApiClient;
@@ -173,8 +176,8 @@ public class IgdbWrapper {
.bodyValue(query) .bodyValue(query)
.retrieve() .retrieve()
.bodyToMono(responseClass) .bodyToMono(responseClass)
.transformDeferred(BulkheadOperator.of(WebClientConfig.IGDB_CONCURRENCY_LIMITER)) .transformDeferred(BulkheadOperator.of(webClientConfig.getIgdbConcurrencyLimiter()))
.transformDeferred(RateLimiterOperator.of(WebClientConfig.IGDB_RATE_LIMITER)) .transformDeferred(RateLimiterOperator.of(webClientConfig.getIgdbRateLimiter()))
.block(); .block();
} }
} }
@@ -8,9 +8,12 @@ import de.grimsi.gameyfin.service.LibraryService;
import de.grimsi.gameyfin.util.ProtobufUtil; import de.grimsi.gameyfin.util.ProtobufUtil;
import lombok.RequiredArgsConstructor; import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.apache.commons.io.FileUtils;
import org.springframework.stereotype.Component; import org.springframework.stereotype.Component;
import org.springframework.util.StopWatch;
import org.springframework.util.StringUtils; import org.springframework.util.StringUtils;
import java.io.File;
import java.io.IOException; import java.io.IOException;
import java.nio.file.FileSystem; import java.nio.file.FileSystem;
import java.nio.file.FileSystems; import java.nio.file.FileSystems;
@@ -49,7 +52,7 @@ public class GameMapper {
.themes(ThemeMapper.toThemes(g.getThemesList())) .themes(ThemeMapper.toThemes(g.getThemesList()))
.playerPerspectives(PlayerPerspectiveMapper.toPlayerPerspectives(g.getPlayerPerspectivesList())) .playerPerspectives(PlayerPerspectiveMapper.toPlayerPerspectives(g.getPlayerPerspectivesList()))
.path(path.toString()) .path(path.toString())
.diskSize(calculateDiskSize(path)) .diskSize(calculateDiskSize(g, path))
.build(); .build();
} }
@@ -93,15 +96,18 @@ public class GameMapper {
return modes.stream().mapToInt(Igdb.MultiplayerMode::getOnlinecoopmax).max().orElse(0); return modes.stream().mapToInt(Igdb.MultiplayerMode::getOnlinecoopmax).max().orElse(0);
} }
private static long calculateDiskSize(Path path) { private static long calculateDiskSize(Igdb.Game g, Path path) {
try(Stream<Path> pathStream = Files.walk(path)) { StopWatch stopWatch = new StopWatch();
return pathStream log.info("Calculating disk size for game '{}'...", g.getName());
.filter(p -> p.toFile().isFile())
.mapToLong(p -> p.toFile().length()) stopWatch.start();
.sum();
} catch (IOException e) { // Some benchmarks I did have shown that trying to parallelize this process makes it slower instead of faster
log.error("Unable to calculate size for path '{}'.", path); long fileSize = FileUtils.sizeOfDirectory(path.toFile());
return -1;
} stopWatch.stop();
log.info("Calculated disk size for game '{}' in {} seconds", g.getName(), (int) stopWatch.getTotalTimeSeconds());
return fileSize;
} }
} }
@@ -2,18 +2,19 @@ package de.grimsi.gameyfin.service;
import de.grimsi.gameyfin.entities.Company; import de.grimsi.gameyfin.entities.Company;
import de.grimsi.gameyfin.entities.DetectedGame; import de.grimsi.gameyfin.entities.DetectedGame;
import de.grimsi.gameyfin.exceptions.DownloadAbortedException;
import de.grimsi.gameyfin.igdb.IgdbApiProperties; import de.grimsi.gameyfin.igdb.IgdbApiProperties;
import de.grimsi.gameyfin.repositories.DetectedGameRepository; import de.grimsi.gameyfin.repositories.DetectedGameRepository;
import lombok.RequiredArgsConstructor; import lombok.RequiredArgsConstructor;
import lombok.SneakyThrows; import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.apache.catalina.connector.ClientAbortException;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value; import org.springframework.beans.factory.annotation.Value;
import org.springframework.core.io.ByteArrayResource; import org.springframework.core.io.ByteArrayResource;
import org.springframework.core.io.Resource; import org.springframework.core.io.Resource;
import org.springframework.core.io.buffer.DataBuffer; import org.springframework.core.io.buffer.DataBuffer;
import org.springframework.core.io.buffer.DataBufferUtils; import org.springframework.core.io.buffer.DataBufferUtils;
import org.springframework.data.repository.core.RepositoryCreationException;
import org.springframework.http.HttpStatus; import org.springframework.http.HttpStatus;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import org.springframework.util.LinkedMultiValueMap; import org.springframework.util.LinkedMultiValueMap;
@@ -63,7 +64,7 @@ public class DownloadService {
public String getDownloadFileName(DetectedGame g) { public String getDownloadFileName(DetectedGame g) {
Path path = Path.of(g.getPath()); Path path = Path.of(g.getPath());
if(!path.toFile().isDirectory()) return getFilenameWithExtension(path); if (!path.toFile().isDirectory()) return getFilenameWithExtension(path);
return getFilenameWithExtension(path) + ".zip"; return getFilenameWithExtension(path) + ".zip";
} }
@@ -87,8 +88,16 @@ public class DownloadService {
Path path = Path.of(game.getPath()); Path path = Path.of(game.getPath());
if(path.toFile().isDirectory()) { if (path.toFile().isDirectory()) {
downloadFilesAsZip(path, outputStream);
try {
downloadFilesAsZip(path, outputStream);
} catch(DownloadAbortedException e) {
stopWatch.stop();
log.info("Download of game {} was aborted by client after {} seconds", game.getTitle(), (int) stopWatch.getTotalTimeSeconds());
return;
}
} else { } else {
downloadFile(path, outputStream); downloadFile(path, outputStream);
} }
@@ -99,7 +108,6 @@ public class DownloadService {
} }
public void downloadGameCoversFromIgdb() { public void downloadGameCoversFromIgdb() {
StopWatch stopWatch = new StopWatch(); StopWatch stopWatch = new StopWatch();
@@ -163,7 +171,7 @@ public class DownloadService {
} }
private void downloadFilesAsZip(Path path, OutputStream outputStream) { private void downloadFilesAsZip(Path path, OutputStream outputStream) {
ZipOutputStream zos = new ZipOutputStream(outputStream){{ ZipOutputStream zos = new ZipOutputStream(outputStream) {{
def.setLevel(Deflater.NO_COMPRESSION); def.setLevel(Deflater.NO_COMPRESSION);
}}; }};
@@ -180,6 +188,9 @@ public class DownloadService {
}); });
zos.close(); zos.close();
} catch (ClientAbortException e) {
// Aborted downloads will be handled gracefully
throw new DownloadAbortedException();
} catch (IOException e) { } catch (IOException e) {
log.error("Error while zipping files:", e); log.error("Error while zipping files:", e);
} }
@@ -14,8 +14,15 @@ gameyfin.db=${gameyfin.root}/.gameyfin/db
# File extensions which gameyfin will recognize as game files # File extensions which gameyfin will recognize as game files
gameyfin.file-extensions=iso, zip, rar, 7z, exe gameyfin.file-extensions=iso, zip, rar, 7z, exe
# List of IGDB platform enums to limit search results. FOr possible values see: https://api-docs.igdb.com/#platform # List of IGDB platform enums to limit search results. For possible values see: https://api-docs.igdb.com/#platform
gameyfin.igdb.config.preferred-platforms=6 gameyfin.igdb.config.preferred-platforms=6
# Twitch Client ID and Client Secret # Twitch Client ID and Client Secret
gameyfin.igdb.api.client-id= gameyfin.igdb.api.client-id=
gameyfin.igdb.api.client-secret= gameyfin.igdb.api.client-secret=
# The IGDB API has a rate limit of 4 req/s
gameyfin.igdb.api.max-requests-per-second=4
# According to the docs, there is a maximum of 8 concurrent requests, but in my tests the actual limit was 4 and even then it sometimes failed, so I set it to 2 to be sure
gameyfin.igdb.api.max-concurrent-requests=2
+1 -1
View File
@@ -13,4 +13,4 @@ COPY ${JAR_FILE} /opt/gameyfin/gameyfin.jar
WORKDIR /opt/gameyfin WORKDIR /opt/gameyfin
ENTRYPOINT ["java", "-Dspring.config.name=application,gameyfin,database,secure", "-jar", "gameyfin.jar"] ENTRYPOINT ["java", "-jar", "gameyfin.jar"]
+2 -2
View File
@@ -1,12 +1,12 @@
{ {
"name": "frontend", "name": "frontend",
"version": "0.0.1-SNAPSHOT", "version": "@project.version@",
"lockfileVersion": 2, "lockfileVersion": 2,
"requires": true, "requires": true,
"packages": { "packages": {
"": { "": {
"name": "frontend", "name": "frontend",
"version": "0.0.1-SNAPSHOT", "version": "@project.version@",
"dependencies": { "dependencies": {
"@angular/animations": "^14.0.0", "@angular/animations": "^14.0.0",
"@angular/cdk": "^14.1.0", "@angular/cdk": "^14.1.0",
+1 -1
View File
@@ -1,6 +1,6 @@
{ {
"name": "frontend", "name": "frontend",
"version": "0.0.1-SNAPSHOT", "version": "@project.version@",
"scripts": { "scripts": {
"ng": "ng", "ng": "ng",
"start": "ng serve", "start": "ng serve",
+9 -9
View File
@@ -5,7 +5,7 @@
<parent> <parent>
<artifactId>gameyfin</artifactId> <artifactId>gameyfin</artifactId>
<groupId>de.grimsi</groupId> <groupId>de.grimsi</groupId>
<version>1.0.0</version> <version>1.0.1</version>
</parent> </parent>
<modelVersion>4.0.0</modelVersion> <modelVersion>4.0.0</modelVersion>
@@ -18,6 +18,13 @@
</properties> </properties>
<build> <build>
<resources>
<resource>
<directory>./dist/frontend</directory>
<targetPath>static</targetPath>
</resource>
</resources>
<plugins> <plugins>
<!-- clean the dist directory used by Angular --> <!-- clean the dist directory used by Angular -->
<plugin> <plugin>
@@ -63,19 +70,12 @@
<goal>npm</goal> <goal>npm</goal>
</goals> </goals>
<configuration> <configuration>
<arguments>run build --prod</arguments> <arguments>run build --omit=dev</arguments>
</configuration> </configuration>
</execution> </execution>
</executions> </executions>
</plugin> </plugin>
</plugins> </plugins>
<resources>
<resource>
<directory>./dist/frontend</directory>
<targetPath>static</targetPath>
</resource>
</resources>
</build> </build>
</project> </project>
@@ -5,6 +5,8 @@
<span class="spacer"></span> <span class="spacer"></span>
<img class="logo" src="assets/Gameyfin_Logo_256px.png" alt="Gameyfin Logo">
<button mat-icon-button matTooltip="Reload library" (click)="reloadLibrary()" *ngIf="onLibraryScreen()"> <button mat-icon-button matTooltip="Reload library" (click)="reloadLibrary()" *ngIf="onLibraryScreen()">
<mat-icon>refresh</mat-icon> <mat-icon>refresh</mat-icon>
</button> </button>
@@ -1,3 +1,11 @@
.spacer { .spacer {
flex: 1 1 auto; flex: 1 1 auto;
} }
.logo {
height: 36px;
position: absolute;
right: 0;
left: 0;
margin: 0 auto;
}
Binary file not shown.

After

Width:  |  Height:  |  Size: 4.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 385 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 487 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 387 B

+9
View File
@@ -0,0 +1,9 @@
<?xml version="1.0" encoding="utf-8"?>
<browserconfig>
<msapplication>
<tile>
<square150x150logo src="/mstile-150x150.png"/>
<TileColor>#ffffff</TileColor>
</tile>
</msapplication>
</browserconfig>
Binary file not shown.

After

Width:  |  Height:  |  Size: 644 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 988 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.4 KiB

+180
View File
@@ -0,0 +1,180 @@
<?xml version="1.0" standalone="no"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 20010904//EN"
"http://www.w3.org/TR/2001/REC-SVG-20010904/DTD/svg10.dtd">
<svg version="1.0" xmlns="http://www.w3.org/2000/svg"
width="900.000000pt" height="900.000000pt" viewBox="0 0 900.000000 900.000000"
preserveAspectRatio="xMidYMid meet">
<metadata>
Created by potrace 1.14, written by Peter Selinger 2001-2017
</metadata>
<g transform="translate(0.000000,900.000000) scale(0.100000,-0.100000)"
fill="#000000" stroke="none">
<path d="M8920 8618 c-36 -10 -104 -30 -152 -44 -175 -48 -273 -75 -288 -79
-8 -2 -15 -4 -15 -5 0 -1 -7 -3 -15 -5 -25 -6 -271 -75 -325 -91 -27 -8 -61
-17 -75 -19 -14 -3 -36 -10 -50 -15 -25 -9 -172 -50 -200 -55 -8 -2 -16 -4
-17 -5 -2 -2 -6 -3 -10 -5 -5 -1 -20 -5 -34 -9 -14 -4 -32 -8 -40 -10 -8 -1
-39 -9 -69 -18 -113 -34 -196 -58 -215 -63 -11 -2 -47 -12 -80 -21 -33 -9 -64
-17 -70 -19 -5 -1 -17 -5 -25 -8 -15 -6 -154 -44 -188 -52 -9 -2 -82 -23 -162
-46 -80 -22 -156 -43 -169 -46 -14 -2 -28 -6 -33 -9 -4 -3 -16 -7 -25 -9 -10
-2 -54 -14 -98 -27 -91 -25 -91 -25 -225 -188 -25 -30 -50 -59 -56 -65 -5 -5
-35 -39 -65 -75 -30 -36 -58 -70 -64 -76 -5 -6 -28 -32 -50 -59 -22 -26 -45
-53 -51 -59 -6 -6 -51 -58 -100 -116 -122 -144 -136 -161 -159 -185 -11 -12
-36 -41 -55 -66 -19 -24 -40 -48 -46 -54 -7 -5 -36 -39 -65 -75 -29 -36 -58
-70 -64 -76 -5 -6 -31 -35 -56 -65 -26 -30 -93 -108 -149 -174 -57 -66 -106
-122 -109 -125 -4 -3 -14 -15 -22 -27 -14 -20 -118 -141 -147 -169 -15 -15 2
-29 21 -17 6 4 17 8 22 9 6 1 118 32 250 69 132 37 245 68 250 70 6 1 30 8 55
15 25 7 50 14 55 16 6 1 30 7 55 15 25 7 50 14 55 15 6 2 51 15 100 29 50 14
99 27 110 29 11 2 33 9 49 15 16 7 31 9 34 6 3 -2 10 -1 16 4 7 6 23 12 36 15
14 3 50 12 80 21 30 9 62 17 70 19 8 2 42 12 75 22 59 18 73 21 98 27 6 1 18
6 25 10 6 5 12 6 12 3 0 -3 18 1 40 9 22 8 40 12 40 10 0 -3 12 0 28 6 22 9
234 70 292 84 8 2 45 13 83 24 37 11 68 20 70 20 1 0 9 2 17 5 17 6 100 28
116 32 6 1 38 10 70 20 33 9 64 18 69 19 22 5 141 37 155 42 8 4 20 7 25 8 15
4 261 73 335 94 54 16 119 33 140 36 3 0 10 3 15 6 9 6 23 9 85 24 16 4 303
85 385 109 80 24 419 118 430 120 6 1 20 6 33 10 l22 7 0 630 c0 346 -3 628
-7 628 -5 -1 -37 -10 -73 -21z"/>
<path d="M5050 7534 c-25 -8 -54 -17 -65 -19 -11 -3 -76 -21 -145 -41 -69 -20
-133 -37 -142 -37 -12 -1 -18 -9 -18 -27 1 -14 1 -53 0 -88 0 -35 2 -67 5 -73
4 -5 2 -9 -4 -9 -7 0 -7 -8 -1 -24 5 -13 6 -26 3 -29 -4 -5 -6 -143 -4 -219 1
-22 21 -24 21 -3 0 8 4 15 8 15 4 0 16 15 27 33 10 17 21 34 25 37 3 3 28 37
55 75 27 39 53 75 57 80 35 44 152 214 161 232 6 13 14 23 18 23 4 0 18 17 30
38 13 20 28 40 33 44 18 12 -15 8 -64 -8z"/>
<path d="M4309 7326 c-2 -2 -17 -6 -34 -9 -16 -3 -61 -15 -100 -26 -121 -36
-175 -52 -193 -56 -19 -5 -98 -28 -149 -44 -18 -5 -33 -7 -33 -4 0 3 -7 -1
-17 -8 -9 -8 -17 -13 -17 -11 -1 2 -13 1 -27 -3 -14 -3 -34 -8 -45 -10 -10 -2
-19 -4 -19 -5 0 -1 -7 -3 -15 -5 -8 -2 -40 -11 -70 -20 -30 -9 -62 -18 -70
-19 -8 -2 -22 -6 -30 -9 -8 -3 -40 -13 -70 -21 -30 -8 -113 -31 -185 -50 -71
-20 -141 -39 -155 -42 -13 -3 -30 -10 -37 -16 -7 -6 -13 -8 -13 -5 0 3 -30 -3
-67 -14 -38 -11 -77 -21 -88 -24 -11 -2 -67 -18 -125 -35 -58 -17 -112 -33
-120 -35 -8 -2 -42 -11 -75 -20 -33 -9 -68 -18 -78 -20 -9 -2 -21 -6 -25 -9
-5 -3 -19 -7 -33 -10 -13 -3 -73 -19 -134 -36 -60 -17 -117 -33 -125 -35 -8
-2 -105 -30 -215 -61 -110 -31 -207 -57 -215 -59 -8 -2 -15 -4 -15 -5 0 -1 -7
-3 -15 -5 -8 -1 -35 -8 -60 -15 -25 -7 -45 -11 -45 -10 0 1 -17 -5 -38 -14
-21 -8 -41 -15 -45 -14 -4 1 -14 -1 -22 -5 -16 -6 -198 -56 -221 -61 -8 -2
-21 -5 -29 -9 -16 -5 -194 -56 -220 -62 -8 -2 -73 -20 -145 -40 -71 -20 -137
-39 -145 -40 -17 -4 -175 -49 -230 -66 -19 -6 -42 -11 -50 -12 -8 -2 -21 -6
-27 -10 -7 -5 -13 -5 -13 -1 0 3 -7 1 -15 -6 -9 -7 -18 -10 -22 -7 -3 4 -15 1
-26 -4 -11 -6 -29 -13 -41 -15 -11 -2 -61 -15 -111 -29 -49 -15 -94 -27 -100
-29 -5 -1 -29 -8 -53 -15 l-42 -12 2 -2877 c3 -2337 5 -2877 16 -2874 7 1 23
4 35 6 12 3 22 5 22 6 0 1 7 3 15 5 8 2 49 13 90 25 94 26 86 24 140 40 25 7
49 13 54 11 4 -1 14 4 22 10 8 6 14 8 14 4 0 -4 7 -2 15 5 8 7 15 10 15 7 0
-3 15 -1 33 4 50 15 182 53 225 63 9 3 98 28 197 56 99 28 205 58 235 66 30 8
80 22 110 31 30 9 60 18 65 19 6 1 30 7 55 15 25 7 50 13 57 15 7 1 45 12 85
25 40 13 76 22 80 21 3 -1 33 8 65 19 32 11 58 19 58 18 0 0 20 4 45 11 25 7
50 13 55 15 6 1 17 5 25 8 13 5 150 42 190 52 8 2 15 4 15 5 0 1 9 3 19 5 10
2 40 10 65 19 25 8 57 17 71 20 14 3 75 19 135 36 61 17 117 33 125 35 8 2 16
4 18 5 2 3 5 4 47 14 17 5 61 17 99 28 38 11 76 21 85 23 9 2 48 13 86 25 87
27 83 25 128 35 20 4 40 11 43 16 3 5 9 7 14 4 5 -3 21 0 37 6 26 10 36 13 81
23 9 2 44 12 77 22 33 10 65 19 70 20 6 1 37 10 70 19 33 10 60 17 60 16 0 -1
14 3 30 9 17 7 44 14 60 17 17 3 54 13 83 23 28 10 52 17 52 15 0 -2 19 3 43
11 23 9 56 18 72 21 17 3 38 9 48 14 9 5 17 7 17 3 0 -3 7 -1 15 6 8 7 15 10
15 6 0 -3 12 -1 28 5 15 6 37 13 50 15 12 2 77 20 145 40 67 19 129 37 137 39
8 2 40 11 70 20 30 9 62 17 70 19 16 3 30 101 19 134 -3 9 -3 29 2 44 4 15 4
30 -1 33 -5 3 -6 15 0 29 4 12 4 29 0 36 -5 8 -5 21 0 30 5 9 5 22 1 28 -4 7
-5 35 -1 62 4 27 3 55 -1 62 -4 6 -4 19 1 28 5 9 5 22 0 30 -4 7 -4 24 0 36 6
14 5 26 0 29 -5 3 -5 18 0 34 4 16 6 32 4 34 -7 7 -9 106 -2 112 3 4 2 19 -2
35 -4 16 -6 31 -3 34 4 3 5 29 4 58 -1 29 -1 60 -1 68 0 8 0 24 0 35 -1 11 -1
27 0 35 0 8 0 24 0 35 -1 11 -1 26 -1 33 2 25 2 38 0 90 -1 29 0 55 4 58 3 3
1 18 -3 34 -4 16 -6 31 -3 34 8 7 7 105 -1 113 -3 3 -1 13 4 22 5 10 5 23 0
31 -4 7 -4 24 0 36 6 14 5 26 0 29 -5 3 -5 18 -1 33 4 15 5 38 1 52 -3 14 -3
39 0 55 4 17 4 39 0 50 -4 11 -4 32 1 47 4 15 4 30 -1 33 -5 3 -6 15 0 29 4
12 4 29 0 36 -5 8 -5 21 0 30 5 9 5 22 1 28 -4 7 -5 32 -1 56 3 24 3 51 0 62
-4 10 -4 31 1 46 4 15 4 30 -1 33 -5 3 -6 15 0 29 4 12 4 29 0 36 -5 8 -5 21
0 30 5 9 5 22 1 28 -4 7 -5 35 -1 62 4 27 3 55 -1 62 -4 6 -4 19 1 28 5 9 5
22 0 30 -4 7 -4 24 0 36 6 14 5 26 0 29 -5 3 -5 18 0 34 4 16 6 32 4 34 -7 7
-9 106 -2 112 3 4 2 19 -2 35 -4 16 -6 31 -2 34 3 4 5 25 3 49 -1 23 -2 51 -2
63 0 21 -16 24 -49 11 -8 -4 -40 -13 -70 -21 -30 -8 -111 -31 -180 -50 -211
-60 -274 -78 -290 -81 -25 -6 -314 -87 -330 -93 -16 -5 -33 -10 -52 -15 -92
-21 -112 -33 -170 -102 -62 -73 -119 -139 -178 -209 -19 -22 -48 -55 -65 -74
-48 -53 -64 -89 -55 -122 4 -16 6 -31 2 -34 -3 -3 -4 -34 -3 -69 1 -34 2 -68
2 -74 -2 -32 -2 -47 -1 -99 1 -31 -1 -58 -3 -60 -3 -3 -1 -19 3 -37 4 -18 4
-41 0 -52 -4 -11 -4 -34 1 -52 4 -18 3 -35 -1 -38 -5 -3 -5 -18 -1 -33 5 -15
5 -36 1 -47 -4 -11 -4 -34 1 -52 4 -18 4 -33 0 -33 -3 0 -5 -21 -4 -47 3 -69
3 -96 3 -113 -1 -20 -1 -116 0 -159 0 -19 3 -37 5 -41 12 -19 -13 -29 -225
-85 -8 -2 -49 -13 -90 -25 -41 -12 -97 -28 -125 -35 -27 -7 -97 -27 -155 -44
-92 -26 -179 -50 -202 -57 -5 -1 -9 -3 -10 -4 -2 -1 -12 -4 -23 -6 -11 -1 -76
-19 -145 -39 -69 -20 -132 -38 -140 -40 -8 -1 -42 -11 -75 -21 -33 -10 -63
-18 -68 -18 -7 -1 -19 -14 -89 -97 -20 -24 -46 -55 -58 -69 -12 -14 -42 -50
-68 -79 -26 -30 -55 -63 -64 -73 -10 -10 -18 -21 -18 -24 0 -4 -8 -14 -17 -23
-10 -9 -42 -45 -72 -81 -30 -36 -64 -75 -75 -87 -12 -12 -35 -39 -53 -60 -41
-50 -155 -185 -168 -199 -20 -22 -200 -233 -241 -282 -10 -13 -28 -34 -39 -46
-27 -30 -113 -130 -165 -191 -23 -27 -58 -69 -78 -92 -59 -69 -74 -87 -122
-143 -25 -29 -55 -64 -67 -77 -11 -13 -35 -41 -53 -63 -17 -21 -41 -49 -52
-62 -11 -13 -31 -36 -44 -52 -37 -43 -38 -26 -1 21 18 23 72 99 122 169 49 70
96 133 103 141 7 7 11 13 7 13 -3 0 20 35 52 78 32 42 72 97 88 122 17 25 32
47 35 50 3 3 66 91 140 195 74 105 138 192 142 193 5 2 8 8 8 14 0 5 6 16 13
23 6 8 22 29 35 47 59 84 144 205 158 223 8 11 109 153 224 315 115 162 214
301 220 307 9 11 11 45 9 153 -1 43 0 189 1 215 1 17 2 35 2 40 -1 11 -2 173
-2 315 -1 52 -1 100 -1 105 -1 6 0 33 0 60 2 57 2 190 1 320 -1 50 -1 97 -1
105 0 16 0 107 0 125 0 6 0 57 0 115 0 58 0 114 0 125 0 11 0 85 0 165 0 80 0
152 0 160 0 8 0 80 0 160 0 80 0 152 0 160 0 8 0 80 0 160 -1 191 1 224 9 231
6 5 85 29 115 35 4 1 8 2 10 4 1 1 9 3 17 5 8 2 49 13 90 25 41 12 82 23 90
25 8 2 15 4 15 5 0 1 11 3 25 6 14 2 36 8 50 14 14 5 45 15 70 21 25 7 47 13
50 14 24 7 59 17 71 19 8 2 21 5 29 8 24 9 203 59 230 64 14 3 50 12 80 22 30
9 58 17 63 18 4 1 8 2 10 4 1 1 9 3 17 5 20 6 93 25 105 29 52 15 103 29 115
31 8 2 15 4 15 5 0 1 8 3 18 5 10 2 41 11 70 20 28 9 61 18 72 20 11 2 20 4
20 5 0 1 8 4 50 14 11 2 32 9 48 15 15 6 27 8 27 5 0 -4 7 0 17 7 9 8 17 12
17 10 2 -7 110 21 124 32 6 5 12 7 12 4 0 -3 27 3 60 13 33 10 60 16 60 13 0
-3 6 -1 13 5 7 6 24 13 37 15 33 6 111 27 130 34 8 3 22 7 30 8 8 2 84 23 168
47 l153 44 30 44 c17 25 34 47 40 48 5 2 9 10 9 18 0 8 5 14 10 14 6 0 10 4
10 10 0 5 27 47 61 92 70 97 93 128 159 223 27 39 56 79 64 90 64 81 95 136
98 168 1 20 -1 39 -5 43 -3 4 -2 14 3 23 5 10 5 23 0 31 -4 7 -4 24 0 36 6 14
5 26 0 29 -5 3 -5 18 0 34 4 16 6 32 4 34 -7 7 -9 106 -2 112 3 4 2 19 -2 35
-4 16 -6 31 -3 34 4 3 5 29 4 58 -1 29 -1 60 -1 68 0 8 0 24 0 35 -1 11 -1 27
0 35 0 8 0 24 0 35 -1 11 -1 26 -1 33 1 15 1 33 0 94 0 26 2 52 6 58 3 5 2 10
-4 10 -6 0 -8 7 -5 15 6 14 2 20 -7 11z m-4259 -6910 c0 -2 -8 -10 -17 -17
-16 -13 -17 -12 -4 4 13 16 21 21 21 13z"/>
<path d="M8928 6820 c-32 -10 -58 -16 -58 -14 0 3 -8 0 -18 -5 -10 -5 -63 -21
-117 -36 -55 -15 -131 -36 -170 -47 -38 -11 -92 -26 -120 -33 -47 -13 -121
-34 -245 -70 -30 -9 -62 -18 -70 -20 -41 -10 -130 -35 -173 -48 -26 -9 -51
-16 -55 -16 -4 -1 -25 -6 -47 -12 -22 -6 -47 -13 -55 -14 -8 -2 -15 -4 -15 -5
0 -1 -7 -3 -15 -5 -8 -2 -40 -11 -70 -19 -30 -9 -66 -19 -79 -22 -13 -3 -32
-9 -43 -15 -10 -5 -18 -8 -18 -5 0 3 -15 0 -32 -6 -18 -5 -64 -19 -103 -29
-38 -11 -74 -21 -80 -23 -5 -2 -23 -6 -40 -10 -16 -4 -37 -10 -45 -13 -8 -3
-24 -8 -35 -10 -11 -2 -41 -10 -66 -18 -25 -9 -54 -17 -65 -20 -10 -2 -53 -14
-94 -25 -41 -12 -82 -23 -90 -25 -8 -2 -55 -16 -105 -30 -49 -14 -98 -28 -107
-30 -36 -10 -56 -15 -163 -45 -60 -17 -117 -33 -125 -35 -56 -14 -248 -68
-260 -72 -8 -4 -19 -7 -25 -8 -5 -2 -30 -8 -55 -16 -52 -14 -41 -11 -145 -39
-44 -12 -87 -24 -95 -27 -8 -3 -22 -7 -30 -9 -8 -1 -31 -7 -50 -13 -19 -6 -66
-20 -105 -32 -38 -11 -78 -22 -87 -24 -9 -2 -65 -18 -125 -35 -59 -17 -121
-33 -137 -36 -16 -3 -34 -8 -40 -12 -6 -3 -15 -7 -21 -8 -5 -1 -62 -16 -125
-34 -63 -17 -137 -38 -165 -45 -27 -8 -90 -26 -140 -40 -49 -14 -97 -28 -105
-29 -10 -2 -15 -15 -15 -40 0 -20 -4 -36 -8 -36 -4 0 -3 -9 3 -20 7 -13 7 -20
1 -20 -7 0 -7 -9 -3 -22 5 -13 6 -36 2 -53 -3 -16 -3 -51 0 -77 4 -27 3 -48
-2 -48 -4 0 -3 -11 2 -24 5 -13 6 -26 3 -29 -3 -3 -5 -35 -4 -71 1 -37 2 -74
1 -83 0 -118 2 -178 6 -185 3 -4 0 -8 -6 -8 -8 0 -8 -6 0 -20 7 -13 7 -20 1
-20 -7 0 -7 -9 -3 -22 5 -13 6 -36 2 -53 -3 -16 -3 -51 0 -77 4 -27 3 -48 -2
-48 -4 0 -3 -11 2 -24 5 -13 6 -26 3 -29 -3 -3 -5 -35 -4 -71 1 -37 2 -74 1
-83 0 -118 2 -178 6 -185 3 -4 0 -8 -6 -8 -8 0 -8 -6 0 -20 7 -13 7 -20 1 -20
-7 0 -7 -9 -3 -22 5 -13 6 -36 2 -53 -3 -16 -3 -51 0 -77 4 -27 3 -48 -2 -48
-4 0 -4 -10 1 -22 5 -13 6 -31 2 -40 -3 -9 -4 -53 -1 -98 3 -44 1 -80 -3 -80
-4 0 -3 -9 3 -20 6 -12 7 -20 1 -20 -6 0 -6 -11 -1 -29 4 -16 6 -32 3 -35 -5
-5 -5 -45 0 -169 1 -26 -2 -47 -6 -47 -4 0 -3 -9 3 -20 6 -12 7 -20 1 -20 -6
0 -6 -11 -1 -29 4 -16 6 -32 3 -35 -3 -3 -5 -31 -3 -63 2 -66 3 -86 1 -125 -2
-63 -3 -84 -2 -98 1 -8 1 -26 0 -40 0 -14 0 -32 0 -40 1 -8 1 -26 0 -40 -1
-28 -1 -55 0 -85 1 -11 2 -58 2 -104 0 -46 2 -88 5 -93 3 -4 0 -8 -6 -8 -8 0
-8 -6 0 -20 7 -13 7 -20 1 -20 -7 0 -7 -9 -3 -22 5 -13 6 -36 2 -53 -3 -16 -3
-51 0 -77 4 -27 3 -48 -2 -48 -4 0 -4 -10 1 -22 5 -13 5 -31 2 -42 -4 -10 -4
-40 -1 -66 3 -25 3 -58 -1 -72 -4 -14 -3 -31 1 -38 4 -7 4 -25 0 -40 -4 -15
-4 -33 0 -40 4 -7 4 -25 0 -40 -4 -15 -4 -33 0 -40 4 -7 5 -24 1 -38 -4 -14
-4 -47 -1 -74 4 -26 3 -48 -2 -48 -5 0 -4 -9 2 -20 6 -12 7 -20 1 -20 -6 0 -6
-11 -1 -29 4 -16 6 -32 3 -35 -7 -7 -6 -83 1 -95 3 -4 36 0 73 11 38 11 100
28 138 38 39 10 126 34 195 55 69 20 136 39 149 41 14 3 28 7 33 10 5 3 18 7
31 10 12 2 63 15 112 29 50 14 98 28 108 30 9 2 21 6 25 9 5 3 19 7 33 10 13
3 67 17 119 32 52 14 100 27 105 29 6 1 17 4 25 7 8 4 77 23 153 43 l138 38 0
126 c0 69 -1 135 -1 146 -1 11 -1 27 -1 35 0 8 0 42 0 75 0 33 0 65 0 70 0 32
1 73 1 90 -1 69 -1 142 -1 155 0 8 0 33 0 55 0 41 0 56 2 78 1 17 0 377 -1
402 -1 11 -1 27 -1 35 0 8 0 42 0 75 0 33 0 65 0 70 0 32 1 73 1 90 -1 69 -1
142 -1 155 0 8 0 33 0 55 0 41 0 56 2 78 1 17 0 377 -1 402 -1 11 -1 27 -1 35
0 8 0 42 0 75 0 33 0 65 0 70 0 32 1 73 1 90 -1 69 -1 142 -1 155 0 8 0 33 0
55 0 22 0 47 0 55 1 8 1 49 0 90 0 41 3 71 7 67 4 -4 12 -3 18 2 6 5 25 11 41
14 17 3 62 15 100 27 39 11 83 23 99 26 16 2 36 9 45 13 9 5 19 9 24 10 7 1
11 2 25 6 4 1 21 5 37 9 17 5 61 17 99 28 38 11 76 21 85 24 16 3 112 30 211
59 30 9 63 18 74 20 10 2 44 12 75 21 31 9 63 18 71 19 8 2 15 4 15 5 0 1 7 3
15 5 8 2 40 11 70 20 30 9 63 18 74 20 10 2 44 12 75 21 31 10 65 19 76 21 11
2 43 10 70 19 28 9 59 18 69 20 11 2 40 10 65 18 62 19 104 30 114 28 4 -1 7
3 7 9 0 5 3 8 8 5 4 -2 55 9 112 26 58 16 112 31 120 33 8 2 16 4 18 5 3 3 12
6 57 15 22 4 45 11 50 15 6 5 28 11 50 15 22 4 45 10 50 14 6 3 19 7 30 8 11
2 52 13 90 25 39 12 75 22 80 24 19 4 120 31 325 89 113 32 211 58 218 58 9 0
12 116 12 544 0 299 -3 543 -7 542 -5 0 -34 -9 -65 -19z"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 13 KiB

+19
View File
@@ -0,0 +1,19 @@
{
"name": "Gameyfin",
"short_name": "Gameyfin",
"icons": [
{
"src": "/android-chrome-192x192.png",
"sizes": "192x192",
"type": "image/png"
},
{
"src": "/android-chrome-512x512.png",
"sizes": "512x512",
"type": "image/png"
}
],
"theme_color": "#ffffff",
"background_color": "#ffffff",
"display": "standalone"
}
+11 -2
View File
@@ -2,13 +2,22 @@
<html lang="en"> <html lang="en">
<head> <head>
<meta charset="utf-8"> <meta charset="utf-8">
<title>Frontend</title> <title>Gameyfin</title>
<base href="/"> <base href="/">
<meta name="viewport" content="width=device-width, initial-scale=1"> <meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="icon" type="image/x-icon" href="favicon.ico">
<link rel="preconnect" href="https://fonts.gstatic.com"> <link rel="preconnect" href="https://fonts.gstatic.com">
<link href="https://fonts.googleapis.com/css2?family=Roboto:wght@300;400;500&display=swap" rel="stylesheet"> <link href="https://fonts.googleapis.com/css2?family=Roboto:wght@300;400;500&display=swap" rel="stylesheet">
<link href="https://fonts.googleapis.com/icon?family=Material+Icons|Material+Icons+Outlined" rel="stylesheet"/> <link href="https://fonts.googleapis.com/icon?family=Material+Icons|Material+Icons+Outlined" rel="stylesheet"/>
<link rel="apple-touch-icon" sizes="180x180" href="assets/apple-touch-icon.png">
<link rel="icon" type="image/png" sizes="32x32" href="assets/favicon-32x32.png">
<link rel="icon" type="image/png" sizes="16x16" href="assets/favicon-16x16.png">
<link rel="manifest" href="assets/site.webmanifest">
<link rel="mask-icon" href="assets/safari-pinned-tab.svg" color="#ffffff">
<meta name="apple-mobile-web-app-title" content="Gameyfin">
<meta name="application-name" content="Gameyfin">
<meta name="msapplication-TileColor" content="#ffffff">
<meta name="theme-color" content="#ffffff">
</head> </head>
<body class="mat-typography"> <body class="mat-typography">
<app-root></app-root> <app-root></app-root>
+1 -1
View File
@@ -5,7 +5,7 @@
<groupId>de.grimsi</groupId> <groupId>de.grimsi</groupId>
<artifactId>gameyfin</artifactId> <artifactId>gameyfin</artifactId>
<version>1.0.0</version> <version>1.0.1</version>
<name>gameyfin</name> <name>gameyfin</name>
<description>gameyfin</description> <description>gameyfin</description>