diff --git a/.gitignore b/.gitignore
index 5d839f3..bbaa093 100644
--- a/.gitignore
+++ b/.gitignore
@@ -36,4 +36,5 @@ build/
### Custom ###
/data/
/backend/src/main/resources/static/
-/docker/docker-compose.yml
\ No newline at end of file
+/docker/docker-compose.yml
+/.gameyfin/
diff --git a/backend/pom.xml b/backend/pom.xml
index 9734656..6906b6d 100644
--- a/backend/pom.xml
+++ b/backend/pom.xml
@@ -16,12 +16,16 @@
18
+
1.6.9
1.7.1
2.11.0
1.21
3.11.4
3.21.3
+
+
+ 3.1.0
@@ -140,6 +144,7 @@
**/*.properties
**/*.yml
**/*.yaml
+ **/*.json
**/*.txt
**/*.js
**/*.css
@@ -166,6 +171,7 @@
maven-resources-plugin
+ ${maven-resources-plugin.version}
copy-resources
diff --git a/backend/src/main/java/de/grimsi/gameyfin/GameyfinApplication.java b/backend/src/main/java/de/grimsi/gameyfin/GameyfinApplication.java
index 4c79e1b..f17d349 100644
--- a/backend/src/main/java/de/grimsi/gameyfin/GameyfinApplication.java
+++ b/backend/src/main/java/de/grimsi/gameyfin/GameyfinApplication.java
@@ -8,7 +8,7 @@ public class GameyfinApplication {
public static void main(String[] args) {
new SpringApplicationBuilder(GameyfinApplication.class)
- .properties("spring.config.name=application,gameyfin,database,secure")
+ .properties( "file.encoding=UTF-8", "spring.config.name=application,gameyfin,database,secure")
.build()
.run(args);
}
diff --git a/backend/src/main/java/de/grimsi/gameyfin/config/FilesystemConfig.java b/backend/src/main/java/de/grimsi/gameyfin/config/FilesystemConfig.java
new file mode 100644
index 0000000..5b9cf98
--- /dev/null
+++ b/backend/src/main/java/de/grimsi/gameyfin/config/FilesystemConfig.java
@@ -0,0 +1,66 @@
+package de.grimsi.gameyfin.config;
+
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.boot.context.properties.ConfigurationProperties;
+import org.springframework.boot.jdbc.DataSourceBuilder;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.context.annotation.Primary;
+import org.springframework.core.env.*;
+import org.springframework.util.PropertyPlaceholderHelper;
+
+import javax.sql.DataSource;
+import java.util.Arrays;
+import java.util.Properties;
+import java.util.stream.StreamSupport;
+
+@Configuration
+public class FilesystemConfig {
+
+ @Value("#{'${gameyfin.sources}'.split(',')[0]}")
+ private String firstLibraryPath;
+
+ @Autowired
+ Environment env;
+
+ @Autowired
+ public void setConfigurableEnvironment(ConfigurableEnvironment env) {
+ Properties props = new Properties();
+ props.setProperty("gameyfin.db", "%s/.gameyfin/db".formatted(firstLibraryPath));
+ props.setProperty("gameyfin.cache", "%s/.gameyfin/cache".formatted(firstLibraryPath));
+ env.getPropertySources().addFirst(new PropertiesPropertySource("gameyfinFilesystemProperties", props));
+ }
+
+ /**
+ * This bean is needed so Spring initializes the data source after we are done messing with the configuration environment
+ * @return DataSource
+ */
+ @ConfigurationProperties(prefix = "spring.datasource")
+ @Bean
+ @Primary
+ public DataSource getDataSource() {
+
+ Properties properties = loadAllProperties();
+
+ return DataSourceBuilder
+ .create()
+ .url(properties.getProperty("spring.datasource.url"))
+ .build();
+ }
+
+ private Properties loadAllProperties() {
+ Properties props = new Properties();
+
+ MutablePropertySources propSrcs = ((AbstractEnvironment) env).getPropertySources();
+
+ StreamSupport.stream(propSrcs.spliterator(), false)
+ .filter(ps -> ps instanceof EnumerablePropertySource)
+ .map(ps -> ((EnumerablePropertySource>) ps).getPropertyNames())
+ .flatMap(Arrays::stream)
+ .forEach(propName -> props.setProperty(propName, env.getProperty(propName)));
+
+ return props;
+ }
+
+}
diff --git a/backend/src/main/java/de/grimsi/gameyfin/config/SecureProperties.java b/backend/src/main/java/de/grimsi/gameyfin/config/SecureProperties.java
index 6d66527..3140b37 100644
--- a/backend/src/main/java/de/grimsi/gameyfin/config/SecureProperties.java
+++ b/backend/src/main/java/de/grimsi/gameyfin/config/SecureProperties.java
@@ -16,7 +16,7 @@ public class SecureProperties {
@Autowired
public void setConfigurableEnvironment(ConfigurableEnvironment env) {
try {
- Resource resource = new ClassPathResource("/config/secure.properties");
+ Resource resource = new ClassPathResource("/config/secure.yml");
env.getPropertySources().addFirst(new PropertiesPropertySource(Objects.requireNonNull(resource.getFilename()), PropertiesLoaderUtils.loadProperties(resource)));
} catch (Exception ex) {
throw new RuntimeException(ex.getMessage(), ex);
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 3531176..f5c7308 100644
--- a/backend/src/main/java/de/grimsi/gameyfin/service/LibraryService.java
+++ b/backend/src/main/java/de/grimsi/gameyfin/service/LibraryService.java
@@ -10,13 +10,13 @@ import de.grimsi.gameyfin.repositories.DetectedGameRepository;
import de.grimsi.gameyfin.repositories.UnmappableFileRepository;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
-import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import org.springframework.util.StopWatch;
import java.io.IOException;
-import java.nio.file.*;
+import java.nio.file.Files;
+import java.nio.file.Path;
import java.util.*;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.stream.Stream;
@@ -29,25 +29,29 @@ import static de.grimsi.gameyfin.util.FilenameUtil.hasGameArchiveExtension;
@RequiredArgsConstructor
public class LibraryService {
- @Value("${gameyfin.root}")
- private String rootFolderPath;
-
+ @Value("${gameyfin.sources}")
+ private List libraryFolders;
private final IgdbWrapper igdbWrapper;
private final DetectedGameRepository detectedGameRepository;
private final UnmappableFileRepository unmappableFileRepository;
public List getGameFiles() {
+ List gamefiles = new ArrayList<>();
- Path rootFolder = Path.of(rootFolderPath);
+ libraryFolders.stream().map(Path::of).forEach(
+ folder -> {
+ try (Stream stream = Files.list(folder)) {
+ // return all sub-folders (non-recursive) and files that have an extension that indicates that they are a downloadable file
+ List gameFilesFromThisFolder = stream.filter(p -> Files.isDirectory(p) || hasGameArchiveExtension(p)).toList();
+ gamefiles.addAll(gameFilesFromThisFolder);
- try (Stream stream = Files.list(rootFolder)) {
- // return all sub-folders (non-recursive) and files that have an extension that indicates that they are a downloadable file
- return stream
- .filter(p -> Files.isDirectory(p) || hasGameArchiveExtension(p))
- .toList();
- } catch (IOException e) {
- throw new RuntimeException("Error while opening root folder", e);
- }
+ } catch (IOException e) {
+ throw new RuntimeException("Error while opening library folder '%s'".formatted(folder), e);
+ }
+ }
+ );
+
+ return gamefiles;
}
public void scanGameLibrary() {
diff --git a/backend/src/main/resources/META-INF/additional-spring-configuration-metadata.json b/backend/src/main/resources/META-INF/additional-spring-configuration-metadata.json
new file mode 100644
index 0000000..e346e41
--- /dev/null
+++ b/backend/src/main/resources/META-INF/additional-spring-configuration-metadata.json
@@ -0,0 +1,9 @@
+{
+ "properties": [
+ {
+ "name": "gameyfin.sources",
+ "type": "java.lang.String[]",
+ "description": "List of directories Gameyfin should scan for games."
+ }
+ ]
+}
\ No newline at end of file
diff --git a/backend/src/main/resources/application-dev.yml b/backend/src/main/resources/application-dev.yml
index 30efaba..37e7822 100644
--- a/backend/src/main/resources/application-dev.yml
+++ b/backend/src/main/resources/application-dev.yml
@@ -1,8 +1,6 @@
gameyfin:
user: admin
password: password
- cache: ${gameyfin.root}\.gameyfin\cache
- db: ${gameyfin.root}\.gameyfin\db
logging:
level:
diff --git a/backend/src/main/resources/config/database.properties b/backend/src/main/resources/config/database.properties
deleted file mode 100644
index 6123b15..0000000
--- a/backend/src/main/resources/config/database.properties
+++ /dev/null
@@ -1,13 +0,0 @@
-# This file contains properties related to the database configuration
-#
-#
-spring.jpa.properties.hibernate.enable_lazy_load_no_trans=true
-spring.datasource.db-name=gameyfin_db
-spring.datasource.url=jdbc:h2:file:${gameyfin.db}/${spring.datasource.db-name}
-spring.datasource.username=gfadmin
-spring.datasource.password=gameyfin
-spring.datasource.driverClassName=org.h2.Driver
-spring.jpa.database-platform=org.hibernate.dialect.H2Dialect
-spring.jpa.hibernate.ddl-auto=update
-spring.jpa.open-in-view=true
-spring.jpa.properties.hibernate.event.merge.entity_copy_observer=allow
\ No newline at end of file
diff --git a/backend/src/main/resources/config/database.yml b/backend/src/main/resources/config/database.yml
new file mode 100644
index 0000000..0e02ae7
--- /dev/null
+++ b/backend/src/main/resources/config/database.yml
@@ -0,0 +1,18 @@
+spring:
+ jpa:
+ open-in-view: true
+ properties:
+ hibernate:
+ enable_lazy_load_no_trans: true
+ event:
+ merge:
+ entity_copy_observer: allow
+ database-platform: org.hibernate.dialect.H2Dialect
+ hibernate:
+ ddl-auto: update
+ datasource:
+ username: gfadmin
+ password: gameyfin
+ db-name: gameyfin_db
+ url: jdbc:h2:file:${gameyfin.db}/${spring.datasource.db-name}
+ driverClassName: org.h2.Driver
\ No newline at end of file
diff --git a/backend/src/main/resources/config/gameyfin.properties b/backend/src/main/resources/config/gameyfin.properties
deleted file mode 100644
index 776836d..0000000
--- a/backend/src/main/resources/config/gameyfin.properties
+++ /dev/null
@@ -1,28 +0,0 @@
-# This file contains properties related to the configuration of Gameyfin
-#
-#
-# Username and password for the web interface
-gameyfin.user=
-gameyfin.password=
-
-# Root folder of your game library
-gameyfin.root=
-# Folders where gameyfin will store cached images and the database
-gameyfin.cache=${gameyfin.root}/.gameyfin/cache
-gameyfin.db=${gameyfin.root}/.gameyfin/db
-
-# File extensions which gameyfin will recognize as game files
-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
-gameyfin.igdb.config.preferred-platforms=6
-
-# Twitch Client ID and Client Secret
-gameyfin.igdb.api.client-id=
-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
\ No newline at end of file
diff --git a/backend/src/main/resources/config/gameyfin.yml b/backend/src/main/resources/config/gameyfin.yml
new file mode 100644
index 0000000..f70079e
--- /dev/null
+++ b/backend/src/main/resources/config/gameyfin.yml
@@ -0,0 +1,10 @@
+gameyfin:
+ file-extensions: iso, zip, rar, 7z, exe
+ igdb:
+ api:
+ client-id:
+ client-secret:
+ max-concurrent-requests: 2
+ max-requests-per-second: 4
+ config:
+ preferred-platforms: 6
\ No newline at end of file
diff --git a/backend/src/main/resources/config/secure.properties b/backend/src/main/resources/config/secure.properties
deleted file mode 100644
index cda7c4e..0000000
--- a/backend/src/main/resources/config/secure.properties
+++ /dev/null
@@ -1,19 +0,0 @@
-# This file contains properties that are *NOT* safe to override by the user
-# In theory a user should not be able to override them since they will be loaded from the classpath at launch, overriding existing user properties with the same key
-#
-#
-# System Info
-application.name=Gameyfin
-application.version=@project.version@
-# API
-server.servlet.context-path=/
-# Spring Actuator
-management.endpoints.enabled-by-default=false
-management.endpoint.health.enabled=true
-# Server
-server.error.include-stacktrace=never
-spring.mvc.async.request-timeout=-1
-# Jackson JSON Mapping
-spring.jackson.default-property-inclusion=non_null
-spring.jackson.mapper.accept-case-insensitive-enums=true
-spring.jackson.deserialization.fail-on-unknown-properties=false
diff --git a/backend/src/main/resources/config/secure.yml b/backend/src/main/resources/config/secure.yml
new file mode 100644
index 0000000..cf80710
--- /dev/null
+++ b/backend/src/main/resources/config/secure.yml
@@ -0,0 +1,27 @@
+application:
+ version: '@project.version@'
+ name: Gameyfin
+
+server:
+ servlet:
+ context-path: /
+ error:
+ include-stacktrace: never
+
+spring:
+ jackson:
+ deserialization:
+ fail-on-unknown-properties: false
+ default-property-inclusion: non_null
+ mapper:
+ accept-case-insensitive-enums: true
+ mvc:
+ async:
+ request-timeout: -1
+
+management:
+ endpoint:
+ health:
+ enabled: true
+ endpoints:
+ enabled-by-default: false
\ No newline at end of file
diff --git a/docker/Dockerfile b/docker/Dockerfile
index c6077b7..743516b 100644
--- a/docker/Dockerfile
+++ b/docker/Dockerfile
@@ -1,10 +1,10 @@
FROM openjdk:18
-ENV GAMEYFIN_ROOT=/opt/gameyfin-library
+ENV GAMEYFIN_SOURCES=/opt/gameyfin-library
RUN groupadd gameyfin && useradd gameyfin -g gameyfin && \
- mkdir -p /opt/gameyfin ${GAMEYFIN_ROOT} && \
- chown -R gameyfin:gameyfin /opt/gameyfin ${GAMEYFIN_ROOT}
+ mkdir -p /opt/gameyfin ${GAMEYFIN_SOURCES} && \
+ chown -R gameyfin:gameyfin /opt/gameyfin ${GAMEYFIN_SOURCES}
USER gameyfin:gameyfin