diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml new file mode 100644 index 0000000..fd19a95 --- /dev/null +++ b/.github/workflows/build.yml @@ -0,0 +1,48 @@ +name: Gameyfin CI Pipeline + +on: + push: + branches: + - master + pull_request: + types: [opened, synchronize, reopened] + workflow_dispatch: + +jobs: + build: + name: Build, Test & Scan + runs-on: ubuntu-latest + if: "!contains(github.event.head_commit.message, '[ci skip]')" + steps: + - name: Git checkout + uses: actions/checkout@v3 + + - name: Set up JDK + uses: actions/setup-java@v3 + with: + java-version: '18' + distribution: 'temurin' + cache: 'maven' + + - name: Extract Maven project version + id: project + run: echo "GAMEYFIN_VERSION=$(mvn help:evaluate -Dexpression=project.version -q -DforceStdout)" >> $GITHUB_OUTPUT + + - name: Show extracted Maven project version + run: echo "${{ steps.project.outputs.GAMEYFIN_VERSION }}" + + - name: Maven build + run: mvn --batch-mode --update-snapshots package + + - name: SonarQube scan + uses: kitabisa/sonarqube-action@v1.2.0 + with: + host: https://sonarqube.grimsi.de + login: ${{ secrets.SONARQUBE_TOKEN }} + projectKey: grimsi_gameyfin_AYPM67pzsxiaNzCh9BZd + + - name: Upload build artifact + uses: actions/upload-artifact@v3 + with: + name: gameyfin-${{ steps.project.outputs.GAMEYFIN_VERSION }}.jar + path: backend/target/gameyfin-*.jar diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml new file mode 100644 index 0000000..11002f4 --- /dev/null +++ b/.github/workflows/release.yml @@ -0,0 +1,65 @@ +name: Gameyfin Release + +on: + workflow_dispatch: + inputs: + branch: + description: "The branch to checkout when cutting the release." + required: true + default: "main" + releaseVersion: + description: "Default version to use when preparing a release." + required: true + default: "X.Y.Z" + developmentVersion: + description: "Default version to use for new local working copy." + required: true + default: "X.Y.Z-SNAPSHOT" + +jobs: + release: + runs-on: ubuntu-latest + name: Release + steps: + - name: Git checkout + uses: actions/checkout@v3 + with: + ref: ${{ github.event.inputs.branch }} + + - name: Set up JDK + uses: actions/setup-java@v3 + with: + java-version: '18' + distribution: 'temurin' + cache: 'maven' + + - name: Maven build + run: mvn --batch-mode --update-snapshots package -Dmaven.test.skip=true + + - name: Configure Git User + run: | + git config user.email "actions@github.com" + git config user.name "GitHub Actions" + + - name: Maven Release + run: mvn release:prepare release:perform -B -s .maven_settings.xml -DreleaseVersion=${{ github.event.inputs.releaseVersion }} -DdevelopmentVersion=${{ github.event.inputs.developmentVersion }} + env: + GITHUB_ACTOR: ${{ github.actor }} + GITHUB_TOKEN: ${{ github.token }} + + - name: Git tag + uses: mathieudutour/github-tag-action@v6.0 + with: + github_token: ${{ github.token }} + default_bump: false + custom_tag: ${{ github.event.inputs.releaseVersion }} + + - name: Github Release + uses: "marvinpinto/action-automatic-releases@v1.2.1" + with: + repo_token: ${{ github.token }} + prerelease: false + files: | + LICENSE.md + backend/target/gameyfin-*.jar + config/gameyfin.properties diff --git a/.maven_settings.xml b/.maven_settings.xml new file mode 100644 index 0000000..2d0b3b8 --- /dev/null +++ b/.maven_settings.xml @@ -0,0 +1,10 @@ + + + + + github + ${env.GITHUB_ACTOR} + ${env.GITHUB_TOKEN} + + + diff --git a/backend/pom.xml b/backend/pom.xml index ad668cb..3aa49e7 100644 --- a/backend/pom.xml +++ b/backend/pom.xml @@ -7,7 +7,7 @@ gameyfin de.grimsi - 1.2.1 + 1.2.2-SNAPSHOT gameyfin-backend @@ -17,7 +17,7 @@ 18 - 1.6.9 + 1.6.11 1.7.1 2.11.0 1.21 diff --git a/backend/src/main/java/de/grimsi/gameyfin/config/FilesystemConfig.java b/backend/src/main/java/de/grimsi/gameyfin/config/FilesystemConfig.java index 10df1fc..54801b2 100644 --- a/backend/src/main/java/de/grimsi/gameyfin/config/FilesystemConfig.java +++ b/backend/src/main/java/de/grimsi/gameyfin/config/FilesystemConfig.java @@ -2,16 +2,25 @@ package de.grimsi.gameyfin.config; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Value; +import org.springframework.boot.context.event.ApplicationReadyEvent; 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.context.event.EventListener; import org.springframework.core.env.*; import org.springframework.util.PropertyPlaceholderHelper; import org.springframework.util.StringUtils; +import javax.annotation.PostConstruct; import javax.sql.DataSource; +import java.io.File; +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.LinkOption; +import java.nio.file.Path; +import java.nio.file.Paths; import java.util.Arrays; import java.util.Properties; import java.util.stream.StreamSupport; @@ -19,6 +28,8 @@ import java.util.stream.StreamSupport; @Configuration public class FilesystemConfig { + private static final String INTERNAL_FOLDER_NAME = ".gameyfin"; + @Value("#{'${gameyfin.sources}'.split(',')[0]}") private String firstLibraryPath; @@ -31,16 +42,29 @@ public class FilesystemConfig { @Autowired Environment env; + /** + * This will make sure that the internal folder (".gameyfin") is marked as hidden on DOS/Windows-based systems. + * On UNIX-based systems files and folders starting with a dot are hidden + */ + @EventListener(ApplicationReadyEvent.class) + public void hideInternalFolderOnDOS() throws IOException { + Path internalFolder = Paths.get("%s/%s".formatted(firstLibraryPath, INTERNAL_FOLDER_NAME)); + + if(!Files.exists(internalFolder) || !Files.isDirectory(internalFolder)) return; + + Files.setAttribute(internalFolder, "dos:hidden", Boolean.TRUE, LinkOption.NOFOLLOW_LINKS); + } + @Autowired public void setConfigurableEnvironment(ConfigurableEnvironment env) { Properties props = new Properties(); if(!StringUtils.hasText(dbPath)) { - props.setProperty("gameyfin.db", "%s/.gameyfin/db".formatted(firstLibraryPath)); + props.setProperty("gameyfin.db", "%s/%s/db".formatted(firstLibraryPath, INTERNAL_FOLDER_NAME)); } if(!StringUtils.hasText(cachePath)) { - props.setProperty("gameyfin.cache", "%s/.gameyfin/cache".formatted(firstLibraryPath)); + props.setProperty("gameyfin.cache", "%s/%s/cache".formatted(firstLibraryPath, INTERNAL_FOLDER_NAME)); } env.getPropertySources().addFirst(new PropertiesPropertySource("gameyfinFilesystemProperties", props)); 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 a171e0e..9ecf99d 100644 --- a/backend/src/main/java/de/grimsi/gameyfin/service/LibraryService.java +++ b/backend/src/main/java/de/grimsi/gameyfin/service/LibraryService.java @@ -16,8 +16,10 @@ import org.springframework.stereotype.Service; import org.springframework.util.StopWatch; import java.io.IOException; +import java.nio.file.DirectoryStream; import java.nio.file.Files; import java.nio.file.Path; +import java.nio.file.Paths; import java.util.*; import java.util.concurrent.atomic.AtomicInteger; import java.util.stream.Collectors; @@ -46,7 +48,28 @@ public class LibraryService { 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(); + List gameFilesFromThisFolder = stream + .filter(p -> Files.isDirectory(p) || hasGameArchiveExtension(p)) + // filter out all hidden files and folders + .filter(p -> { + try { + return !(Files.isHidden(p)); + } catch (IOException e) { + throw new RuntimeException("Error while checking if '%s' is hidden.".formatted(p), e); + } + }) + // filter out all empty directories + .filter(p -> { + if(!Files.isDirectory(p)) return true; + + try(DirectoryStream s = Files.newDirectoryStream(p)) { + return s.iterator().hasNext(); + } catch(IOException e) { + throw new RuntimeException("Error while checking if folder '%s' is empty.".formatted(p), e); + } + }) + .toList(); + gamefiles.addAll(gameFilesFromThisFolder); } catch (IOException e) { diff --git a/config/gameyfin.properties b/config/gameyfin.properties new file mode 100644 index 0000000..eeeae45 --- /dev/null +++ b/config/gameyfin.properties @@ -0,0 +1,19 @@ +# Uncomment if you want to change the port from 8080 +# server.port=8081 + +# Gameyfin admin interface username and password +gameyfin.user= +gameyfin.password= + +# Gameyfin source folders +gameyfin.sources= + +# Uncomment if you want to specify the Gameyfin database path (default is /.gameyfin/db) +# gameyfin.db= + +# Uncomment if you want to specify the Gameyfin cache path (default is /.gameyfin/cache) +# gameyfin.cache= + +# Your twitch client-id and client-secret +gameyfin.igdb.api.client-id= +gameyfin.igdb.api.client-secret= \ No newline at end of file diff --git a/frontend/package-lock.json b/frontend/package-lock.json index 5d66472..7f2a857 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -1,12 +1,12 @@ { "name": "frontend", - "version": "1.2.1", + "version": "1.2.2-SNAPSHOT", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "frontend", - "version": "1.2.1", + "version": "1.2.2-SNAPSHOT", "dependencies": { "@angular/animations": "^14.0.0", "@angular/cdk": "^14.1.0", diff --git a/frontend/package.json b/frontend/package.json index e6137b3..bfe88ad 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -1,6 +1,6 @@ { "name": "frontend", - "version": "1.2.1", + "version": "1.2.2-SNAPSHOT", "scripts": { "ng": "ng", "start": "ng serve", diff --git a/frontend/pom.xml b/frontend/pom.xml index d3e8103..a187801 100644 --- a/frontend/pom.xml +++ b/frontend/pom.xml @@ -5,7 +5,7 @@ gameyfin de.grimsi - 1.2.1 + 1.2.2-SNAPSHOT 4.0.0 diff --git a/pom.xml b/pom.xml index b100e7d..6a952d1 100644 --- a/pom.xml +++ b/pom.xml @@ -1,25 +1,45 @@ - 4.0.0 + xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd"> + 4.0.0 - de.grimsi - gameyfin - 1.2.1 - gameyfin - gameyfin + de.grimsi + gameyfin + 1.2.2-SNAPSHOT + gameyfin + gameyfin - pom + pom - frontend + frontend backend - + - org.springframework.boot - spring-boot-starter-parent - 2.7.1 - - + org.springframework.boot + spring-boot-starter-parent + 2.7.1 + + + + + scm:git:https://github.com/grimsi/gameyfin.git + scm:git:https://github.com/grimsi/gameyfin.git + scm:git:https://github.com/grimsi/gameyfin.git + HEAD + + + + + + org.apache.maven.plugins + maven-release-plugin + 2.5.3 + + [ci skip] + + + + diff --git a/sonar-project.properties b/sonar-project.properties new file mode 100644 index 0000000..cb0b4b5 --- /dev/null +++ b/sonar-project.properties @@ -0,0 +1,4 @@ +sonar.projectKey=grimsi_gameyfin_AYPM67pzsxiaNzCh9BZd + +# Point SONAR to the compiled Java classes +sonar.java.binaries=./backend/target