Compare commits

...

24 Commits

Author SHA1 Message Date
Michael Green
07c973cd75 Fixed captialisation issue in sql query
* Fixed captialisation issue in sql query
2024-02-08 14:43:20 +11:00
Michael Green
b94950fed0 Create an icon
* Create an icon Fixes #63

* Added favicons
2024-02-08 13:19:16 +11:00
Michael Green
b5511a3c51 Add ability to set games/roms as favouriteFixes #49
* Add ability to set games/roms as favourite
Fixes #49
2024-02-08 12:47:22 +11:00
Michael Green
73174a30f0 Integrated EJS 4.0.11
* Integrated EJS 4.0.11
2024-02-07 22:29:05 +11:00
Michael Green
a2d863dc7a Added game last played, and total play time stats
* Added game last played, and total play time stats
2024-02-07 16:19:30 +11:00
Michael Green
3c451f5558 Added support for custom user avatars
* Added support for custom user avatars
2024-02-06 19:43:15 +11:00
Michael Green
645327bdd1 Integrate EJS 4.0.10Fixes #284
* Integrate EJS 4.0.10
Fixes #284
2024-02-05 00:39:44 +11:00
Michael Green
7754fd5dda Minor fixes to complete the upgrade from 1.6.1 to 1.7.0
* Update README

* Perform upgrade testing from most recent full release version (1.6.1)
Fixes #278
2024-02-04 22:17:11 +11:00
Michael Green
5e45fc1aa9 Update documentation
* Update documentation
Fixes #279

* Merge remote-tracking branch 'origin/main' into michael-j-green/issue279
2024-02-02 23:19:39 +11:00
Michael Green
36938ed2f8 Define port 80 as the default port to listen on - overrides new net8.0 default of 8080
* Define port 80 as the default port to listen on - overrides new net8.0 default of 8080
2024-02-02 13:00:25 +11:00
Michael Green
344d37c96a Migrate existing collections to new user on first run
* Migrate existing collections to new user on first run
2024-02-02 12:03:02 +11:00
Michael Green
60da78fa7d .Net8.0 update, add ARM support, various bug fixes (#277)
* Update to .net8.0 LTS (closes #271)
* Add ARM docker container support (closes #245)
* Library updates (closes #260 and #261)
* Database updates to support changes in the latest IGDB client version
* Version number will no longer be displayed when built from source
2024-02-02 11:35:20 +11:00
dependabot[bot]
cb3c7fa901 chore(deps): bump Microsoft.VisualStudio.Web.CodeGeneration.Design (#183)
Bumps [Microsoft.VisualStudio.Web.CodeGeneration.Design](https://github.com/dotnet/Scaffolding) from 7.0.10 to 8.0.0.
- [Release notes](https://github.com/dotnet/Scaffolding/releases)
- [Commits](https://github.com/dotnet/Scaffolding/commits)

---
updated-dependencies:
- dependency-name: Microsoft.VisualStudio.Web.CodeGeneration.Design
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-01-31 14:13:27 +11:00
dependabot[bot]
b0c8075865 chore(deps): bump Microsoft.Extensions.DependencyInjection (#197)
Bumps [Microsoft.Extensions.DependencyInjection](https://github.com/dotnet/runtime) from 7.0.0 to 8.0.0.
- [Release notes](https://github.com/dotnet/runtime/releases)
- [Commits](https://github.com/dotnet/runtime/compare/v7.0.0...v8.0.0)

---
updated-dependencies:
- dependency-name: Microsoft.Extensions.DependencyInjection
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-01-31 14:04:43 +11:00
dependabot[bot]
0d8d2707cb chore(deps): bump Microsoft.AspNetCore.OpenApi from 7.0.12 to 7.0.15 (#257)
Bumps [Microsoft.AspNetCore.OpenApi](https://github.com/dotnet/aspnetcore) from 7.0.12 to 7.0.15.
- [Release notes](https://github.com/dotnet/aspnetcore/releases)
- [Changelog](https://github.com/dotnet/aspnetcore/blob/main/docs/ReleasePlanning.md)
- [Commits](https://github.com/dotnet/aspnetcore/compare/v7.0.12...v7.0.15)

---
updated-dependencies:
- dependency-name: Microsoft.AspNetCore.OpenApi
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-01-31 14:00:39 +11:00
dependabot[bot]
9fce071af6 chore(deps): bump Microsoft.AspNetCore.Identity.UI from 7.0.13 to 7.0.15 (#258)
Bumps [Microsoft.AspNetCore.Identity.UI](https://github.com/dotnet/aspnetcore) from 7.0.13 to 7.0.15.
- [Release notes](https://github.com/dotnet/aspnetcore/releases)
- [Changelog](https://github.com/dotnet/aspnetcore/blob/main/docs/ReleasePlanning.md)
- [Commits](https://github.com/dotnet/aspnetcore/compare/v7.0.13...v7.0.15)

---
updated-dependencies:
- dependency-name: Microsoft.AspNetCore.Identity.UI
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-01-31 13:52:59 +11:00
dependabot[bot]
da0813b76f chore(deps): bump MySqlConnector from 2.3.1 to 2.3.5 (#266)
Bumps [MySqlConnector](https://github.com/mysql-net/MySqlConnector) from 2.3.1 to 2.3.5.
- [Release notes](https://github.com/mysql-net/MySqlConnector/releases)
- [Changelog](https://github.com/mysql-net/MySqlConnector/blob/master/docs/VersionHistory.md)
- [Commits](https://github.com/mysql-net/MySqlConnector/compare/2.3.1...2.3.5)

---
updated-dependencies:
- dependency-name: MySqlConnector
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-01-31 13:48:25 +11:00
Michael Green
5f6a71e065 Collections needs to honour the logged in users age rating permissionFixes #273
Known issue: #275
2024-01-31 07:28:07 +11:00
Michael Green
f65408a64d Game library should return to the page it was on after a page navigation or refresh Fixes #253
* Game library should return to the page it was on after a page navigation or refresh
Fixes #253
2024-01-29 18:08:09 +11:00
Michael Green
3a9d3df013 Add filters for number of rating votes, and release year
Added support for filtering:
* number of rating votes(closes #144)
* release year (closes #146)
2024-01-29 16:36:32 +11:00
Michael Green
69863f8b61 Added a setting to toggle the emulator debug mode
* Added a setting to toggle the emulator debug mode
2024-01-28 21:46:47 +11:00
Michael Green
10be6c53ba Update background task time scheduling to use server local time. Ensure that the TZ variable is passed to the container with the location of the server. (#268)
* Remove LastRun values during upgrade

* Add timezone variable to docker-compose files

* Release note update to include PR's labeled "note"
2024-01-28 15:18:13 +11:00
Michael Green
163aa7a446 Add enhanced schedule management for background tasks (#267) 2024-01-27 23:30:35 +11:00
Michael Green
ec115b33de Add alpha pager, and move main pager to bottom of page (#265)
* Add alpha paging, move main pager to the bottom of the page
2024-01-22 01:17:54 +11:00
104 changed files with 3648 additions and 964 deletions

4
.devcontainer/.env Normal file
View File

@@ -0,0 +1,4 @@
DATABASE_HOST=mariadb
DATABASE_USER=root
DATABASE_PASSWORD=gaseous
DATABASE_DB=gaseous

6
.devcontainer/Dockerfile Normal file
View File

@@ -0,0 +1,6 @@
FROM mcr.microsoft.com/devcontainers/dotnet:1-8.0-bookworm
RUN apt-get update && apt-get install -y p7zip-full
RUN mkdir -p /workspace/gaseous-server/wwwroot/emulators/EmulatorJS
RUN wget https://cdn.emulatorjs.org/releases/4.0.11.7z
RUN 7z x -y -o/workspace/gaseous-server/wwwroot/emulators/EmulatorJS 4.0.11.7z

View File

@@ -0,0 +1,46 @@
// For format details, see https://aka.ms/devcontainer.json. For config options, see the
// README at: https://github.com/devcontainers/templates/tree/main/src/dotnet
{
"name": "C# (.NET)",
// Or use a Dockerfile or Docker Compose file. More info: https://containers.dev/guide/dockerfile
//"image": "mcr.microsoft.com/devcontainers/dotnet:1-8.0-bookworm",
"dockerComposeFile": "docker-compose.yml",
"service": "development",
"workspaceFolder": "/workspace",
// Features to add to the dev container. More info: https://containers.dev/features.
// "features": {},
// Use 'forwardPorts' to make a list of ports inside the container available locally.
"forwardPorts": [5198],
"portsAttributes": {
"5198": {
"protocol": "http"
}
},
// Use 'postCreateCommand' to run commands after the container is created.
"postCreateCommand": "dotnet restore",
// Configure tool-specific properties.
"customizations": {
"vscode": {
"extensions": [
"cweijan.vscode-mysql-client2",
"ms-dotnettools.csdevkit",
"ms-dotnettools.csharp",
"ms-dotnettools.vscode-dotnet-runtime",
"ecmel.vscode-html-css",
"github.vscode-github-actions",
"GitHub.vscode-pull-request-github",
"AndersEAndersen.html-class-suggestions",
"george-alisson.html-preview-vscode",
"ms-dotnettools.vscodeintellicode-csharp",
"Zignd.html-css-class-completion"
]
}
}
// Uncomment to connect as root instead. More info: https://aka.ms/dev-containers-non-root.
// "remoteUser": "root"
}

View File

@@ -0,0 +1,23 @@
services:
development:
build:
context: .
dockerfile: Dockerfile
volumes:
- ..:/workspace
stdin_open: true
environment:
- TZ=Australia/Sydney
- dbhost=${DATABASE_HOST}
- dbuser=${DATABASE_USER}
- dbpass=${DATABASE_PASSWORD}
- igdbclientid=<clientid>
- igdbclientsecret=<clientsecret>
mariadb:
hostname: mariadb
image: mariadb:latest
environment:
- MARIADB_ROOT_PASSWORD=${DATABASE_PASSWORD}
- MARIADB_DATABASE=${DATABASE_DB}
- MARIADB_USER=${DATABASE_USER}
- MARIADB_PASSWORD=${DATABASE_PASSWORD}

View File

@@ -9,3 +9,7 @@ updates:
directory: "/" # Location of package manifests directory: "/" # Location of package manifests
schedule: schedule:
interval: "weekly" interval: "weekly"
- package-ecosystem: "devcontainers"
directory: "/"
schedule:
interval: "weekly"

4
.github/release.yml vendored
View File

@@ -5,8 +5,12 @@ changelog:
- '*' - '*'
exclude: exclude:
labels: labels:
- note
- bug - bug
- dependencies - dependencies
- title: Notes
labels:
- note
- title: Bug Fixes - title: Bug Fixes
labels: labels:
- bug - bug

View File

@@ -33,6 +33,6 @@ jobs:
uses: docker/build-push-action@v4 uses: docker/build-push-action@v4
with: with:
context: . context: .
platforms: linux/amd64 #,linux/arm64 platforms: linux/amd64,linux/arm64
push: true push: true
tags: gaseousgames/gaseousserver:${{ github.ref_name}} tags: gaseousgames/gaseousserver:${{ github.ref_name}}

View File

@@ -32,6 +32,6 @@ jobs:
uses: docker/build-push-action@v4 uses: docker/build-push-action@v4
with: with:
context: . context: .
platforms: linux/amd64 #,linux/arm64 platforms: linux/amd64,linux/arm64
push: true push: true
tags: gaseousgames/gaseousserver:latest,gaseousgames/gaseousserver:${{ github.ref_name}} tags: gaseousgames/gaseousserver:latest,gaseousgames/gaseousserver:${{ github.ref_name}}

36
.github/workflows/BuildOnTestBranch.yml vendored Normal file
View File

@@ -0,0 +1,36 @@
name: Build test branch
on:
push:
branches: [test]
jobs:
docker:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v3
with:
submodules: 'true'
- name: Install dotnet tool
run: dotnet tool install -g dotnetCampus.TagToVersion
- name: Set tag to version
run: dotnet TagToVersion -t 0.0.1
- name: Sign in to Nuget
run: dotnet nuget add source --username michael-j-green --password ${{ secrets.NUGETKEY }} --store-password-in-clear-text --name github "https://nuget.pkg.github.com/gaseous-project/index.json"
- name: Set up QEMU
uses: docker/setup-qemu-action@v3
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
- name: Login to Docker Hub
uses: docker/login-action@v3
with:
username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_TOKEN }}
- name: Build and push
uses: docker/build-push-action@v5
with:
context: .
platforms: linux/amd64,linux/arm64
push: true
tags: gaseousgames/test:latest

View File

@@ -15,7 +15,7 @@ jobs:
- name: Setup .NET - name: Setup .NET
uses: actions/setup-dotnet@v1 uses: actions/setup-dotnet@v1
with: with:
dotnet-version: 7.0.x dotnet-version: 8.0.x
- name: Sign in to Nuget - name: Sign in to Nuget
run: dotnet nuget add source --username michael-j-green --password ${{ secrets.NUGETKEY }} --store-password-in-clear-text --name github "https://nuget.pkg.github.com/gaseous-project/index.json" run: dotnet nuget add source --username michael-j-green --password ${{ secrets.NUGETKEY }} --store-password-in-clear-text --name github "https://nuget.pkg.github.com/gaseous-project/index.json"
- name: Restore dependencies - name: Restore dependencies

4
.vscode/launch.json vendored
View File

@@ -10,14 +10,14 @@
"request": "launch", "request": "launch",
"preLaunchTask": "build", "preLaunchTask": "build",
// If you have changed target frameworks, make sure to update the program path. // If you have changed target frameworks, make sure to update the program path.
"program": "${workspaceFolder}/gaseous-server/bin/Debug/net7.0/gaseous-server.dll", "program": "${workspaceFolder}/gaseous-server/bin/Debug/net8.0/gaseous-server.dll",
"args": [], "args": [],
"cwd": "${workspaceFolder}/gaseous-server", "cwd": "${workspaceFolder}/gaseous-server",
"stopAtEntry": false, "stopAtEntry": false,
// Enable launching a web browser when ASP.NET Core starts. For more information: https://aka.ms/VSCode-CS-LaunchJson-WebBrowser // Enable launching a web browser when ASP.NET Core starts. For more information: https://aka.ms/VSCode-CS-LaunchJson-WebBrowser
"serverReadyAction": { "serverReadyAction": {
"action": "openExternally", "action": "openExternally",
"pattern": "\\bNow listening on:\\s+(https?://\\S+)" "pattern": "\\bNow listening on:\\s+(http?://\\S+)"
}, },
"env": { "env": {
"ASPNETCORE_ENVIRONMENT": "Development" "ASPNETCORE_ENVIRONMENT": "Development"

View File

@@ -1,22 +1,28 @@
FROM mcr.microsoft.com/dotnet/sdk:7.0 AS build-env FROM --platform=$BUILDPLATFORM mcr.microsoft.com/dotnet/sdk:8.0 AS build-env
ARG TARGETARCH
ARG BUILDPLATFORM
WORKDIR /App WORKDIR /App
EXPOSE 80 EXPOSE 80
RUN echo "Target: $TARGETARCH"
RUN echo "Build: $BUILDPLATFORM"
# Copy everything # Copy everything
COPY . ./ COPY . ./
# Restore as distinct layers # Restore as distinct layers
RUN dotnet restore "gaseous-server/gaseous-server.csproj" RUN dotnet restore "gaseous-server/gaseous-server.csproj" -a $TARGETARCH
# Build and publish a release # Build and publish a release
RUN dotnet publish "gaseous-server/gaseous-server.csproj" --use-current-runtime --self-contained false -c Release -o out RUN dotnet publish "gaseous-server/gaseous-server.csproj" --use-current-runtime --self-contained true -c Release -o out -a $TARGETARCH
# download and unzip EmulatorJS from CDN # download and unzip EmulatorJS from CDN
RUN apt-get update && apt-get install -y p7zip-full RUN apt-get update && apt-get install -y p7zip-full
RUN mkdir -p out/wwwroot/emulators/EmulatorJS RUN mkdir -p out/wwwroot/emulators/EmulatorJS
RUN wget https://cdn.emulatorjs.org/releases/4.0.9.7z RUN wget https://cdn.emulatorjs.org/releases/4.0.11.7z
RUN 7z x -y -oout/wwwroot/emulators/EmulatorJS 4.0.9.7z RUN 7z x -y -oout/wwwroot/emulators/EmulatorJS 4.0.11.7z
# Build runtime image # Build runtime image
FROM mcr.microsoft.com/dotnet/aspnet:7.0 FROM mcr.microsoft.com/dotnet/aspnet:8.0
ENV INDOCKER=1
WORKDIR /App WORKDIR /App
COPY --from=build-env /App/out . COPY --from=build-env /App/out .
ENTRYPOINT ["dotnet", "gaseous-server.dll"] ENTRYPOINT ["dotnet", "gaseous-server.dll"]

View File

@@ -1,9 +1,9 @@

Microsoft Visual Studio Solution File, Format Version 12.00 Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio Version 16 # Visual Studio Version 16
VisualStudioVersion = 25.0.1704.4 VisualStudioVersion = 25.0.1704.4
MinimumVisualStudioVersion = 10.0.40219.1 MinimumVisualStudioVersion = 10.0.40219.1
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "gaseous-server", "gaseous-server\gaseous-server.csproj", "{A01D2EFF-C82E-473B-84D7-7C25E736F5D2}" Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "gaseous-server", "gaseous-server\gaseous-server.csproj", "{A01D2EFF-C82E-473B-84D7-7C25E736F5D2}"
EndProject EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{17FA6F12-8532-420C-9489-CB8FDE42137C}" Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{17FA6F12-8532-420C-9489-CB8FDE42137C}"
ProjectSection(SolutionItems) = preProject ProjectSection(SolutionItems) = preProject

185
README.MD
View File

@@ -1,186 +1,53 @@
[![.NET](https://github.com/gaseous-project/gaseous-server/actions/workflows/dotnet.yml/badge.svg)](https://github.com/gaseous-project/gaseous-server/actions/workflows/dotnet.yml) [![CodeQL](https://github.com/gaseous-project/gaseous-server/actions/workflows/codeql.yml/badge.svg)](https://github.com/gaseous-project/gaseous-server/actions/workflows/codeql.yml) [![Build Release Docker Image](https://github.com/gaseous-project/gaseous-server/actions/workflows/BuildDockerOnTag-Release.yml/badge.svg)](https://github.com/gaseous-project/gaseous-server/actions/workflows/BuildDockerOnTag-Release.yml)
# Gaseous Server # Gaseous Server
This is the server for the Gaseous system. It offers ROM and title management, as well as some basic in browser emulation of those ROMs. This is the server for the Gaseous system. It offers ROM and title management, as well as some basic in browser emulation of those ROMs.
## Warning ## Warning
This project is currently not suitable for being exposed to the internet. Versions 1.6.1 and earlier are not suitable for being exposed to the internet, as:
1. there is currently no authentication support, meaning anyone could trash your library 1. there is no authentication support, meaning anyone could trash your library
2. the server has not been hardened for exposure to the internet - so there maybe unknown vulnerabilities 2. the server has not been hardened for exposure to the internet - so there maybe unknown vulnerabilities
If you expose the server to the internet, **you do so at your own risk**. If you expose one of these earlier versions of the server to the internet, **you do so at your own risk**.
Version 1.7.0 and later contain user authentication, and can be exposed to the internet. However, it is recommended to no expose the server to the internet if you're not actively using it remotely, or if you have alternative means to access it remotely like a VPN.
While we do our best to stay on top of server security, if you expose the server to the internet **you do so at your own risk**.
## Screenshots ## Screenshots
![Library](./screenshots/Library.png) ![Library](./screenshots/Library.png)
![Game](./screenshots/Game.png) ![Game](./screenshots/Game.png)
![Emulator](./screenshots/Emulator.png) ![Emulator](./screenshots/Emulator.png)
## Requirements ## Requirements
* MariaDB 11.1.2 or MySQL Server 8+ * MariaDB 11.1.2 (preferred) or MySQL Server 8+
* These are the database versions Gaseous has been tested and developed against. Your mileage may vary with earlier versions. * These are the database versions Gaseous has been tested and developed against. Your mileage may vary with earlier versions.
* Currently MariaDB is the preferred database server, while MySQL will continue to be supported for existing users (they should be interchangable). * MariaDB is the preferred database server, while MySQL will continue to be supported for existing users (they should be interchangable).
* Note that due to the earlier database schema using MySQL specific features, moving to MariaDB from MySQL will require rebuilding your database from scratch. The "Library Scan" background task can be used to re-import all titles. * Note that due to the earlier database schema using MySQL specific features, moving to MariaDB from MySQL will require rebuilding your database from scratch. The "Library Scan" background task can be used to re-import all titles.
* Internet Game Database API Key. See: https://api-docs.igdb.com/#account-creation * Internet Game Database API Key. See: https://api-docs.igdb.com/#account-creation
If using the provided docker-compose.yml, MariaDB will be installed for you.
## Friends of Gaseous
* [EmulatorJS](https://github.com/EmulatorJS/EmulatorJS): A fantastic (and fast) Javascript based implementation of RetroArch, supporting a wide variety of platforms. Discord: https://discord.gg/6akryGkETU
* [RomM](https://github.com/zurdi15/romm): Another self hosted ROM manager. Discord: https://discord.gg/P5HtHnhUDH
## Third Party Projects ## Third Party Projects
The following projects are used by Gaseous The following projects are used by Gaseous
* https://dotnet.microsoft.com/en-us/apps/aspnet * [ASP.NET](https://dotnet.microsoft.com/en-us/apps/aspnet)
* https://github.com/JamesNK/Newtonsoft.Json * [Newtonsoft.Json](https://github.com/JamesNK/Newtonsoft.Json)
* https://www.nuget.org/packages/MySql.Data/8.0.32.1 * [MySQLConnector](https://mysqlconnector.net)
* https://github.com/kamranayub/igdb-dotnet * [IGDB-DOTNET](https://github.com/kamranayub/igdb-dotnet)
* https://github.com/EmulatorJS/EmulatorJS * [EmulatorJS](https://github.com/EmulatorJS/EmulatorJS)
## Discord Server ## Discord Server
[![Join our Discord server!](https://invite.casperiv.dev/?inviteCode=Nhu7wpT3k4&format=svg)](https://discord.gg/Nhu7wpT3k4) [![Join our Discord server!](https://invite.casperiv.dev/?inviteCode=Nhu7wpT3k4&format=svg)](https://discord.gg/Nhu7wpT3k4)
# Setup # Installation
See https://github.com/gaseous-project/gaseous-server/wiki/Installation for installation instructions.
## Configuration File
When Gaseous-Server is started for the first time, it creates a configuration file at ~/.gaseous-server/config.json if it doesn't exist. Some values can be filled in using environment variables (such as in the case of using docker).
### DatabaseConfiguration
| Attribute | Environment Variable |
| --------- | -------------------- |
| HostName | dbhost |
| UserName | dbuser |
| Password | dbpass |
### IGDBConfiguration
| Attribute | Environment Variable |
| --------- | -------------------- |
| ClientId | igdbclientid |
| Secret. | igdbclientsecret |
### config.json
```json
{
"DatabaseConfiguration": {
"HostName": "localhost",
"UserName": "gaseous",
"Password": "gaseous",
"DatabaseName": "gaseous",
"Port": 3306
},
"IGDBConfiguration": {
"ClientId": "<clientid>",
"Secret": "<secret>"
},
"LoggingConfiguration": {
"DebugLogging": false,
"LogRetention": 7
}
}
```
## Docker
### Deploy with the prebuilt Docker image
Dockerfile and docker-compose.yml files have been provided to make deployment of the server as easy as possible.
1. Download the docker-compose-{database}.yml file for the database type you would like to use.
2. Open the docker-compose.yml file and edit the igdbclientid and igdbclientsecret to the values retrieved from your IGDB account
3. Run the command ```docker-compose up -d```
4. Connect to the host on port 5198
### Build and deploy a Docker image from source
Dockerfile and docker-compose-build.yml files have been provided to make deployment of the server as easy as possible.
1. Clone the repo with ```git clone https://github.com/gaseous-project/gaseous-server.git```
2. Change into the gaseous-server directory
3. Open the docker-compose-{database}-build.yml file and edit the igdbclientid and igdbclientsecret to the values retrieved from your IGDB account
4. Run the command ```docker-compose --file docker-compose-{database}-build.yml up -d```
5. Connect to the host on port 5198
## Source
### Build and deploy
1. Install and configure a MariaDB or MySQL instance - this is beyond the scope of this document
2. Install the dotnet 7.0 packages appropriate for your operating system
* See: https://learn.microsoft.com/en-us/dotnet/core/install/linux
3. Create a database user with permission to create a databse. Gaseous will create the new database and apply the database schema on it's first startup.
4. Clone the repo with ```git clone https://github.com/gaseous-project/gaseous-server.git```
5. Change into the gaseous-server directory
6. As the main branch is the development branch, you might want to change to a stable version - these are tagged with a version number. For example to change to the 1.5.0 release, use the command ```git checkout v1.5.0```
* Check the releases page for the version you would like to run: https://github.com/gaseous-project/gaseous-server/releases
7. Download the emulator files from ```https://cdn.emulatorjs.org/releases/4.0.9.zip``` and extract the files to ```gaseous-server/wwwroot/emulators/EmulatorJS```
8. Create a directory in the home directory of the user that will run the server. For example, if running as the user ```gaseous```, create the directory ```/home/gaseous/.gaseous-server```
9. Change into the ```.gaseous-server``` directory created in the previous step
10. Copy the JSON from the config file above into a new file named ```config.json```
11. Update the database section with the database server hostname, username, password, and port
12. Compile the server by changing back to the repo cloned earlier and executing:
* ```dotnet restore "gaseous-server/gaseous-server.csproj"```
* ```dotnet publish "gaseous-server/gaseous-server.csproj" --use-current-runtime --self-contained false -c Release -o <output directory>```
* replace ```<output directory>``` with the directory of your choosing. The compiled application will be copied there. For this example we'll use ```/opt/gaseous-server```
13. The server can then be started by executing ```dotnet /opt/gaseous-server/gaseous-server.dll```
* If you would like the server to run on a different ip address and port (for example 0.0.0.0:8080), add the --urls argument: ```dotnet /opt/gaseous-server/gaseous-server.dll --urls http://0.0.0.0:8080```
**Note**: The above instructions were tested on macOS Ventura, and Ubuntu 22.04.3. There was a report that Debian 11 had an issue with the git submodule commands (see: https://github.com/gaseous-project/gaseous-server/issues/71). This was possibly due to an older git package.
### Updating from source
1. Stop the server
2. Switch to the source directory
3. Update your repo:
* If running from the main branch, run ```git pull``` to update the repo
* If running from another branch or tag, run:
* ```git fetch```
* ```git checkout <branch or tag name>```
4. Run steps 12 and 13 from the above Build guide
# Adding Content # Adding Content
While games can be added to the server without them, it is recommended adding some signature DAT files beforehand to allow for better matching of ROMs to games. 1. Import signatures: see https://github.com/gaseous-project/gaseous-server/wiki/Signatures
2. Add ROMs: see https://github.com/gaseous-project/gaseous-server/wiki/Adding-ROMs
These signature DAT files contain a list of titles with hashes for many of the ROM images that have been found by the community.
Currently supported DAT's:
* TOSEC: https://www.tosecdev.org/downloads/category/56-2023-01-23
* MAME Arcade and MAME Mess: https://www.progettosnaps.net/dats/MAME
If there are other DAT's you'd like to see support for, please raise an issue with a link to the DAT's.
## Adding signature DAT files
### TOSEC
1. Download the DAT files from the source website. For example; from https://www.tosecdev.org/downloads/category/56-2023-01-23
2. Extract the archive
3. Copy the DAT files to ~/.gaseous-server/Data/Signatures/TOSEC/
### MAME Arcade
1. Download the DAT files from the source website. For example; from https://www.progettosnaps.net/dats/MAME
2. Extract the archive
3. Copy the file name "MAME 0.257 (arcade).dat" files to ~/.gaseous-server/Data/Signatures/MAME Arcade/
### MAME MESS
1. Download the DAT files from the source website. For example; from https://www.progettosnaps.net/dats/MAME
2. Extract the archive
3. Copy the file name "MAME 0.257 (mess).dat" files to ~/.gaseous-server/Data/Signatures/MAME MESS/
# Adding Game Images
1. Files can be presented as either stand alone files, or as zip files - currently 7z is unsupported.
2. Name the file appropriately.
* Attempting a search for the game name on https://www.igdb.com can help with file naming. If a hash search is unsuccessful, Gaseous will fall back to attempting to search by the file name.
3. Add the file to the server:
* Click the Upload button in the top right of the main Gaseous web page, and drag the files into the modal. The files will be uploaded and analyzed.
* Copy the file to ~/.gaseous-server/Data/Import
# Game Image Title Matching
Image to game matching follows the following order of operations, stopping the process at the first match:
### Get the file signature
1. Attempt a hash search
2. Attempt to search the signature database for a rom matching the file name - sometimes the hash can not be matched as a highscore table for example was saved to the image
3. Attempt to parse the file name - clues such as the extension being used to define which platform the file belongs to are used to create a search criteria
**Note**: If the file being scanned is a zip, the file will be extracted and searched. The first file whose signature can be found will be used to match the entire zip archive - be sure that the zip only contains files related to one game.
### Create a list of search candidates
Before beginning, remove any version numbers, and anything in the search string that is between ()
1. Add the full name of the image
2. Add the name of the image with any " - " replaced by ": "
3. Add the name of the image with text after a " - " removed
4. Add the name of the image with text after a ": " removed
### Search IGDB for a game match
Loop through each of the search candidates searching using:
1. "where" - exact match as the search candidate
2. "wherefuzzy" - partial match using wildcards
3. "search" - uses a more flexible search method
4. "searchNoPlatform" - uses the "search" method, but does not constrain the search to the determined platform
**Note**: If more than one result is found, the seach will loop through the returned results:
* If an exact (case-insensitive) match is found, that result is used for the match
* If still no match, the image will be set as "Unknown" as there is no way for Gaseous to know which title is the correct one.

View File

@@ -14,6 +14,7 @@ services:
volumes: volumes:
- gs:/root/.gaseous-server - gs:/root/.gaseous-server
environment: environment:
- TZ=Australia/Sydney
- dbhost=gsdb - dbhost=gsdb
- dbuser=root - dbuser=root
- dbpass=gaseous - dbpass=gaseous

View File

@@ -1,39 +0,0 @@
version: '2'
services:
gaseous-server:
container_name: gaseous-server
build:
context: ./
restart: unless-stopped
networks:
- gaseous
depends_on:
- gsdb
ports:
- 5198:80
volumes:
- gs:/root/.gaseous-server
environment:
- dbhost=gsdb
- dbuser=root
- dbpass=gaseous
- igdbclientid=<clientid>
- igdbclientsecret=<clientsecret>
gsdb:
container_name: gsdb
image: mysql:8
restart: unless-stopped
networks:
- gaseous
volumes:
- gsdb:/var/lib/mysql
environment:
- MYSQL_ROOT_PASSWORD=gaseous
- MYSQL_USER=gaseous
- MYSQL_PASSWORD=gaseous
networks:
gaseous:
driver: bridge
volumes:
gs:
gsdb:

View File

@@ -1,38 +0,0 @@
version: '2'
services:
gaseous-server:
container_name: gaseous-server
image: gaseousgames/gaseousserver:latest
restart: unless-stopped
networks:
- gaseous
depends_on:
- gsdb
ports:
- 5198:80
volumes:
- gs:/root/.gaseous-server
environment:
- dbhost=gsdb
- dbuser=root
- dbpass=gaseous
- igdbclientid=<clientid>
- igdbclientsecret=<clientsecret>
gsdb:
container_name: gsdb
image: mysql:8
restart: unless-stopped
networks:
- gaseous
volumes:
- gsdb:/var/lib/mysql
environment:
- MYSQL_ROOT_PASSWORD=gaseous
- MYSQL_USER=gaseous
- MYSQL_PASSWORD=gaseous
networks:
gaseous:
driver: bridge
volumes:
gs:
gsdb:

View File

@@ -13,6 +13,7 @@ services:
volumes: volumes:
- gs:/root/.gaseous-server - gs:/root/.gaseous-server
environment: environment:
- TZ=Australia/Sydney
- dbhost=gsdb - dbhost=gsdb
- dbuser=root - dbuser=root
- dbpass=gaseous - dbpass=gaseous

Binary file not shown.

View File

@@ -12,5 +12,6 @@ namespace Authentication
{ {
public SecurityProfileViewModel SecurityProfile { get; set; } public SecurityProfileViewModel SecurityProfile { get; set; }
public List<UserPreferenceViewModel> UserPreferences { get; set; } public List<UserPreferenceViewModel> UserPreferences { get; set; }
public Guid Avatar { get; set; }
} }
} }

View File

@@ -75,7 +75,7 @@ namespace Authentication
public TUser GetUserById(string userId) public TUser GetUserById(string userId)
{ {
TUser user = null; TUser user = null;
string commandText = "Select * from Users where Id = @id"; string commandText = "Select * from Users LEFT JOIN (SELECT UserId, Id AS AvatarId FROM UserAvatars) UserAvatars ON Users.Id = UserAvatars.UserId where Id = @id";
Dictionary<string, object> parameters = new Dictionary<string, object>() { { "@id", userId } }; Dictionary<string, object> parameters = new Dictionary<string, object>() { { "@id", userId } };
var rows = _database.ExecuteCMDDict(commandText, parameters); var rows = _database.ExecuteCMDDict(commandText, parameters);
@@ -100,6 +100,7 @@ namespace Authentication
user.TwoFactorEnabled = row["TwoFactorEnabled"] == "1" ? true:false; user.TwoFactorEnabled = row["TwoFactorEnabled"] == "1" ? true:false;
user.SecurityProfile = GetSecurityProfile(user); user.SecurityProfile = GetSecurityProfile(user);
user.UserPreferences = GetPreferences(user); user.UserPreferences = GetPreferences(user);
user.Avatar = string.IsNullOrEmpty((string?)row["AvatarId"]) ? Guid.Empty : Guid.Parse((string?)row["AvatarId"]);
} }
return user; return user;
@@ -113,7 +114,7 @@ namespace Authentication
public List<TUser> GetUserByName(string normalizedUserName) public List<TUser> GetUserByName(string normalizedUserName)
{ {
List<TUser> users = new List<TUser>(); List<TUser> users = new List<TUser>();
string commandText = "Select * from Users where NormalizedEmail = @name"; string commandText = "Select * from Users LEFT JOIN (SELECT UserId, Id AS AvatarId FROM UserAvatars) UserAvatars ON Users.Id = UserAvatars.UserId where NormalizedEmail = @name";
Dictionary<string, object> parameters = new Dictionary<string, object>() { { "@name", normalizedUserName } }; Dictionary<string, object> parameters = new Dictionary<string, object>() { { "@name", normalizedUserName } };
var rows = _database.ExecuteCMDDict(commandText, parameters); var rows = _database.ExecuteCMDDict(commandText, parameters);
@@ -137,6 +138,7 @@ namespace Authentication
user.TwoFactorEnabled = row["TwoFactorEnabled"] == "1" ? true:false; user.TwoFactorEnabled = row["TwoFactorEnabled"] == "1" ? true:false;
user.SecurityProfile = GetSecurityProfile(user); user.SecurityProfile = GetSecurityProfile(user);
user.UserPreferences = GetPreferences(user); user.UserPreferences = GetPreferences(user);
user.Avatar = string.IsNullOrEmpty((string?)row["AvatarId"]) ? Guid.Empty : Guid.Parse((string?)row["AvatarId"]);
users.Add(user); users.Add(user);
} }
@@ -146,7 +148,7 @@ namespace Authentication
public List<TUser> GetUsers() public List<TUser> GetUsers()
{ {
List<TUser> users = new List<TUser>(); List<TUser> users = new List<TUser>();
string commandText = "Select * from Users order by NormalizedUserName"; string commandText = "Select * from Users LEFT JOIN (SELECT UserId, Id AS AvatarId FROM UserAvatars) UserAvatars ON Users.Id = UserAvatars.UserId order by NormalizedUserName";
var rows = _database.ExecuteCMDDict(commandText); var rows = _database.ExecuteCMDDict(commandText);
foreach(Dictionary<string, object> row in rows) foreach(Dictionary<string, object> row in rows)
@@ -169,6 +171,7 @@ namespace Authentication
user.TwoFactorEnabled = row["TwoFactorEnabled"] == "1" ? true:false; user.TwoFactorEnabled = row["TwoFactorEnabled"] == "1" ? true:false;
user.SecurityProfile = GetSecurityProfile(user); user.SecurityProfile = GetSecurityProfile(user);
user.UserPreferences = GetPreferences(user); user.UserPreferences = GetPreferences(user);
user.Avatar = string.IsNullOrEmpty((string?)row["AvatarId"]) ? Guid.Empty : Guid.Parse((string?)row["AvatarId"]);
users.Add(user); users.Add(user);
} }
@@ -437,5 +440,30 @@ namespace Authentication
return 0; return 0;
} }
} }
public Guid SetAvatar(TUser user, byte[] bytes)
{
Database db = new Database(Database.databaseType.MySql, Config.DatabaseConfiguration.ConnectionString);
string sql;
Dictionary<string, object> dbDict = new Dictionary<string, object>
{
{ "userid", user.Id }
};
if (bytes.Length == 0)
{
sql = "DELETE FROM UserAvatars WHERE UserId = @userid";
db.ExecuteNonQuery(sql, dbDict);
return Guid.Empty;
}
else
{
sql = "DELETE FROM UserAvatars WHERE UserId = @userid; INSERT INTO UserAvatars (UserId, Id, Avatar) VALUES (@userid, @id, @avatar);";
dbDict.Add("id", Guid.NewGuid());
dbDict.Add("avatar", bytes);
db.ExecuteNonQuery(sql, dbDict);
return (Guid)dbDict["id"];
}
}
} }
} }

View File

@@ -8,6 +8,7 @@ namespace Authentication
public List<String> Roles { get; set; } public List<String> Roles { get; set; }
public SecurityProfileViewModel SecurityProfile { get; set; } public SecurityProfileViewModel SecurityProfile { get; set; }
public List<UserPreferenceViewModel> UserPreferences { get; set; } public List<UserPreferenceViewModel> UserPreferences { get; set; }
public Guid Avatar { get; set; }
public string HighestRole { public string HighestRole {
get get
{ {

View File

@@ -8,6 +8,7 @@ namespace Authentication
public DateTimeOffset? LockoutEnd { get; set; } public DateTimeOffset? LockoutEnd { get; set; }
public List<string> Roles { get; set; } public List<string> Roles { get; set; }
public SecurityProfileViewModel SecurityProfile { get; set; } public SecurityProfileViewModel SecurityProfile { get; set; }
public Guid Avatar { get; set; }
public string HighestRole { public string HighestRole {
get get
{ {

View File

@@ -7,33 +7,26 @@ using System.Security.Cryptography;
using Authentication; using Authentication;
using gaseous_server.Classes.Metadata; using gaseous_server.Classes.Metadata;
using gaseous_server.Controllers; using gaseous_server.Controllers;
using gaseous_server.Controllers.v1_1;
using gaseous_server.Models; using gaseous_server.Models;
using IGDB.Models; using IGDB.Models;
using Microsoft.AspNetCore.Identity; using Microsoft.AspNetCore.Identity;
using Microsoft.AspNetCore.Mvc.Filters; using Microsoft.AspNetCore.Mvc.Filters;
using Newtonsoft.Json; using Newtonsoft.Json;
using SharpCompress.Common; using SharpCompress.Common;
using static gaseous_server.Classes.Metadata.Games;
namespace gaseous_server.Classes namespace gaseous_server.Classes
{ {
public class Collections public class Collections
{ {
private readonly UserManager<ApplicationUser> _userManager; public static List<CollectionItem> GetCollections(string userid) {
private readonly SignInManager<ApplicationUser> _signInManager;
public Collections(
UserManager<ApplicationUser> userManager,
SignInManager<ApplicationUser> signInManager)
{
_userManager = userManager;
_signInManager = signInManager;
}
public static List<CollectionItem> GetCollections() {
Database db = new Database(Database.databaseType.MySql, Config.DatabaseConfiguration.ConnectionString); Database db = new Database(Database.databaseType.MySql, Config.DatabaseConfiguration.ConnectionString);
string sql = "SELECT * FROM RomCollections ORDER BY `Name`"; string sql = "SELECT * FROM RomCollections WHERE OwnedBy=@ownedby ORDER BY `Name`";
Dictionary<string, object> dbDict = new Dictionary<string, object>{
DataTable data = db.ExecuteCMD(sql); { "ownedby", userid }
};
DataTable data = db.ExecuteCMD(sql, dbDict);
List<CollectionItem> collectionItems = new List<CollectionItem>(); List<CollectionItem> collectionItems = new List<CollectionItem>();
@@ -44,11 +37,24 @@ namespace gaseous_server.Classes
return collectionItems; return collectionItems;
} }
public static CollectionItem GetCollection(long Id) { public static CollectionItem GetCollection(long Id, string userid) {
Database db = new Database(Database.databaseType.MySql, Config.DatabaseConfiguration.ConnectionString); Database db = new Database(Database.databaseType.MySql, Config.DatabaseConfiguration.ConnectionString);
string sql = "SELECT * FROM RomCollections WHERE Id = @id ORDER BY `Name`"; string sql;
Dictionary<string, object> dbDict = new Dictionary<string, object>(); if (userid == "")
dbDict.Add("id", Id); {
// reserved for internal operations
sql = "SELECT * FROM RomCollections WHERE Id = @id ORDER BY `Name`";
}
else
{
// instigated by a user
sql = "SELECT * FROM RomCollections WHERE Id = @id AND OwnedBy = @ownedby ORDER BY `Name`";
}
Dictionary<string, object> dbDict = new Dictionary<string, object>
{
{ "id", Id },
{ "ownedby", userid }
};
DataTable romDT = db.ExecuteCMD(sql, dbDict); DataTable romDT = db.ExecuteCMD(sql, dbDict);
if (romDT.Rows.Count > 0) if (romDT.Rows.Count > 0)
@@ -64,60 +70,66 @@ namespace gaseous_server.Classes
} }
} }
public static CollectionItem NewCollection(CollectionItem item) public static CollectionItem NewCollection(CollectionItem item, string userid)
{ {
Database db = new Database(Database.databaseType.MySql, Config.DatabaseConfiguration.ConnectionString); Database db = new Database(Database.databaseType.MySql, Config.DatabaseConfiguration.ConnectionString);
string sql = "INSERT INTO RomCollections (`Name`, Description, Platforms, Genres, Players, PlayerPerspectives, Themes, MinimumRating, MaximumRating, MaximumRomsPerPlatform, MaximumBytesPerPlatform, MaximumCollectionSizeInBytes, FolderStructure, IncludeBIOSFiles, ArchiveType, AlwaysInclude, BuiltStatus) VALUES (@name, @description, @platforms, @genres, @players, @playerperspectives, @themes, @minimumrating, @maximumrating, @maximumromsperplatform, @maximumbytesperplatform, @maximumcollectionsizeinbytes, @folderstructure, @includebiosfiles, @archivetype, @alwaysinclude, @builtstatus); SELECT CAST(LAST_INSERT_ID() AS SIGNED);"; string sql = "INSERT INTO RomCollections (`Name`, Description, Platforms, Genres, Players, PlayerPerspectives, Themes, MinimumRating, MaximumRating, MaximumRomsPerPlatform, MaximumBytesPerPlatform, MaximumCollectionSizeInBytes, FolderStructure, IncludeBIOSFiles, ArchiveType, AlwaysInclude, BuiltStatus, OwnedBy) VALUES (@name, @description, @platforms, @genres, @players, @playerperspectives, @themes, @minimumrating, @maximumrating, @maximumromsperplatform, @maximumbytesperplatform, @maximumcollectionsizeinbytes, @folderstructure, @includebiosfiles, @archivetype, @alwaysinclude, @builtstatus, @ownedby); SELECT CAST(LAST_INSERT_ID() AS SIGNED);";
Dictionary<string, object> dbDict = new Dictionary<string, object>(); Dictionary<string, object> dbDict = new Dictionary<string, object>
dbDict.Add("name", item.Name); {
dbDict.Add("description", item.Description); { "name", item.Name },
dbDict.Add("platforms", Newtonsoft.Json.JsonConvert.SerializeObject(Common.ReturnValueIfNull(item.Platforms, new List<long>()))); { "description", item.Description },
dbDict.Add("genres", Newtonsoft.Json.JsonConvert.SerializeObject(Common.ReturnValueIfNull(item.Genres, new List<long>()))); { "platforms", Newtonsoft.Json.JsonConvert.SerializeObject(Common.ReturnValueIfNull(item.Platforms, new List<long>())) },
dbDict.Add("players", Newtonsoft.Json.JsonConvert.SerializeObject(Common.ReturnValueIfNull(item.Players, new List<long>()))); { "genres", Newtonsoft.Json.JsonConvert.SerializeObject(Common.ReturnValueIfNull(item.Genres, new List<long>())) },
dbDict.Add("playerperspectives", Newtonsoft.Json.JsonConvert.SerializeObject(Common.ReturnValueIfNull(item.PlayerPerspectives, new List<long>()))); { "players", Newtonsoft.Json.JsonConvert.SerializeObject(Common.ReturnValueIfNull(item.Players, new List<long>())) },
dbDict.Add("themes", Newtonsoft.Json.JsonConvert.SerializeObject(Common.ReturnValueIfNull(item.Themes, new List<long>()))); { "playerperspectives", Newtonsoft.Json.JsonConvert.SerializeObject(Common.ReturnValueIfNull(item.PlayerPerspectives, new List<long>())) },
dbDict.Add("minimumrating", Common.ReturnValueIfNull(item.MinimumRating, -1)); { "themes", Newtonsoft.Json.JsonConvert.SerializeObject(Common.ReturnValueIfNull(item.Themes, new List<long>())) },
dbDict.Add("maximumrating", Common.ReturnValueIfNull(item.MaximumRating, -1)); { "minimumrating", Common.ReturnValueIfNull(item.MinimumRating, -1) },
dbDict.Add("maximumromsperplatform", Common.ReturnValueIfNull(item.MaximumRomsPerPlatform, -1)); { "maximumrating", Common.ReturnValueIfNull(item.MaximumRating, -1) },
dbDict.Add("maximumbytesperplatform", Common.ReturnValueIfNull(item.MaximumBytesPerPlatform, -1)); { "maximumromsperplatform", Common.ReturnValueIfNull(item.MaximumRomsPerPlatform, -1) },
dbDict.Add("maximumcollectionsizeinbytes", Common.ReturnValueIfNull(item.MaximumCollectionSizeInBytes, -1)); { "maximumbytesperplatform", Common.ReturnValueIfNull(item.MaximumBytesPerPlatform, -1) },
dbDict.Add("folderstructure", Common.ReturnValueIfNull(item.FolderStructure, CollectionItem.FolderStructures.Gaseous)); { "maximumcollectionsizeinbytes", Common.ReturnValueIfNull(item.MaximumCollectionSizeInBytes, -1) },
dbDict.Add("includebiosfiles", Common.ReturnValueIfNull(item.IncludeBIOSFiles, 0)); { "folderstructure", Common.ReturnValueIfNull(item.FolderStructure, CollectionItem.FolderStructures.Gaseous) },
dbDict.Add("archivetype", Common.ReturnValueIfNull(item.ArchiveType, CollectionItem.ArchiveTypes.Zip)); { "includebiosfiles", Common.ReturnValueIfNull(item.IncludeBIOSFiles, 0) },
dbDict.Add("alwaysinclude", Newtonsoft.Json.JsonConvert.SerializeObject(Common.ReturnValueIfNull(item.AlwaysInclude, new List<CollectionItem.AlwaysIncludeItem>()))); { "archivetype", Common.ReturnValueIfNull(item.ArchiveType, CollectionItem.ArchiveTypes.Zip) },
dbDict.Add("builtstatus", CollectionItem.CollectionBuildStatus.WaitingForBuild); { "alwaysinclude", Newtonsoft.Json.JsonConvert.SerializeObject(Common.ReturnValueIfNull(item.AlwaysInclude, new List<CollectionItem.AlwaysIncludeItem>())) },
{ "builtstatus", CollectionItem.CollectionBuildStatus.WaitingForBuild },
{ "ownedby", userid }
};
DataTable romDT = db.ExecuteCMD(sql, dbDict); DataTable romDT = db.ExecuteCMD(sql, dbDict);
long CollectionId = (long)romDT.Rows[0][0]; long CollectionId = (long)romDT.Rows[0][0];
CollectionItem collectionItem = GetCollection(CollectionId); CollectionItem collectionItem = GetCollection(CollectionId, userid);
StartCollectionItemBuild(CollectionId); StartCollectionItemBuild(CollectionId, userid);
return collectionItem; return collectionItem;
} }
public static CollectionItem EditCollection(long Id, CollectionItem item, bool ForceRebuild = true) public static CollectionItem EditCollection(long Id, CollectionItem item, string userid, bool ForceRebuild = true)
{ {
Database db = new Database(Database.databaseType.MySql, Config.DatabaseConfiguration.ConnectionString); Database db = new Database(Database.databaseType.MySql, Config.DatabaseConfiguration.ConnectionString);
string sql = "UPDATE RomCollections SET `Name`=@name, Description=@description, Platforms=@platforms, Genres=@genres, Players=@players, PlayerPerspectives=@playerperspectives, Themes=@themes, MinimumRating=@minimumrating, MaximumRating=@maximumrating, MaximumRomsPerPlatform=@maximumromsperplatform, MaximumBytesPerPlatform=@maximumbytesperplatform, MaximumCollectionSizeInBytes=@maximumcollectionsizeinbytes, FolderStructure=@folderstructure, IncludeBIOSFiles=@includebiosfiles, ArchiveType=@archivetype, AlwaysInclude=@alwaysinclude, BuiltStatus=@builtstatus WHERE Id=@id"; string sql = "UPDATE RomCollections SET `Name`=@name, Description=@description, Platforms=@platforms, Genres=@genres, Players=@players, PlayerPerspectives=@playerperspectives, Themes=@themes, MinimumRating=@minimumrating, MaximumRating=@maximumrating, MaximumRomsPerPlatform=@maximumromsperplatform, MaximumBytesPerPlatform=@maximumbytesperplatform, MaximumCollectionSizeInBytes=@maximumcollectionsizeinbytes, FolderStructure=@folderstructure, IncludeBIOSFiles=@includebiosfiles, ArchiveType=@archivetype, AlwaysInclude=@alwaysinclude, BuiltStatus=@builtstatus WHERE Id=@id AND OwnedBy=@ownedby";
Dictionary<string, object> dbDict = new Dictionary<string, object>(); Dictionary<string, object> dbDict = new Dictionary<string, object>
dbDict.Add("id", Id); {
dbDict.Add("name", item.Name); { "id", Id },
dbDict.Add("description", item.Description); { "name", item.Name },
dbDict.Add("platforms", Newtonsoft.Json.JsonConvert.SerializeObject(Common.ReturnValueIfNull(item.Platforms, new List<long>()))); { "description", item.Description },
dbDict.Add("genres", Newtonsoft.Json.JsonConvert.SerializeObject(Common.ReturnValueIfNull(item.Genres, new List<long>()))); { "platforms", Newtonsoft.Json.JsonConvert.SerializeObject(Common.ReturnValueIfNull(item.Platforms, new List<long>())) },
dbDict.Add("players", Newtonsoft.Json.JsonConvert.SerializeObject(Common.ReturnValueIfNull(item.Players, new List<long>()))); { "genres", Newtonsoft.Json.JsonConvert.SerializeObject(Common.ReturnValueIfNull(item.Genres, new List<long>())) },
dbDict.Add("playerperspectives", Newtonsoft.Json.JsonConvert.SerializeObject(Common.ReturnValueIfNull(item.PlayerPerspectives, new List<long>()))); { "players", Newtonsoft.Json.JsonConvert.SerializeObject(Common.ReturnValueIfNull(item.Players, new List<long>())) },
dbDict.Add("themes", Newtonsoft.Json.JsonConvert.SerializeObject(Common.ReturnValueIfNull(item.Themes, new List<long>()))); { "playerperspectives", Newtonsoft.Json.JsonConvert.SerializeObject(Common.ReturnValueIfNull(item.PlayerPerspectives, new List<long>())) },
dbDict.Add("minimumrating", Common.ReturnValueIfNull(item.MinimumRating, -1)); { "themes", Newtonsoft.Json.JsonConvert.SerializeObject(Common.ReturnValueIfNull(item.Themes, new List<long>())) },
dbDict.Add("maximumrating", Common.ReturnValueIfNull(item.MaximumRating, -1)); { "minimumrating", Common.ReturnValueIfNull(item.MinimumRating, -1) },
dbDict.Add("maximumromsperplatform", Common.ReturnValueIfNull(item.MaximumRomsPerPlatform, -1)); { "maximumrating", Common.ReturnValueIfNull(item.MaximumRating, -1) },
dbDict.Add("maximumbytesperplatform", Common.ReturnValueIfNull(item.MaximumBytesPerPlatform, -1)); { "maximumromsperplatform", Common.ReturnValueIfNull(item.MaximumRomsPerPlatform, -1) },
dbDict.Add("maximumcollectionsizeinbytes", Common.ReturnValueIfNull(item.MaximumCollectionSizeInBytes, -1)); { "maximumbytesperplatform", Common.ReturnValueIfNull(item.MaximumBytesPerPlatform, -1) },
dbDict.Add("folderstructure", Common.ReturnValueIfNull(item.FolderStructure, CollectionItem.FolderStructures.Gaseous)); { "maximumcollectionsizeinbytes", Common.ReturnValueIfNull(item.MaximumCollectionSizeInBytes, -1) },
dbDict.Add("includebiosfiles", Common.ReturnValueIfNull(item.IncludeBIOSFiles, 0)); { "folderstructure", Common.ReturnValueIfNull(item.FolderStructure, CollectionItem.FolderStructures.Gaseous) },
dbDict.Add("alwaysinclude", Newtonsoft.Json.JsonConvert.SerializeObject(Common.ReturnValueIfNull(item.AlwaysInclude, new List<CollectionItem.AlwaysIncludeItem>()))); { "includebiosfiles", Common.ReturnValueIfNull(item.IncludeBIOSFiles, 0) },
dbDict.Add("archivetype", Common.ReturnValueIfNull(item.ArchiveType, CollectionItem.ArchiveTypes.Zip)); { "alwaysinclude", Newtonsoft.Json.JsonConvert.SerializeObject(Common.ReturnValueIfNull(item.AlwaysInclude, new List<CollectionItem.AlwaysIncludeItem>())) },
{ "archivetype", Common.ReturnValueIfNull(item.ArchiveType, CollectionItem.ArchiveTypes.Zip) },
{ "ownedby", userid }
};
string CollectionZipFile = Path.Combine(Config.LibraryConfiguration.LibraryCollectionsDirectory, Id + item.ArchiveExtension); string CollectionZipFile = Path.Combine(Config.LibraryConfiguration.LibraryCollectionsDirectory, Id + item.ArchiveExtension);
if (ForceRebuild == true) if (ForceRebuild == true)
@@ -142,22 +154,25 @@ namespace gaseous_server.Classes
} }
db.ExecuteCMD(sql, dbDict); db.ExecuteCMD(sql, dbDict);
CollectionItem collectionItem = GetCollection(Id); CollectionItem collectionItem = GetCollection(Id, userid);
if (collectionItem.BuildStatus == CollectionItem.CollectionBuildStatus.WaitingForBuild) if (collectionItem.BuildStatus == CollectionItem.CollectionBuildStatus.WaitingForBuild)
{ {
StartCollectionItemBuild(Id); StartCollectionItemBuild(Id, userid);
} }
return collectionItem; return collectionItem;
} }
public static void DeleteCollection(long Id) public static void DeleteCollection(long Id, string userid)
{ {
Database db = new Database(Database.databaseType.MySql, Config.DatabaseConfiguration.ConnectionString); Database db = new Database(Database.databaseType.MySql, Config.DatabaseConfiguration.ConnectionString);
string sql = "DELETE FROM RomCollections WHERE Id=@id"; string sql = "DELETE FROM RomCollections WHERE Id=@id AND OwnedBy=@ownedby";
Dictionary<string, object> dbDict = new Dictionary<string, object>(); Dictionary<string, object> dbDict = new Dictionary<string, object>
dbDict.Add("id", Id); {
{ "id", Id },
{ "ownedby", userid }
};
db.ExecuteCMD(sql, dbDict); db.ExecuteCMD(sql, dbDict);
string CollectionZipFile = Path.Combine(Config.LibraryConfiguration.LibraryCollectionsDirectory, Id + ".zip"); string CollectionZipFile = Path.Combine(Config.LibraryConfiguration.LibraryCollectionsDirectory, Id + ".zip");
@@ -167,9 +182,10 @@ namespace gaseous_server.Classes
} }
} }
public static void StartCollectionItemBuild(long Id) public static void StartCollectionItemBuild(long Id, string userid)
{ {
CollectionItem collectionItem = GetCollection(Id); // send blank user id to getcollection as this is not a user initiated process
CollectionItem collectionItem = GetCollection(Id, userid);
if (collectionItem.BuildStatus != CollectionItem.CollectionBuildStatus.Building) if (collectionItem.BuildStatus != CollectionItem.CollectionBuildStatus.Building)
{ {
@@ -183,13 +199,40 @@ namespace gaseous_server.Classes
// start background task // start background task
ProcessQueue.QueueItem queueItem = new ProcessQueue.QueueItem(ProcessQueue.QueueItemType.CollectionCompiler, 1, false, true); ProcessQueue.QueueItem queueItem = new ProcessQueue.QueueItem(ProcessQueue.QueueItemType.CollectionCompiler, 1, false, true);
queueItem.Options = Id; queueItem.Options = new Dictionary<string, object>{
{ "Id", Id },
{ "UserId", userid }
};
queueItem.ForceExecute(); queueItem.ForceExecute();
ProcessQueue.QueueItems.Add(queueItem); ProcessQueue.QueueItems.Add(queueItem);
} }
} }
public static CollectionContents GetCollectionContent(CollectionItem collectionItem) { public static CollectionContents GetCollectionContent(CollectionItem collectionItem, string userid) {
Database db = new Database(Database.databaseType.MySql, Config.DatabaseConfiguration.ConnectionString);
// get age ratings for specified user
List<AgeGroups.AgeRestrictionGroupings> UserAgeGroupings = new List<AgeGroups.AgeRestrictionGroupings>();
bool UserAgeGroupIncludeUnrated = true;
if (userid != "")
{
Authentication.UserTable<Authentication.ApplicationUser> userTable = new UserTable<ApplicationUser>(db);
var user = userTable.GetUserById(userid);
if (user.SecurityProfile.AgeRestrictionPolicy.IncludeUnrated == false)
{
UserAgeGroupIncludeUnrated = false;
}
foreach (AgeGroups.AgeRestrictionGroupings ageGrouping in Enum.GetValues(typeof(AgeGroups.AgeRestrictionGroupings)))
{
if (ageGrouping <= user.SecurityProfile.AgeRestrictionPolicy.MaximumAgeRestriction && ageGrouping != AgeGroups.AgeRestrictionGroupings.Unclassified)
{
UserAgeGroupings.Add(ageGrouping);
}
}
}
List<CollectionContents.CollectionPlatformItem> collectionPlatformItems = new List<CollectionContents.CollectionPlatformItem>(); List<CollectionContents.CollectionPlatformItem> collectionPlatformItems = new List<CollectionContents.CollectionPlatformItem>();
// get platforms // get platforms
@@ -230,6 +273,10 @@ namespace gaseous_server.Classes
} }
} }
// age ratings
AgeGroups.AgeRestrictionGroupings AgeGrouping = AgeGroups.AgeRestrictionGroupings.Unclassified;
bool ContainsUnclassifiedAgeGroup = false;
// build collection // build collection
List<CollectionContents.CollectionPlatformItem> platformItems = new List<CollectionContents.CollectionPlatformItem>(); List<CollectionContents.CollectionPlatformItem> platformItems = new List<CollectionContents.CollectionPlatformItem>();
@@ -247,18 +294,29 @@ namespace gaseous_server.Classes
isDynamic = true; isDynamic = true;
} }
List<Game> games = new List<Game>(); Controllers.v1_1.GamesController.GameReturnPackage games = new Controllers.v1_1.GamesController.GameReturnPackage();
if (isDynamic == true) if (isDynamic == true)
{ {
games = GamesController.GetGames("", Controllers.v1_1.GamesController.GameSearchModel searchModel = new Controllers.v1_1.GamesController.GameSearchModel{
platform.Id.ToString(), Name = "",
string.Join(",", collectionItem.Genres), Platform = new List<string>{
string.Join(",", collectionItem.Players), platform.Id.ToString()
string.Join(",", collectionItem.PlayerPerspectives), },
string.Join(",", collectionItem.Themes), Genre = collectionItem.Genres.ConvertAll(s => s.ToString()),
collectionItem.MinimumRating, GameMode = collectionItem.Players.ConvertAll(s => s.ToString()),
collectionItem.MaximumRating PlayerPerspective = collectionItem.PlayerPerspectives.ConvertAll(s => s.ToString()),
); Theme = collectionItem.Themes.ConvertAll(s => s.ToString()),
GameRating = new Controllers.v1_1.GamesController.GameSearchModel.GameRatingItem{
MinimumRating = collectionItem.MinimumRating,
MaximumRating = collectionItem.MaximumRating
},
GameAgeRating = new Controllers.v1_1.GamesController.GameSearchModel.GameAgeRatingItem{
AgeGroupings = UserAgeGroupings,
IncludeUnrated = UserAgeGroupIncludeUnrated
}
};
games = Controllers.v1_1.GamesController.GetGames(searchModel, userid);
} }
CollectionContents.CollectionPlatformItem collectionPlatformItem = new CollectionContents.CollectionPlatformItem(platform); CollectionContents.CollectionPlatformItem collectionPlatformItem = new CollectionContents.CollectionPlatformItem(platform);
@@ -274,7 +332,7 @@ namespace gaseous_server.Classes
) && alwaysIncludeItem.PlatformId == platform.Id ) && alwaysIncludeItem.PlatformId == platform.Id
) )
{ {
Game AlwaysIncludeGame = Games.GetGame(alwaysIncludeItem.GameId, false, false, false); MinimalGameItem AlwaysIncludeGame = new MinimalGameItem(Games.GetGame(alwaysIncludeItem.GameId, false, false, false));
CollectionContents.CollectionPlatformItem.CollectionGameItem gameItem = new CollectionContents.CollectionPlatformItem.CollectionGameItem(AlwaysIncludeGame); CollectionContents.CollectionPlatformItem.CollectionGameItem gameItem = new CollectionContents.CollectionPlatformItem.CollectionGameItem(AlwaysIncludeGame);
gameItem.InclusionStatus = new CollectionItem.AlwaysIncludeItem(); gameItem.InclusionStatus = new CollectionItem.AlwaysIncludeItem();
gameItem.InclusionStatus.PlatformId = alwaysIncludeItem.PlatformId; gameItem.InclusionStatus.PlatformId = alwaysIncludeItem.PlatformId;
@@ -286,7 +344,7 @@ namespace gaseous_server.Classes
} }
} }
foreach (Game game in games) { foreach (MinimalGameItem game in games.Games) {
bool gameAlreadyInList = false; bool gameAlreadyInList = false;
foreach (CollectionContents.CollectionPlatformItem.CollectionGameItem existingGame in collectionPlatformItem.Games) foreach (CollectionContents.CollectionPlatformItem.CollectionGameItem existingGame in collectionPlatformItem.Games)
{ {
@@ -341,6 +399,17 @@ namespace gaseous_server.Classes
} }
} }
} }
// handle age grouping
AgeGroups.AgeRestrictionGroupings CurrentAgeGroup = AgeGroups.GetAgeGroupFromAgeRatings(game.AgeRatings);
if (CurrentAgeGroup > AgeGrouping)
{
AgeGrouping = CurrentAgeGroup;
}
if (CurrentAgeGroup == AgeGroups.AgeRestrictionGroupings.Unclassified)
{
ContainsUnclassifiedAgeGroup = true;
}
} }
collectionPlatformItem.Games.Sort((x, y) => x.Name.CompareTo(y.Name)); collectionPlatformItem.Games.Sort((x, y) => x.Name.CompareTo(y.Name));
@@ -369,29 +438,39 @@ namespace gaseous_server.Classes
collectionPlatformItems.Sort((x, y) => x.Name.CompareTo(y.Name)); collectionPlatformItems.Sort((x, y) => x.Name.CompareTo(y.Name));
CollectionContents collectionContents = new CollectionContents(); CollectionContents collectionContents = new CollectionContents
collectionContents.Collection = collectionPlatformItems; {
Collection = collectionPlatformItems,
AgeGroup = AgeGrouping,
ContainsUnclassifiedAgeGroup = ContainsUnclassifiedAgeGroup
};
return collectionContents; return collectionContents;
} }
public static void CompileCollections(long CollectionId) public static void CompileCollections(long CollectionId, string userid)
{ {
Database db = new Database(Database.databaseType.MySql, Config.DatabaseConfiguration.ConnectionString); Database db = new Database(Database.databaseType.MySql, Config.DatabaseConfiguration.ConnectionString);
CollectionItem collectionItem = GetCollection(CollectionId); CollectionItem collectionItem = GetCollection(CollectionId, userid);
if (collectionItem.BuildStatus == CollectionItem.CollectionBuildStatus.WaitingForBuild) if (collectionItem.BuildStatus == CollectionItem.CollectionBuildStatus.WaitingForBuild)
{ {
Logging.Log(Logging.LogType.Information, "Collections", "Beginning build of collection: " + collectionItem.Name); Logging.Log(Logging.LogType.Information, "Collections", "Beginning build of collection: " + collectionItem.Name);
// set starting CollectionContents collectionContents = GetCollectionContent(collectionItem, userid);
string sql = "UPDATE RomCollections SET BuiltStatus=@bs WHERE Id=@id";
Dictionary<string, object> dbDict = new Dictionary<string, object>();
dbDict.Add("id", collectionItem.Id);
dbDict.Add("bs", CollectionItem.CollectionBuildStatus.Building);
db.ExecuteCMD(sql, dbDict);
List<CollectionContents.CollectionPlatformItem> collectionPlatformItems = GetCollectionContent(collectionItem).Collection; // set starting
string sql = "UPDATE RomCollections SET BuiltStatus=@bs, AgeGroup=@ag, AgeGroupUnclassified=@agu WHERE Id=@id";
Dictionary<string, object> dbDict = new Dictionary<string, object>
{
{ "id", collectionItem.Id },
{ "bs", CollectionItem.CollectionBuildStatus.Building },
{ "ag", collectionContents.AgeGroup },
{ "agu", collectionContents.ContainsUnclassifiedAgeGroup }
};
db.ExecuteCMD(sql, dbDict);
List<CollectionContents.CollectionPlatformItem> collectionPlatformItems = collectionContents.Collection;
string ZipFilePath = Path.Combine(Config.LibraryConfiguration.LibraryCollectionsDirectory, collectionItem.Id + collectionItem.ArchiveExtension); string ZipFilePath = Path.Combine(Config.LibraryConfiguration.LibraryCollectionsDirectory, collectionItem.Id + collectionItem.ArchiveExtension);
string ZipFileTempPath = Path.Combine(Config.LibraryConfiguration.LibraryTempDirectory, collectionItem.Id.ToString()); string ZipFileTempPath = Path.Combine(Config.LibraryConfiguration.LibraryTempDirectory, collectionItem.Id.ToString());
@@ -758,6 +837,9 @@ namespace gaseous_server.Classes
} }
} }
public AgeGroups.AgeRestrictionGroupings AgeGroup { get; set; }
public bool ContainsUnclassifiedAgeGroup { get; set; }
public class CollectionPlatformItem { public class CollectionPlatformItem {
public CollectionPlatformItem(IGDB.Models.Platform platform) { public CollectionPlatformItem(IGDB.Models.Platform platform) {
string[] PropertyWhitelist = new string[] { "Id", "Name", "Slug" }; string[] PropertyWhitelist = new string[] { "Id", "Name", "Slug" };
@@ -808,48 +890,43 @@ namespace gaseous_server.Classes
} }
} }
public class CollectionGameItem { public class CollectionGameItem : MinimalGameItem
public CollectionGameItem(IGDB.Models.Game game) { {
string[] PropertyWhitelist = new string[] { "Id", "Name", "Slug", "Cover" }; public CollectionGameItem(MinimalGameItem gameObject)
PropertyInfo[] srcProperties = typeof(IGDB.Models.Game).GetProperties(); {
PropertyInfo[] dstProperties = typeof(CollectionPlatformItem.CollectionGameItem).GetProperties(); this.Id = gameObject.Id;
foreach (PropertyInfo srcProperty in srcProperties) { this.Name = gameObject.Name;
if (PropertyWhitelist.Contains<string>(srcProperty.Name)) this.Slug = gameObject.Slug;
this.TotalRating = gameObject.TotalRating;
this.TotalRatingCount = gameObject.TotalRatingCount;
this.Cover = gameObject.Cover;
this.Artworks = gameObject.Artworks;
this.FirstReleaseDate = gameObject.FirstReleaseDate;
this.AgeRatings = gameObject.AgeRatings;
}
public IGDB.Models.Cover? CoverItem
{
get
{
if (Cover != null)
{ {
foreach (PropertyInfo dstProperty in dstProperties) IGDB.Models.Cover cover = Covers.GetCover(Cover.Id, Path.Combine(Config.LibraryConfiguration.LibraryMetadataDirectory, "Games", Slug), false);
{
if (srcProperty.Name == dstProperty.Name) return cover;
{ }
if (srcProperty.GetValue(game) != null) { else
string compareName = srcProperty.PropertyType.Name.ToLower().Split("`")[0]; {
switch(compareName) { return null;
case "identityorvalue":
string newObjectValue = Newtonsoft.Json.JsonConvert.SerializeObject(srcProperty.GetValue(game));
Dictionary<string, object> newDict = Newtonsoft.Json.JsonConvert.DeserializeObject<Dictionary<string, object>>(newObjectValue);
dstProperty.SetValue(this, newDict["Id"]);
break;
default:
dstProperty.SetValue(this, srcProperty.GetValue(game));
break;
}
}
}
}
} }
} }
} }
public long Id { get; set; } public AgeGroups.AgeRestrictionGroupings AgeGrouping
public string Name { get; set; } {
public string Slug { get; set; }
public long Cover { get; set;}
public IGDB.Models.Cover CoverItem
{
get get
{ {
IGDB.Models.Cover cover = Covers.GetCover(Cover, Path.Combine(Config.LibraryConfiguration.LibraryMetadataDirectory, "Games", Slug), false); return AgeGroups.GetAgeGroupFromAgeRatings(this.AgeRatings);
return cover;
} }
} }

View File

@@ -1,5 +1,6 @@
using System.Collections.Concurrent; using System.Collections.Concurrent;
using System.ComponentModel; using System.ComponentModel;
using System.IO.Compression;
using System.Reflection; using System.Reflection;
using System.Security.Cryptography; using System.Security.Cryptography;
@@ -120,6 +121,40 @@ namespace gaseous_server.Classes
.Single(x => x.GetValue(null).Equals(value)), .Single(x => x.GetValue(null).Equals(value)),
typeof(DescriptionAttribute)))?.Description ?? value.ToString(); typeof(DescriptionAttribute)))?.Description ?? value.ToString();
} }
// compression
public static byte[] Compress(byte[] data)
{
MemoryStream output = new MemoryStream();
using (DeflateStream dstream = new DeflateStream(output, CompressionLevel.Optimal))
{
dstream.Write(data, 0, data.Length);
}
return output.ToArray();
}
public static byte[] Decompress(byte[] data)
{
MemoryStream input = new MemoryStream(data);
MemoryStream output = new MemoryStream();
using (DeflateStream dstream = new DeflateStream(input, CompressionMode.Decompress))
{
dstream.CopyTo(output);
}
return output.ToArray();
}
public static object GetEnvVar(string envName, string defaultValue)
{
if (!String.IsNullOrEmpty(Environment.GetEnvironmentVariable(envName)))
{
return Environment.GetEnvironmentVariable(envName);
}
else
{
return defaultValue;
}
}
} }
/// <summary> /// <summary>

View File

@@ -3,6 +3,7 @@ using System.Data;
using Newtonsoft.Json; using Newtonsoft.Json;
using IGDB.Models; using IGDB.Models;
using gaseous_server.Classes.Metadata; using gaseous_server.Classes.Metadata;
using NuGet.Common;
namespace gaseous_server.Classes namespace gaseous_server.Classes
{ {
@@ -117,11 +118,33 @@ namespace gaseous_server.Classes
if (_tempConfig != null) if (_tempConfig != null)
{ {
_config = _tempConfig; _config = _tempConfig;
} else
// load environment variables if we're in a docker container
if (!String.IsNullOrEmpty(Environment.GetEnvironmentVariable("INDOCKER")))
{
if (Environment.GetEnvironmentVariable("INDOCKER") == "1")
{
Console.WriteLine("Running in Docker - setting configuration from variables");
_config.DatabaseConfiguration.HostName = (string)Common.GetEnvVar("dbhost", _config.DatabaseConfiguration.HostName);
_config.DatabaseConfiguration.UserName = (string)Common.GetEnvVar("dbuser", _config.DatabaseConfiguration.UserName);
_config.DatabaseConfiguration.Password = (string)Common.GetEnvVar("dbpass", _config.DatabaseConfiguration.Password);
_config.DatabaseConfiguration.DatabaseName = (string)Common.GetEnvVar("dbname", _config.DatabaseConfiguration.DatabaseName);
_config.DatabaseConfiguration.Port = int.Parse((string)Common.GetEnvVar("dbport", _config.DatabaseConfiguration.Port.ToString()));
_config.MetadataConfiguration.MetadataSource = (HasheousClient.Models.MetadataModel.MetadataSources)Enum.Parse(typeof(HasheousClient.Models.MetadataModel.MetadataSources), (string)Common.GetEnvVar("metadatasource", _config.MetadataConfiguration.MetadataSource.ToString()));
_config.MetadataConfiguration.SignatureSource = (HasheousClient.Models.MetadataModel.SignatureSources)Enum.Parse(typeof(HasheousClient.Models.MetadataModel.SignatureSources), (string)Common.GetEnvVar("signaturesource", _config.MetadataConfiguration.SignatureSource.ToString()));;
_config.MetadataConfiguration.MaxLibraryScanWorkers = int.Parse((string)Common.GetEnvVar("maxlibraryscanworkers", _config.MetadataConfiguration.MaxLibraryScanWorkers.ToString()));
_config.MetadataConfiguration.HasheousHost = (string)Common.GetEnvVar("hasheoushost", _config.MetadataConfiguration.HasheousHost);
_config.IGDBConfiguration.ClientId = (string)Common.GetEnvVar("igdbclientid", _config.IGDBConfiguration.ClientId);
_config.IGDBConfiguration.Secret = (string)Common.GetEnvVar("igdbclientsecret", _config.IGDBConfiguration.Secret);
}
}
}
else
{ {
throw new Exception("There was an error reading the config file: Json returned null"); throw new Exception("There was an error reading the config file: Json returned null");
} }
} else }
else
{ {
// no config file! // no config file!
// use defaults and save // use defaults and save
@@ -161,7 +184,7 @@ namespace gaseous_server.Classes
File.WriteAllText(ConfigurationFilePath, configRaw); File.WriteAllText(ConfigurationFilePath, configRaw);
} }
private static Dictionary<string, string> AppSettings = new Dictionary<string, string>(); private static Dictionary<string, object> AppSettings = new Dictionary<string, object>();
public static void InitSettings() public static void InitSettings()
{ {
@@ -171,62 +194,152 @@ namespace gaseous_server.Classes
DataTable dbResponse = db.ExecuteCMD(sql); DataTable dbResponse = db.ExecuteCMD(sql);
foreach (DataRow dataRow in dbResponse.Rows) foreach (DataRow dataRow in dbResponse.Rows)
{ {
if (AppSettings.ContainsKey((string)dataRow["Setting"])) string SettingName = (string)dataRow["Setting"];
{
AppSettings[(string)dataRow["Setting"]] = (string)dataRow["Value"];
}
else
{
AppSettings.Add((string)dataRow["Setting"], (string)dataRow["Value"]);
}
}
}
public static string ReadSetting(string SettingName, string DefaultValue)
{
if (AppSettings.ContainsKey(SettingName))
{
return AppSettings[SettingName];
}
else
{
Database db = new Database(Database.databaseType.MySql, Config.DatabaseConfiguration.ConnectionString);
string sql = "SELECT Value FROM Settings WHERE Setting = @SettingName";
Dictionary<string, object> dbDict = new Dictionary<string, object>();
dbDict.Add("SettingName", SettingName);
dbDict.Add("Value", DefaultValue);
if (AppSettings.ContainsKey(SettingName))
{
AppSettings.Remove(SettingName);
}
Logging.Log(Logging.LogType.Information, "Load Settings", "Loading setting " + SettingName + " from database");
try try
{ {
Logging.Log(Logging.LogType.Debug, "Database", "Reading setting '" + SettingName + "'"); switch ((int)dataRow["ValueType"])
DataTable dbResponse = db.ExecuteCMD(sql, dbDict);
if (dbResponse.Rows.Count == 0)
{ {
// no value with that name stored - respond with the default value case 0:
SetSetting(SettingName, DefaultValue); default:
return DefaultValue; // value is a string
AppSettings.Add(SettingName, dataRow["Value"]);
break;
case 1:
// value is a datetime
AppSettings.Add(SettingName, dataRow["ValueDate"]);
break;
} }
else }
catch (InvalidCastException castEx)
{
Logging.Log(Logging.LogType.Warning, "Settings", "Exception when reading server setting " + SettingName + ". Resetting to default.", castEx);
// delete broken setting and return the default
// this error is probably generated during an upgrade
sql = "DELETE FROM Settings WHERE Setting = @SettingName";
Dictionary<string, object> dbDict = new Dictionary<string, object>
{ {
AppSettings.Add(SettingName, (string)dbResponse.Rows[0][0]); { "SettingName", SettingName }
return (string)dbResponse.Rows[0][0]; };
}
} }
catch (Exception ex) catch (Exception ex)
{ {
Logging.Log(Logging.LogType.Critical, "Database", "Failed reading setting " + SettingName, ex); Logging.Log(Logging.LogType.Critical, "Settings", "Exception when reading server setting " + SettingName + ".", ex);
throw;
} }
} }
} }
public static void SetSetting(string SettingName, string Value) public static T ReadSetting<T>(string SettingName, T DefaultValue)
{ {
Database db = new Database(Database.databaseType.MySql, Config.DatabaseConfiguration.ConnectionString); Database db = new Database(Database.databaseType.MySql, Config.DatabaseConfiguration.ConnectionString);
string sql = "REPLACE INTO Settings (Setting, Value) VALUES (@SettingName, @Value)"; try
Dictionary<string, object> dbDict = new Dictionary<string, object>(); {
dbDict.Add("SettingName", SettingName); if (AppSettings.ContainsKey(SettingName))
dbDict.Add("Value", Value); {
return (T)AppSettings[SettingName];
}
else
{
string sql = "SELECT Value, ValueDate FROM Settings WHERE Setting = @SettingName";
Dictionary<string, object> dbDict = new Dictionary<string, object>
{
{ "SettingName", SettingName }
};
try
{
Logging.Log(Logging.LogType.Debug, "Database", "Reading setting '" + SettingName + "'");
DataTable dbResponse = db.ExecuteCMD(sql, dbDict);
Type type = typeof(T);
if (dbResponse.Rows.Count == 0)
{
// no value with that name stored - respond with the default value
SetSetting<T>(SettingName, DefaultValue);
return DefaultValue;
}
else
{
if (type.ToString() == "System.DateTime")
{
AppSettings.Add(SettingName, dbResponse.Rows[0]["ValueDate"]);
return (T)dbResponse.Rows[0]["ValueDate"];
}
else
{
AppSettings.Add(SettingName, dbResponse.Rows[0]["Value"]);
return (T)dbResponse.Rows[0]["Value"];
}
}
}
catch (Exception ex)
{
Logging.Log(Logging.LogType.Critical, "Database", "Failed reading setting " + SettingName, ex);
throw;
}
}
}
catch (InvalidCastException castEx)
{
Logging.Log(Logging.LogType.Warning, "Settings", "Exception when reading server setting " + SettingName + ". Resetting to default.", castEx);
// delete broken setting and return the default
// this error is probably generated during an upgrade
if (AppSettings.ContainsKey(SettingName))
{
AppSettings.Remove(SettingName);
}
string sql = "DELETE FROM Settings WHERE Setting = @SettingName";
Dictionary<string, object> dbDict = new Dictionary<string, object>
{
{ "SettingName", SettingName }
};
return DefaultValue;
}
catch (Exception ex)
{
Logging.Log(Logging.LogType.Critical, "Settings", "Exception when reading server setting " + SettingName + ".", ex);
throw;
}
}
public static void SetSetting<T>(string SettingName, T Value)
{
Database db = new Database(Database.databaseType.MySql, Config.DatabaseConfiguration.ConnectionString);
string sql = "REPLACE INTO Settings (Setting, ValueType, Value, ValueDate) VALUES (@SettingName, @ValueType, @Value, @ValueDate)";
Dictionary<string, object> dbDict;
Type type = typeof(T);
if (type.ToString() == "System.DateTime")
{
dbDict = new Dictionary<string, object>
{
{ "SettingName", SettingName },
{ "ValueType", 1 },
{ "Value", null },
{ "ValueDate", Value }
};
}
else
{
dbDict = new Dictionary<string, object>
{
{ "SettingName", SettingName },
{ "ValueType", 0 },
{ "Value", Value },
{ "ValueDate", null }
};
}
Logging.Log(Logging.LogType.Debug, "Database", "Storing setting '" + SettingName + "' to value: '" + Value + "'"); Logging.Log(Logging.LogType.Debug, "Database", "Storing setting '" + SettingName + "' to value: '" + Value + "'");
try try
@@ -308,11 +421,41 @@ namespace gaseous_server.Classes
} }
} }
private static string _DefaultDatabaseName
{
get
{
if (!String.IsNullOrEmpty(Environment.GetEnvironmentVariable("dbname")))
{
return Environment.GetEnvironmentVariable("dbname");
}
else
{
return "gaseous";
}
}
}
private static int _DefaultDatabasePort
{
get
{
if (!String.IsNullOrEmpty(Environment.GetEnvironmentVariable("dbport")))
{
return int.Parse(Environment.GetEnvironmentVariable("dbport"));
}
else
{
return 3306;
}
}
}
public string HostName = _DefaultHostName; public string HostName = _DefaultHostName;
public string UserName = _DefaultUserName; public string UserName = _DefaultUserName;
public string Password = _DefaultPassword; public string Password = _DefaultPassword;
public string DatabaseName = "gaseous"; public string DatabaseName = _DefaultDatabaseName;
public int Port = 3306; public int Port = _DefaultDatabasePort;
[JsonIgnore] [JsonIgnore]
public string ConnectionString public string ConnectionString
@@ -341,11 +484,11 @@ namespace gaseous_server.Classes
{ {
get get
{ {
return ReadSetting("LibraryRootDirectory", Path.Combine(Config.ConfigurationPath, "Data")); return ReadSetting<string>("LibraryRootDirectory", Path.Combine(Config.ConfigurationPath, "Data"));
} }
set set
{ {
SetSetting("LibraryRootDirectory", value); SetSetting<string>("LibraryRootDirectory", value);
} }
} }
@@ -507,7 +650,14 @@ namespace gaseous_server.Classes
{ {
get get
{ {
return 4; if (!String.IsNullOrEmpty(Environment.GetEnvironmentVariable("maxlibraryscanworkers")))
{
return int.Parse(Environment.GetEnvironmentVariable("maxlibraryscanworkers"));
}
else
{
return 4;
}
} }
} }
@@ -515,9 +665,9 @@ namespace gaseous_server.Classes
{ {
get get
{ {
if (!String.IsNullOrEmpty(Environment.GetEnvironmentVariable("hasheoushoust"))) if (!String.IsNullOrEmpty(Environment.GetEnvironmentVariable("hasheoushost")))
{ {
return Environment.GetEnvironmentVariable("hasheoushoust"); return Environment.GetEnvironmentVariable("hasheoushost");
} }
else else
{ {

View File

@@ -1,5 +1,6 @@
using System; using System;
using System.Data; using System.Data;
using System.Reflection;
namespace gaseous_server.Classes namespace gaseous_server.Classes
{ {
@@ -9,13 +10,64 @@ namespace gaseous_server.Classes
public static void PreUpgradeScript(int TargetSchemaVersion, Database.databaseType? DatabaseType) public static void PreUpgradeScript(int TargetSchemaVersion, Database.databaseType? DatabaseType)
{ {
// load resources
var assembly = Assembly.GetExecutingAssembly();
Database db = new Database(Database.databaseType.MySql, Config.DatabaseConfiguration.ConnectionString);
string sql = "";
Dictionary<string, object> dbDict = new Dictionary<string, object>();
DataTable data;
Logging.Log(Logging.LogType.Information, "Database", "Checking for pre-upgrade for schema version " + TargetSchemaVersion);
switch(DatabaseType)
{
case Database.databaseType.MySql:
switch (TargetSchemaVersion)
{
case 1005:
Logging.Log(Logging.LogType.Information, "Database", "Running pre-upgrade for schema version " + TargetSchemaVersion);
// there was a mistake at dbschema version 1004-1005
// the first preview release of v1.7 reused dbschema version 1004
// if table "Relation_Game_AgeRatings" exists - then we need to apply the gaseous-fix-1005.sql script before applying the standard 1005 script
sql = "SELECT table_name FROM information_schema.tables WHERE table_schema = @dbname AND table_name = @tablename;";
dbDict.Add("dbname", Config.DatabaseConfiguration.DatabaseName);
dbDict.Add("tablename", "Relation_Game_AgeRatings");
data = db.ExecuteCMD(sql, dbDict);
if (data.Rows.Count == 0)
{
Logging.Log(Logging.LogType.Information, "Database", "Schema version " + TargetSchemaVersion + " requires a table which is missing.");
string resourceName = "gaseous_server.Support.Database.MySQL.gaseous-fix-1005.sql";
string dbScript = "";
string[] resources = Assembly.GetExecutingAssembly().GetManifestResourceNames();
if (resources.Contains(resourceName))
{
using (Stream stream = assembly.GetManifestResourceStream(resourceName))
using (StreamReader reader = new StreamReader(stream))
{
dbScript = reader.ReadToEnd();
// apply schema!
Logging.Log(Logging.LogType.Information, "Database", "Applying schema version fix prior to version 1005");
db.ExecuteCMD(dbScript, dbDict, 180);
}
}
}
break;
}
break;
}
} }
public static void PostUpgradeScript(int TargetSchemaVersion, Database.databaseType? DatabaseType) public static void PostUpgradeScript(int TargetSchemaVersion, Database.databaseType? DatabaseType)
{ {
Database db = new Database(Database.databaseType.MySql, Config.DatabaseConfiguration.ConnectionString); Database db = new Database(Database.databaseType.MySql, Config.DatabaseConfiguration.ConnectionString);
string sql = "";
Dictionary<string, object> dbDict = new Dictionary<string, object>(); Dictionary<string, object> dbDict = new Dictionary<string, object>();
DataTable data;
switch(DatabaseType) switch(DatabaseType)
{ {
@@ -32,12 +84,12 @@ namespace gaseous_server.Classes
// copy root path to new libraries format // copy root path to new libraries format
string oldRoot = Path.Combine(Config.LibraryConfiguration.LibraryRootDirectory, "Library"); string oldRoot = Path.Combine(Config.LibraryConfiguration.LibraryRootDirectory, "Library");
string sql = "INSERT INTO GameLibraries (Name, Path, DefaultLibrary, DefaultPlatform) VALUES (@name, @path, @defaultlibrary, @defaultplatform); SELECT CAST(LAST_INSERT_ID() AS SIGNED);"; sql = "INSERT INTO GameLibraries (Name, Path, DefaultLibrary, DefaultPlatform) VALUES (@name, @path, @defaultlibrary, @defaultplatform); SELECT CAST(LAST_INSERT_ID() AS SIGNED);";
dbDict.Add("name", "Default"); dbDict.Add("name", "Default");
dbDict.Add("path", oldRoot); dbDict.Add("path", oldRoot);
dbDict.Add("defaultlibrary", 1); dbDict.Add("defaultlibrary", 1);
dbDict.Add("defaultplatform", 0); dbDict.Add("defaultplatform", 0);
DataTable data = db.ExecuteCMD(sql, dbDict); data = db.ExecuteCMD(sql, dbDict);
// apply the new library id to the existing roms // apply the new library id to the existing roms
sql = "UPDATE Games_Roms SET LibraryId=@libraryid;"; sql = "UPDATE Games_Roms SET LibraryId=@libraryid;";
@@ -46,6 +98,11 @@ namespace gaseous_server.Classes
db.ExecuteCMD(sql, dbDict); db.ExecuteCMD(sql, dbDict);
break; break;
case 1016:
// delete old format LastRun_* settings from settings table
sql = "DELETE FROM Settings WHERE Setting LIKE 'LastRun_%';";
db.ExecuteNonQuery(sql);
break;
} }
break; break;
} }

View File

@@ -0,0 +1,58 @@
using IGDB.Models;
namespace gaseous_server.Classes
{
public class Favourites
{
public bool GetFavourite(string userid, long GameId)
{
Database db = new Database(Database.databaseType.MySql, Config.DatabaseConfiguration.ConnectionString);
string sql = "SELECT * FROM Favourites WHERE UserId=@userid AND GameId=@gameid";
Dictionary<string, object> dbDict = new Dictionary<string, object>{
{ "userid", userid },
{ "gameid", GameId}
};
if (db.ExecuteCMD(sql, dbDict).Rows.Count > 0)
{
return true;
}
else
{
return false;
}
}
public bool SetFavourite(string userid, long GameId, bool Favourite)
{
bool CurrentFavourite = GetFavourite(userid, GameId);
if (CurrentFavourite == Favourite)
{
return Favourite;
}
else
{
Database db = new Database(Database.databaseType.MySql, Config.DatabaseConfiguration.ConnectionString);
string sql;
Dictionary<string, object> dbDict = new Dictionary<string, object>{
{ "userid", userid },
{ "gameid", GameId}
};
if (CurrentFavourite == true)
{
// delete existing value
sql = "DELETE FROM Favourites WHERE UserId=@userid AND GameId=@gameid";
}
else
{
// insert new value
sql = "INSERT INTO Favourites (UserId, GameId) VALUES (@userid, @gameid)";
}
db.ExecuteNonQuery(sql, dbDict);
return Favourite;
}
}
}
}

View File

@@ -452,13 +452,15 @@ namespace gaseous_server.Classes
if (romDT.Rows.Count > 0) if (romDT.Rows.Count > 0)
{ {
foreach (DataRow dr in romDT.Rows) for (int i = 0; i < romDT.Rows.Count; i++)
{ {
Logging.Log(Logging.LogType.Information, "Organise Library", "Processing ROM " + dr["name"]); SetStatus(i, romDT.Rows.Count, "Processing file " + romDT.Rows[i]["name"]);
long RomId = (long)dr["id"]; Logging.Log(Logging.LogType.Information, "Organise Library", "(" + i + "/" + romDT.Rows.Count + ") Processing ROM " + romDT.Rows[i]["name"]);
long RomId = (long)romDT.Rows[i]["id"];
MoveGameFile(RomId); MoveGameFile(RomId);
} }
} }
ClearStatus();
// clean up empty directories // clean up empty directories
DeleteOrphanedDirectories(GameLibrary.GetDefaultLibrary.Path); DeleteOrphanedDirectories(GameLibrary.GetDefaultLibrary.Path);

View File

@@ -9,7 +9,7 @@ namespace gaseous_server.Classes
{ {
const int MaxFileAge = 30; const int MaxFileAge = 30;
public void RunMaintenance() public void RunDailyMaintenance()
{ {
Database db = new Database(Database.databaseType.MySql, Config.DatabaseConfiguration.ConnectionString); Database db = new Database(Database.databaseType.MySql, Config.DatabaseConfiguration.ConnectionString);
string sql = ""; string sql = "";
@@ -33,8 +33,8 @@ namespace gaseous_server.Classes
} }
// delete old logs // delete old logs
sql = "DELETE FROM ServerLogs WHERE EventTime < @EventRententionDate;"; sql = "DELETE FROM ServerLogs WHERE EventTime < @EventRetentionDate;";
dbDict.Add("EventRententionDate", DateTime.UtcNow.AddDays(Config.LoggingConfiguration.LogRetention * -1)); dbDict.Add("EventRetentionDate", DateTime.UtcNow.AddDays(Config.LoggingConfiguration.LogRetention * -1));
db.ExecuteCMD(sql, dbDict); db.ExecuteCMD(sql, dbDict);
// delete files and directories older than 7 days in PathsToClean // delete files and directories older than 7 days in PathsToClean
@@ -69,6 +69,13 @@ namespace gaseous_server.Classes
} }
} }
} }
}
public void RunWeeklyMaintenance()
{
Database db = new Database(Database.databaseType.MySql, Config.DatabaseConfiguration.ConnectionString);
string sql = "";
Dictionary<string, object> dbDict = new Dictionary<string, object>();
Logging.Log(Logging.LogType.Information, "Maintenance", "Optimising database tables"); Logging.Log(Logging.LogType.Information, "Maintenance", "Optimising database tables");
sql = "SHOW FULL TABLES WHERE Table_Type = 'BASE TABLE';"; sql = "SHOW FULL TABLES WHERE Table_Type = 'BASE TABLE';";

View File

@@ -72,32 +72,7 @@ namespace gaseous_server.Classes.Metadata
} }
// compile the ratings values into the ratings groups // compile the ratings values into the ratings groups
AgeRestrictionGroupings highestAgeGroup = AgeRestrictionGroupings.Unclassified; AgeRestrictionGroupings highestAgeGroup = GetAgeGroupFromAgeRatings(ageRatings);
foreach (AgeRating ageRating in ageRatings)
{
foreach (KeyValuePair<AgeRestrictionGroupings, AgeGroupItem> ageGroupItem in AgeGroupingsFlat)
{
PropertyInfo[] groupProps = typeof(AgeGroupItem).GetProperties();
foreach (PropertyInfo property in groupProps)
{
if (RatingsBoards.Contains(property.Name))
{
List<AgeRatingTitle> ratingBoard = (List<AgeRatingTitle>)property.GetValue(ageGroupItem.Value);
foreach (AgeRatingTitle ratingTitle in ratingBoard)
{
if (ageRating.Rating == ratingTitle)
{
if (highestAgeGroup < ageGroupItem.Key)
{
highestAgeGroup = ageGroupItem.Key;
}
}
}
}
}
}
}
// return the compiled ratings group // return the compiled ratings group
AgeGroup ageGroup = new AgeGroup(); AgeGroup ageGroup = new AgeGroup();
@@ -138,6 +113,39 @@ namespace gaseous_server.Classes.Metadata
return null; return null;
} }
public static AgeRestrictionGroupings GetAgeGroupFromAgeRatings(List<AgeRating> ageRatings)
{
// compile the ratings values into the ratings groups
AgeRestrictionGroupings highestAgeGroup = AgeRestrictionGroupings.Unclassified;
foreach (AgeRating ageRating in ageRatings)
{
foreach (KeyValuePair<AgeRestrictionGroupings, AgeGroupItem> ageGroupItem in AgeGroupingsFlat)
{
PropertyInfo[] groupProps = typeof(AgeGroupItem).GetProperties();
foreach (PropertyInfo property in groupProps)
{
if (RatingsBoards.Contains(property.Name))
{
List<AgeRatingTitle> ratingBoard = (List<AgeRatingTitle>)property.GetValue(ageGroupItem.Value);
foreach (AgeRatingTitle ratingTitle in ratingBoard)
{
if (ageRating.Rating == ratingTitle)
{
if (highestAgeGroup < ageGroupItem.Key)
{
highestAgeGroup = ageGroupItem.Key;
}
}
}
}
}
}
}
return highestAgeGroup;
}
public class AgeGroup public class AgeGroup
{ {
public long? Id { get; set; } public long? Id { get; set; }

View File

@@ -7,7 +7,7 @@ namespace gaseous_server.Classes.Metadata
{ {
public class Collections public class Collections
{ {
const string fieldList = "fields checksum,created_at,games,name,slug,updated_at,url;"; const string fieldList = "fields as_child_relations,as_parent_relations,checksum,created_at,games,name,slug,type,updated_at,url;";
public Collections() public Collections()
{ {

View File

@@ -224,6 +224,22 @@ namespace gaseous_server.Classes.Metadata
return await IGDBAPI<T>(Endpoint, Fields, Query); return await IGDBAPI<T>(Endpoint, Fields, Query);
} }
case HttpStatusCode.Unauthorized:
Logging.Log(Logging.LogType.Information, "API Connection", "IGDB API unauthorised error while accessing endpoint " + Endpoint + ". Waiting " + RateLimitAvoidanceWait + " milliseconds and resetting IGDB client.", apiEx);
Thread.Sleep(RateLimitAvoidanceWait);
igdb = new IGDBClient(
// Found in Twitch Developer portal for your app
Config.IGDB.ClientId,
Config.IGDB.Secret
);
RetryAttempts += 1;
return await IGDBAPI<T>(Endpoint, Fields, Query);
default: default:
Logging.Log(Logging.LogType.Warning, "API Connection", "Exception when accessing endpoint " + Endpoint, apiEx); Logging.Log(Logging.LogType.Warning, "API Connection", "Exception when accessing endpoint " + Endpoint, apiEx);
throw; throw;

View File

@@ -9,7 +9,7 @@ namespace gaseous_server.Classes.Metadata
{ {
public class Covers public class Covers
{ {
const string fieldList = "fields alpha_channel,animated,checksum,game,height,image_id,url,width;"; const string fieldList = "fields alpha_channel,animated,checksum,game,game_localization,height,image_id,url,width;";
public Covers() public Covers()
{ {

View File

@@ -7,7 +7,7 @@ namespace gaseous_server.Classes.Metadata
{ {
public class Games public class Games
{ {
const string fieldList = "fields age_ratings,aggregated_rating,aggregated_rating_count,alternative_names,artworks,bundles,category,checksum,collection,cover,created_at,dlcs,expanded_games,expansions,external_games,first_release_date,follows,forks,franchise,franchises,game_engines,game_localizations,game_modes,genres,hypes,involved_companies,keywords,language_supports,multiplayer_modes,name,parent_game,platforms,player_perspectives,ports,rating,rating_count,release_dates,remakes,remasters,screenshots,similar_games,slug,standalone_expansions,status,storyline,summary,tags,themes,total_rating,total_rating_count,updated_at,url,version_parent,version_title,videos,websites;"; const string fieldList = "fields age_ratings,aggregated_rating,aggregated_rating_count,alternative_names,artworks,bundles,category,checksum,collection,collections,cover,created_at,dlcs,expanded_games,expansions,external_games,first_release_date,follows,forks,franchise,franchises,game_engines,game_localizations,game_modes,genres,hypes,involved_companies,keywords,language_supports,multiplayer_modes,name,parent_game,platforms,player_perspectives,ports,rating,rating_count,release_dates,remakes,remasters,screenshots,similar_games,slug,standalone_expansions,status,storyline,summary,tags,themes,total_rating,total_rating_count,updated_at,url,version_parent,version_title,videos,websites;";
public Games() public Games()
{ {
@@ -538,6 +538,7 @@ namespace gaseous_server.Classes.Metadata
{ {
this.Id = gameObject.Id; this.Id = gameObject.Id;
this.Name = gameObject.Name; this.Name = gameObject.Name;
this.Slug = gameObject.Slug;
this.TotalRating = gameObject.TotalRating; this.TotalRating = gameObject.TotalRating;
this.TotalRatingCount = gameObject.TotalRatingCount; this.TotalRatingCount = gameObject.TotalRatingCount;
this.Cover = gameObject.Cover; this.Cover = gameObject.Cover;
@@ -561,9 +562,11 @@ namespace gaseous_server.Classes.Metadata
public long? Id { get; set; } public long? Id { get; set; }
public string Name { get; set; } public string Name { get; set; }
public string Slug { get; set; }
public double? TotalRating { get; set; } public double? TotalRating { get; set; }
public int? TotalRatingCount { get; set; } public int? TotalRatingCount { get; set; }
public bool HasSavedGame { get; set; } = false; public bool HasSavedGame { get; set; } = false;
public bool IsFavourite { get; set; } = false;
public DateTimeOffset? FirstReleaseDate { get; set; } public DateTimeOffset? FirstReleaseDate { get; set; }
public IGDB.IdentityOrValue<IGDB.Models.Cover> Cover { get; set; } public IGDB.IdentityOrValue<IGDB.Models.Cover> Cover { get; set; }
public IGDB.IdentitiesOrValues<IGDB.Models.Artwork> Artworks { get; set; } public IGDB.IdentitiesOrValues<IGDB.Models.Artwork> Artworks { get; set; }

View File

@@ -0,0 +1,99 @@
using System.Data;
using gaseous_server.Models;
namespace gaseous_server.Classes
{
public class Statistics
{
public StatisticsModel RecordSession(Guid SessionId, long GameId, string UserId)
{
Database db = new Database(Database.databaseType.MySql, Config.DatabaseConfiguration.ConnectionString);
string sql;
Dictionary<string, object> dbDict;
if (SessionId == Guid.Empty)
{
// new session required
SessionId = Guid.NewGuid();
sql = "INSERT INTO UserTimeTracking (GameId, UserId, SessionId, SessionTime, SessionLength) VALUES (@gameid, @userid, @sessionid, @sessiontime, @sessionlength);";
dbDict = new Dictionary<string, object>{
{ "gameid", GameId },
{ "userid", UserId },
{ "sessionid", SessionId },
{ "sessiontime", DateTime.UtcNow },
{ "sessionlength", 1 }
};
db.ExecuteNonQuery(sql, dbDict);
return new StatisticsModel{
GameId = GameId,
SessionId = SessionId,
SessionStart = (DateTime)dbDict["sessiontime"],
SessionLength = (int)dbDict["sessionlength"]
};
}
else
{
// update existing session
sql = "UPDATE UserTimeTracking SET SessionLength = SessionLength + @sessionlength WHERE GameId = @gameid AND UserId = @userid AND SessionId = @sessionid;";
dbDict = new Dictionary<string, object>{
{ "gameid", GameId },
{ "userid", UserId },
{ "sessionid", SessionId },
{ "sessionlength", 1 }
};
db.ExecuteNonQuery(sql, dbDict);
sql = "SELECT * FROM UserTimeTracking WHERE GameId = @gameid AND UserId = @userid AND SessionId = @sessionid;";
DataTable data = db.ExecuteCMD(sql, dbDict);
return new StatisticsModel{
GameId = (long)data.Rows[0]["GameId"],
SessionId = Guid.Parse(data.Rows[0]["SessionId"].ToString()),
SessionStart = (DateTime)data.Rows[0]["SessionTime"],
SessionLength = (int)data.Rows[0]["SessionLength"]
};
}
}
public StatisticsModel? GetSession(long GameId, string UserId)
{
Database db = new Database(Database.databaseType.MySql, Config.DatabaseConfiguration.ConnectionString);
string sql = "SELECT SUM(SessionLength) AS TotalLength FROM UserTimeTracking WHERE GameId = @gameid AND UserId = @userid;";
Dictionary<string, object> dbDict = new Dictionary<string, object>{
{ "gameid", GameId },
{ "userid", UserId }
};
DataTable data = db.ExecuteCMD(sql, dbDict);
if (data.Rows.Count == 0)
{
return null;
}
else
{
if (data.Rows[0]["TotalLength"] == DBNull.Value)
{
return null;
}
else
{
int TotalTime = int.Parse(data.Rows[0]["TotalLength"].ToString());
sql = "SELECT * FROM UserTimeTracking WHERE GameId = @gameid AND UserId = @userid ORDER BY SessionTime DESC LIMIT 1;";
data = db.ExecuteCMD(sql, dbDict);
return new StatisticsModel{
GameId = GameId,
SessionLength = TotalTime,
SessionStart = (DateTime)data.Rows[0]["SessionTime"]
};
}
}
}
}
}

View File

@@ -8,6 +8,8 @@ using Microsoft.AspNetCore.Identity;
using Microsoft.AspNetCore.Identity.UI.Services; using Microsoft.AspNetCore.Identity.UI.Services;
using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.Rendering; using Microsoft.AspNetCore.Mvc.Rendering;
using Asp.Versioning;
using IGDB;
namespace gaseous_server.Controllers namespace gaseous_server.Controllers
{ {
@@ -97,6 +99,7 @@ namespace gaseous_server.Controllers
profile.Roles = new List<string>(await _userManager.GetRolesAsync(user)); profile.Roles = new List<string>(await _userManager.GetRolesAsync(user));
profile.SecurityProfile = user.SecurityProfile; profile.SecurityProfile = user.SecurityProfile;
profile.UserPreferences = user.UserPreferences; profile.UserPreferences = user.UserPreferences;
profile.Avatar = user.Avatar;
profile.Roles.Sort(); profile.Roles.Sort();
return Ok(profile); return Ok(profile);
@@ -185,6 +188,7 @@ namespace gaseous_server.Controllers
user.LockoutEnabled = rawUser.LockoutEnabled; user.LockoutEnabled = rawUser.LockoutEnabled;
user.LockoutEnd = rawUser.LockoutEnd; user.LockoutEnd = rawUser.LockoutEnd;
user.SecurityProfile = rawUser.SecurityProfile; user.SecurityProfile = rawUser.SecurityProfile;
user.Avatar = rawUser.Avatar;
// get roles // get roles
ApplicationUser? aUser = await _userManager.FindByIdAsync(rawUser.Id); ApplicationUser? aUser = await _userManager.FindByIdAsync(rawUser.Id);
@@ -413,5 +417,115 @@ namespace gaseous_server.Controllers
return Ok(); return Ok();
} }
} }
[HttpPost]
[ProducesResponseType(StatusCodes.Status200OK)]
[RequestSizeLimit(long.MaxValue)]
[Consumes("multipart/form-data")]
[DisableRequestSizeLimit, RequestFormLimits(MultipartBodyLengthLimit = long.MaxValue, ValueLengthLimit = int.MaxValue)]
[Route("Avatar")]
public async Task<IActionResult> UploadAvatar(IFormFile file)
{
ApplicationUser? user = await _userManager.GetUserAsync(User);
if (user == null)
{
return Unauthorized();
}
else
{
Guid avatarId = Guid.Empty;
if (file.Length > 0)
{
using (var ms = new MemoryStream())
{
file.CopyTo(ms);
byte[] fileBytes = ms.ToArray();
byte[] targetBytes;
using (var image = new ImageMagick.MagickImage(fileBytes))
{
ImageMagick.MagickGeometry size = new ImageMagick.MagickGeometry(256, 256);
// This will resize the image to a fixed size without maintaining the aspect ratio.
// Normally an image will be resized to fit inside the specified size.
size.IgnoreAspectRatio = true;
image.Resize(size);
var newMs = new MemoryStream();
image.Resize(size);
image.Strip();
image.Write(newMs, ImageMagick.MagickFormat.Jpg);
targetBytes = newMs.ToArray();
}
Database db = new Database(Database.databaseType.MySql, Config.DatabaseConfiguration.ConnectionString);
UserTable<ApplicationUser> userTable = new UserTable<ApplicationUser>(db);
avatarId = userTable.SetAvatar(user, targetBytes);
}
}
return Ok(avatarId);
}
}
[HttpGet]
[Route("Avatar/{id}.jpg")]
[ProducesResponseType(typeof(FileStreamResult), StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status404NotFound)]
public ActionResult GetAvatar(Guid id)
{
if (id == Guid.Empty)
{
return NotFound();
}
else
{
Database db = new Database(Database.databaseType.MySql, Config.DatabaseConfiguration.ConnectionString);
string sql = "SELECT * FROM UserAvatars WHERE Id = @id";
Dictionary<string, object> dbDict = new Dictionary<string, object>{
{ "id", id }
};
DataTable data = db.ExecuteCMD(sql, dbDict);
if (data.Rows.Count > 0)
{
string filename = id.ToString() + ".jpg";
byte[] filedata = (byte[])data.Rows[0]["Avatar"];
string contentType = "image/jpg";
var cd = new System.Net.Mime.ContentDisposition
{
FileName = filename,
Inline = true,
};
Response.Headers.Add("Content-Disposition", cd.ToString());
Response.Headers.Add("Cache-Control", "public, max-age=604800");
return File(filedata, contentType);
}
else
{
return NotFound();
}
}
}
[HttpDelete]
[Route("Avatar/{id}.jpg")]
[ProducesResponseType(StatusCodes.Status200OK)]
public async Task<ActionResult> DeleteAvatarAsync()
{
ApplicationUser? user = await _userManager.GetUserAsync(User);
Database db = new Database(Database.databaseType.MySql, Config.DatabaseConfiguration.ConnectionString);
UserTable<ApplicationUser> userTable = new UserTable<ApplicationUser>(db);
userTable.SetAvatar(user, new byte[0]);
return Ok();
}
} }
} }

View File

@@ -4,6 +4,7 @@ using System.Linq;
using System.Threading.Tasks; using System.Threading.Tasks;
using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc;
using Asp.Versioning;
namespace gaseous_server.Controllers namespace gaseous_server.Controllers
{ {

View File

@@ -6,6 +6,7 @@ using System.Threading.Tasks;
using gaseous_server.Classes; using gaseous_server.Classes;
using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc;
using Asp.Versioning;
namespace gaseous_server.Controllers namespace gaseous_server.Controllers
{ {

View File

@@ -3,9 +3,12 @@ using System.Collections.Generic;
using System.IO.Compression; using System.IO.Compression;
using System.Linq; using System.Linq;
using System.Threading.Tasks; using System.Threading.Tasks;
using Authentication;
using gaseous_server.Classes; using gaseous_server.Classes;
using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Identity;
using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc;
using Asp.Versioning;
namespace gaseous_server.Controllers namespace gaseous_server.Controllers
{ {
@@ -16,6 +19,17 @@ namespace gaseous_server.Controllers
[Authorize] [Authorize]
public class CollectionsController : Controller public class CollectionsController : Controller
{ {
private readonly UserManager<ApplicationUser> _userManager;
private readonly SignInManager<ApplicationUser> _signInManager;
public CollectionsController(
UserManager<ApplicationUser> userManager,
SignInManager<ApplicationUser> signInManager)
{
_userManager = userManager;
_signInManager = signInManager;
}
/// <summary> /// <summary>
/// Gets all ROM collections /// Gets all ROM collections
/// </summary> /// </summary>
@@ -24,9 +38,16 @@ namespace gaseous_server.Controllers
[MapToApiVersion("1.1")] [MapToApiVersion("1.1")]
[HttpGet] [HttpGet]
[ProducesResponseType(StatusCodes.Status200OK)] [ProducesResponseType(StatusCodes.Status200OK)]
public List<Classes.Collections.CollectionItem> GetCollections() public async Task<ActionResult> GetCollectionsAsync()
{ {
return Classes.Collections.GetCollections(); var user = await _userManager.GetUserAsync(User);
if (user != null)
{
return Ok(Classes.Collections.GetCollections(user.Id));
}
return NotFound();
} }
/// <summary> /// <summary>
@@ -41,18 +62,27 @@ namespace gaseous_server.Controllers
[Route("{CollectionId}")] [Route("{CollectionId}")]
[ProducesResponseType(typeof(Classes.Collections.CollectionItem), StatusCodes.Status200OK)] [ProducesResponseType(typeof(Classes.Collections.CollectionItem), StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status404NotFound)] [ProducesResponseType(StatusCodes.Status404NotFound)]
public ActionResult GetCollection(long CollectionId, bool Build = false) public async Task<ActionResult> GetCollection(long CollectionId, bool Build = false)
{ {
try var user = await _userManager.GetUserAsync(User);
{
if (Build == true)
{
Classes.Collections.StartCollectionItemBuild(CollectionId);
}
return Ok(Classes.Collections.GetCollection(CollectionId)); if (user != null)
{
try
{
if (Build == true)
{
Classes.Collections.StartCollectionItemBuild(CollectionId, user.Id);
}
return Ok(Classes.Collections.GetCollection(CollectionId, user.Id));
}
catch
{
return NotFound();
}
} }
catch else
{ {
return NotFound(); return NotFound();
} }
@@ -69,14 +99,23 @@ namespace gaseous_server.Controllers
[Route("{CollectionId}/Roms")] [Route("{CollectionId}/Roms")]
[ProducesResponseType(typeof(List<Classes.Collections.CollectionContents.CollectionPlatformItem>), StatusCodes.Status200OK)] [ProducesResponseType(typeof(List<Classes.Collections.CollectionContents.CollectionPlatformItem>), StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status404NotFound)] [ProducesResponseType(StatusCodes.Status404NotFound)]
public ActionResult GetCollectionRoms(long CollectionId) public async Task<ActionResult> GetCollectionRoms(long CollectionId)
{ {
try var user = await _userManager.GetUserAsync(User);
if (user != null)
{ {
Classes.Collections.CollectionItem collectionItem = Classes.Collections.GetCollection(CollectionId); try
return Ok(Classes.Collections.GetCollectionContent(collectionItem)); {
Classes.Collections.CollectionItem collectionItem = Classes.Collections.GetCollection(CollectionId, user.Id);
return Ok(Classes.Collections.GetCollectionContent(collectionItem, user.Id));
}
catch
{
return NotFound();
}
} }
catch else
{ {
return NotFound(); return NotFound();
} }
@@ -94,15 +133,24 @@ namespace gaseous_server.Controllers
[Authorize(Roles = "Admin,Gamer")] [Authorize(Roles = "Admin,Gamer")]
[ProducesResponseType(typeof(List<Classes.Collections.CollectionContents.CollectionPlatformItem>), StatusCodes.Status200OK)] [ProducesResponseType(typeof(List<Classes.Collections.CollectionContents.CollectionPlatformItem>), StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status404NotFound)] [ProducesResponseType(StatusCodes.Status404NotFound)]
public ActionResult GetCollectionRomsPreview(Classes.Collections.CollectionItem Item) public async Task<ActionResult> GetCollectionRomsPreview(Classes.Collections.CollectionItem Item)
{ {
try var user = await _userManager.GetUserAsync(User);
if (user != null)
{ {
return Ok(Classes.Collections.GetCollectionContent(Item)); try
{
return Ok(Classes.Collections.GetCollectionContent(Item, user.Id));
}
catch (Exception ex)
{
return NotFound(ex);
}
} }
catch (Exception ex) else
{ {
return NotFound(ex); return NotFound();
} }
} }
@@ -117,25 +165,34 @@ namespace gaseous_server.Controllers
[Route("{CollectionId}/Roms/Zip")] [Route("{CollectionId}/Roms/Zip")]
[ProducesResponseType(typeof(FileStreamResult), StatusCodes.Status200OK)] [ProducesResponseType(typeof(FileStreamResult), StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status404NotFound)] [ProducesResponseType(StatusCodes.Status404NotFound)]
public ActionResult GetCollectionRomsZip(long CollectionId) public async Task<ActionResult> GetCollectionRomsZip(long CollectionId)
{ {
try var user = await _userManager.GetUserAsync(User);
if (user != null)
{ {
Classes.Collections.CollectionItem collectionItem = Classes.Collections.GetCollection(CollectionId); try
string ZipFilePath = Path.Combine(Config.LibraryConfiguration.LibraryCollectionsDirectory, CollectionId + ".zip");
if (System.IO.File.Exists(ZipFilePath))
{ {
var stream = new FileStream(ZipFilePath, FileMode.Open); Classes.Collections.CollectionItem collectionItem = Classes.Collections.GetCollection(CollectionId, user.Id);
return File(stream, "application/zip", collectionItem.Name + ".zip");
string ZipFilePath = Path.Combine(Config.LibraryConfiguration.LibraryCollectionsDirectory, CollectionId + ".zip");
if (System.IO.File.Exists(ZipFilePath))
{
var stream = new FileStream(ZipFilePath, FileMode.Open);
return File(stream, "application/zip", collectionItem.Name + ".zip");
}
else
{
return NotFound();
}
} }
else catch
{ {
return NotFound(); return NotFound();
} }
} }
catch else
{ {
return NotFound(); return NotFound();
} }
@@ -152,15 +209,24 @@ namespace gaseous_server.Controllers
[Authorize(Roles = "Admin,Gamer")] [Authorize(Roles = "Admin,Gamer")]
[ProducesResponseType(typeof(Classes.Collections.CollectionItem), StatusCodes.Status200OK)] [ProducesResponseType(typeof(Classes.Collections.CollectionItem), StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status400BadRequest)] [ProducesResponseType(StatusCodes.Status400BadRequest)]
public ActionResult NewCollection(Classes.Collections.CollectionItem Item) public async Task<ActionResult> NewCollectionAsync(Classes.Collections.CollectionItem Item)
{ {
try var user = await _userManager.GetUserAsync(User);
if (user != null)
{ {
return Ok(Classes.Collections.NewCollection(Item)); try
{
return Ok(Classes.Collections.NewCollection(Item, user.Id));
}
catch (Exception ex)
{
return BadRequest(ex);
}
} }
catch (Exception ex) else
{ {
return BadRequest(ex); return NotFound();
} }
} }
@@ -177,13 +243,22 @@ namespace gaseous_server.Controllers
[Authorize(Roles = "Admin,Gamer")] [Authorize(Roles = "Admin,Gamer")]
[ProducesResponseType(typeof(Classes.Collections.CollectionItem), StatusCodes.Status200OK)] [ProducesResponseType(typeof(Classes.Collections.CollectionItem), StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status404NotFound)] [ProducesResponseType(StatusCodes.Status404NotFound)]
public ActionResult EditCollection(long CollectionId, Classes.Collections.CollectionItem Item) public async Task<ActionResult> EditCollection(long CollectionId, Classes.Collections.CollectionItem Item)
{ {
try var user = await _userManager.GetUserAsync(User);
if (user != null)
{ {
return Ok(Classes.Collections.EditCollection(CollectionId, Item, true)); try
{
return Ok(Classes.Collections.EditCollection(CollectionId, Item, user.Id, true));
}
catch
{
return NotFound();
}
} }
catch else
{ {
return NotFound(); return NotFound();
} }
@@ -202,27 +277,36 @@ namespace gaseous_server.Controllers
[Route("{CollectionId}/AlwaysInclude")] [Route("{CollectionId}/AlwaysInclude")]
[ProducesResponseType(typeof(Classes.Collections.CollectionItem), StatusCodes.Status200OK)] [ProducesResponseType(typeof(Classes.Collections.CollectionItem), StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status404NotFound)] [ProducesResponseType(StatusCodes.Status404NotFound)]
public ActionResult EditCollectionAlwaysInclude(long CollectionId, [FromQuery]bool Rebuild, [FromBody]Collections.CollectionItem.AlwaysIncludeItem Inclusion) public async Task<ActionResult> EditCollectionAlwaysInclude(long CollectionId, [FromQuery]bool Rebuild, [FromBody]Collections.CollectionItem.AlwaysIncludeItem Inclusion)
{ {
try var user = await _userManager.GetUserAsync(User);
if (user != null)
{ {
Collections.CollectionItem collectionItem = Classes.Collections.GetCollection(CollectionId); try
bool ItemFound = false;
foreach (Collections.CollectionItem.AlwaysIncludeItem includeItem in collectionItem.AlwaysInclude)
{ {
if (includeItem.PlatformId == Inclusion.PlatformId && includeItem.GameId == Inclusion.GameId) Collections.CollectionItem collectionItem = Classes.Collections.GetCollection(CollectionId, user.Id);
bool ItemFound = false;
foreach (Collections.CollectionItem.AlwaysIncludeItem includeItem in collectionItem.AlwaysInclude)
{ {
ItemFound = true; if (includeItem.PlatformId == Inclusion.PlatformId && includeItem.GameId == Inclusion.GameId)
{
ItemFound = true;
}
}
if (ItemFound == false)
{
collectionItem.AlwaysInclude.Add(Inclusion);
} }
}
if (ItemFound == false)
{
collectionItem.AlwaysInclude.Add(Inclusion);
}
return Ok(Classes.Collections.EditCollection(CollectionId, collectionItem, Rebuild)); return Ok(Classes.Collections.EditCollection(CollectionId, collectionItem, user.Id, Rebuild));
}
catch
{
return NotFound();
}
} }
catch else
{ {
return NotFound(); return NotFound();
} }
@@ -239,14 +323,23 @@ namespace gaseous_server.Controllers
[Route("{CollectionId}")] [Route("{CollectionId}")]
[ProducesResponseType(StatusCodes.Status200OK)] [ProducesResponseType(StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status404NotFound)] [ProducesResponseType(StatusCodes.Status404NotFound)]
public ActionResult DeleteCollection(long CollectionId) public async Task<ActionResult> DeleteCollection(long CollectionId)
{ {
try var user = await _userManager.GetUserAsync(User);
if (user != null)
{ {
Classes.Collections.DeleteCollection(CollectionId); try
return Ok(); {
Classes.Collections.DeleteCollection(CollectionId, user.Id);
return Ok();
}
catch
{
return NotFound();
}
} }
catch else
{ {
return NotFound(); return NotFound();
} }

View File

@@ -10,6 +10,7 @@ using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Identity; using Microsoft.AspNetCore.Identity;
using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc;
using Asp.Versioning;
namespace gaseous_server.Controllers namespace gaseous_server.Controllers
{ {

View File

@@ -17,6 +17,7 @@ using Microsoft.AspNetCore.Identity;
using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc;
using Microsoft.CodeAnalysis.Scripting; using Microsoft.CodeAnalysis.Scripting;
using static gaseous_server.Classes.Metadata.AgeRatings; using static gaseous_server.Classes.Metadata.AgeRatings;
using Asp.Versioning;
namespace gaseous_server.Controllers namespace gaseous_server.Controllers
{ {
@@ -610,6 +611,81 @@ namespace gaseous_server.Controllers
} }
} }
[MapToApiVersion("1.0")]
[MapToApiVersion("1.1")]
[HttpGet]
[Route("{GameId}/favourite")]
[ProducesResponseType(typeof(bool), StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status404NotFound)]
public async Task<ActionResult> GameGetFavouriteAsync(long GameId)
{
try
{
IGDB.Models.Game gameObject = Classes.Metadata.Games.GetGame(GameId, false, false, false);
if (gameObject != null)
{
var user = await _userManager.GetUserAsync(User);
if (user != null)
{
Favourites favourites = new Favourites();
return Ok(favourites.GetFavourite(user.Id, GameId));
}
else
{
return NotFound();
}
}
else
{
return NotFound();
}
}
catch
{
return NotFound();
}
}
[MapToApiVersion("1.0")]
[MapToApiVersion("1.1")]
[HttpPost]
[Route("{GameId}/favourite")]
[ProducesResponseType(typeof(bool), StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status404NotFound)]
public async Task<ActionResult> GameSetFavouriteAsync(long GameId, bool favourite)
{
try
{
IGDB.Models.Game gameObject = Classes.Metadata.Games.GetGame(GameId, false, false, false);
if (gameObject != null)
{
var user = await _userManager.GetUserAsync(User);
if (user != null)
{
Favourites favourites = new Favourites();
return Ok(favourites.SetFavourite(user.Id, GameId, favourite));
}
else
{
return NotFound();
}
}
else
{
return NotFound();
}
}
catch
{
return NotFound();
}
}
[MapToApiVersion("1.0")] [MapToApiVersion("1.0")]
[MapToApiVersion("1.1")] [MapToApiVersion("1.1")]
[HttpGet] [HttpGet]

View File

@@ -5,6 +5,7 @@ using System.Linq;
using System.Threading.Tasks; using System.Threading.Tasks;
using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc;
using Asp.Versioning;
namespace gaseous_server.Controllers namespace gaseous_server.Controllers
{ {

View File

@@ -5,6 +5,7 @@ using System.Threading.Tasks;
using gaseous_server.Classes; using gaseous_server.Classes;
using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc;
using Asp.Versioning;
namespace gaseous_server.Controllers namespace gaseous_server.Controllers
{ {

View File

@@ -14,6 +14,7 @@ using Microsoft.AspNetCore.Mvc;
using Microsoft.CodeAnalysis.Scripting; using Microsoft.CodeAnalysis.Scripting;
using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Authorization;
using System.Text; using System.Text;
using Asp.Versioning;
namespace gaseous_server.Controllers namespace gaseous_server.Controllers
{ {

View File

@@ -13,6 +13,7 @@ using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc;
using Microsoft.CodeAnalysis.Scripting; using Microsoft.CodeAnalysis.Scripting;
using Asp.Versioning;
namespace gaseous_server.Controllers namespace gaseous_server.Controllers
{ {

View File

@@ -14,6 +14,7 @@ using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc;
using Microsoft.CodeAnalysis.Scripting; using Microsoft.CodeAnalysis.Scripting;
using static gaseous_server.Classes.Metadata.AgeRatings; using static gaseous_server.Classes.Metadata.AgeRatings;
using Asp.Versioning;
namespace gaseous_server.Controllers namespace gaseous_server.Controllers
{ {

View File

@@ -12,7 +12,7 @@ using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc;
using NuGet.Common; using NuGet.Common;
using static gaseous_server.Classes.Metadata.Games; using static gaseous_server.Classes.Metadata.Games;
using Asp.Versioning;
namespace gaseous_server.Controllers namespace gaseous_server.Controllers
{ {

View File

@@ -8,6 +8,7 @@ using gaseous_server.Classes;
using gaseous_signature_parser.models.RomSignatureObject; using gaseous_signature_parser.models.RomSignatureObject;
using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc;
using Asp.Versioning;
// For more information on enabling MVC for empty projects, visit https://go.microsoft.com/fwlink/?LinkID=397860 // For more information on enabling MVC for empty projects, visit https://go.microsoft.com/fwlink/?LinkID=397860

View File

@@ -12,6 +12,8 @@ using gaseous_server.Classes.Metadata;
using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Razor.Hosting; using Microsoft.AspNetCore.Razor.Hosting;
using RestEase;
using Asp.Versioning;
namespace gaseous_server.Controllers namespace gaseous_server.Controllers
{ {
@@ -96,7 +98,7 @@ namespace gaseous_server.Controllers
string ver = "var AppVersion = \"" + Assembly.GetExecutingAssembly().GetName().Version.ToString() + "\";" + Environment.NewLine + string ver = "var AppVersion = \"" + Assembly.GetExecutingAssembly().GetName().Version.ToString() + "\";" + Environment.NewLine +
"var DBSchemaVersion = \"" + db.GetDatabaseSchemaVersion() + "\";" + Environment.NewLine + "var DBSchemaVersion = \"" + db.GetDatabaseSchemaVersion() + "\";" + Environment.NewLine +
"var FirstRunStatus = " + Config.ReadSetting("FirstRunStatus", "0") + ";" + Environment.NewLine + "var FirstRunStatus = " + Config.ReadSetting<string>("FirstRunStatus", "0") + ";" + Environment.NewLine +
"var AgeRatingBoardsStrings = " + JsonSerializer.Serialize(ClassificationBoardsStrings, new JsonSerializerOptions{ "var AgeRatingBoardsStrings = " + JsonSerializer.Serialize(ClassificationBoardsStrings, new JsonSerializerOptions{
WriteIndented = true WriteIndented = true
}) + ";" + Environment.NewLine + }) + ";" + Environment.NewLine +
@@ -105,7 +107,8 @@ namespace gaseous_server.Controllers
}) + ";" + Environment.NewLine + }) + ";" + Environment.NewLine +
"var AgeRatingGroups = " + JsonSerializer.Serialize(AgeGroups.AgeGroupingsFlat, new JsonSerializerOptions{ "var AgeRatingGroups = " + JsonSerializer.Serialize(AgeGroups.AgeGroupingsFlat, new JsonSerializerOptions{
WriteIndented = true WriteIndented = true
}) + ";"; }) + ";" + Environment.NewLine +
"var emulatorDebugMode = " + Config.ReadSetting<string>("emulatorDebugMode", false.ToString()).ToLower() + ";";
byte[] bytes = Encoding.UTF8.GetBytes(ver); byte[] bytes = Encoding.UTF8.GetBytes(ver);
return File(bytes, "text/javascript"); return File(bytes, "text/javascript");
} }
@@ -113,19 +116,23 @@ namespace gaseous_server.Controllers
[MapToApiVersion("1.0")] [MapToApiVersion("1.0")]
[MapToApiVersion("1.1")] [MapToApiVersion("1.1")]
[HttpGet] [HttpGet]
[Route("Settings/BackgroundTasks/Intervals")] [Route("Settings/BackgroundTasks/Configuration")]
[Authorize(Roles = "Admin")] [Authorize(Roles = "Admin")]
[ProducesResponseType(StatusCodes.Status200OK)] [ProducesResponseType(StatusCodes.Status200OK)]
public ActionResult GetBackgroundTasks() public ActionResult GetBackgroundTasks()
{ {
Dictionary<string, BackgroundTaskItem> Intervals = new Dictionary<string, BackgroundTaskItem>(); Dictionary<string, BackgroundTaskItem> Intervals = new Dictionary<string, BackgroundTaskItem>();
Intervals.Add(ProcessQueue.QueueItemType.SignatureIngestor.ToString(), new BackgroundTaskItem(ProcessQueue.QueueItemType.SignatureIngestor)); foreach (ProcessQueue.QueueItemType itemType in Enum.GetValues(typeof(ProcessQueue.QueueItemType)))
Intervals.Add(ProcessQueue.QueueItemType.TitleIngestor.ToString(), new BackgroundTaskItem(ProcessQueue.QueueItemType.TitleIngestor)); {
Intervals.Add(ProcessQueue.QueueItemType.MetadataRefresh.ToString(), new BackgroundTaskItem(ProcessQueue.QueueItemType.MetadataRefresh)); BackgroundTaskItem taskItem = new BackgroundTaskItem(itemType);
Intervals.Add(ProcessQueue.QueueItemType.OrganiseLibrary.ToString(), new BackgroundTaskItem(ProcessQueue.QueueItemType.OrganiseLibrary)); if (taskItem.UserManageable == true)
Intervals.Add(ProcessQueue.QueueItemType.LibraryScan.ToString(), new BackgroundTaskItem(ProcessQueue.QueueItemType.LibraryScan)); {
Intervals.Add(ProcessQueue.QueueItemType.Rematcher.ToString(), new BackgroundTaskItem(ProcessQueue.QueueItemType.Rematcher)); if (!Intervals.ContainsKey(itemType.ToString()))
Intervals.Add(ProcessQueue.QueueItemType.Maintainer.ToString(), new BackgroundTaskItem(ProcessQueue.QueueItemType.Maintainer)); {
Intervals.Add(itemType.ToString(), taskItem);
}
}
}
return Ok(Intervals); return Ok(Intervals);
} }
@@ -133,45 +140,102 @@ namespace gaseous_server.Controllers
[MapToApiVersion("1.0")] [MapToApiVersion("1.0")]
[MapToApiVersion("1.1")] [MapToApiVersion("1.1")]
[HttpPost] [HttpPost]
[Route("Settings/BackgroundTasks/Intervals")] [Route("Settings/BackgroundTasks/Configuration")]
[Authorize(Roles = "Admin")] [Authorize(Roles = "Admin")]
[ProducesResponseType(StatusCodes.Status200OK)] [ProducesResponseType(StatusCodes.Status200OK)]
public ActionResult SetBackgroundTasks(Dictionary<string, int> Intervals) public ActionResult SetBackgroundTasks([FromBody] List<BackgroundTaskSettingsItem> model)
{ {
foreach (KeyValuePair<string, int> Interval in Intervals) foreach (BackgroundTaskSettingsItem TaskConfiguration in model)
{ {
if (Enum.IsDefined(typeof(ProcessQueue.QueueItemType), Interval.Key)) if (Enum.IsDefined(typeof(ProcessQueue.QueueItemType), TaskConfiguration.Task))
{ {
try try
{ {
BackgroundTaskItem taskItem = new BackgroundTaskItem( BackgroundTaskItem taskItem = new BackgroundTaskItem(
(ProcessQueue.QueueItemType)Enum.Parse(typeof(ProcessQueue.QueueItemType), Interval.Key) (ProcessQueue.QueueItemType)Enum.Parse(typeof(ProcessQueue.QueueItemType), TaskConfiguration.Task)
); );
if (Interval.Value >= taskItem.MinimumAllowedValue) if (taskItem.UserManageable == true)
{ {
Logging.Log(Logging.LogType.Information, "Update Background Task", "Updating task " + Interval.Key + " with new interval " + Interval.Value); // update task enabled
Logging.Log(Logging.LogType.Information, "Update Background Task", "Updating task " + TaskConfiguration.Task + " with enabled value " + TaskConfiguration.Enabled.ToString());
Config.SetSetting("Interval_" + Interval.Key, Interval.Value.ToString()); Config.SetSetting<string>("Enabled_" + TaskConfiguration.Task, TaskConfiguration.Enabled.ToString());
// update existing process // update existing process
foreach (ProcessQueue.QueueItem item in ProcessQueue.QueueItems) foreach (ProcessQueue.QueueItem item in ProcessQueue.QueueItems)
{ {
if (item.ItemType.ToString().ToLower() == Interval.Key.ToLower()) if (item.ItemType.ToString().ToLower() == TaskConfiguration.Task.ToLower())
{ {
item.Interval = Interval.Value; item.Enabled(Boolean.Parse(TaskConfiguration.Enabled.ToString()));
} }
} }
// update task interval
if (TaskConfiguration.Interval >= taskItem.MinimumAllowedInterval)
{
Logging.Log(Logging.LogType.Information, "Update Background Task", "Updating task " + TaskConfiguration.Task + " with new interval " + TaskConfiguration.Interval);
Config.SetSetting<string>("Interval_" + TaskConfiguration.Task, TaskConfiguration.Interval.ToString());
// update existing process
foreach (ProcessQueue.QueueItem item in ProcessQueue.QueueItems)
{
if (item.ItemType.ToString().ToLower() == TaskConfiguration.Task.ToLower())
{
item.Interval = TaskConfiguration.Interval;
}
}
}
else
{
Logging.Log(Logging.LogType.Warning, "Update Background Task", "Interval " + TaskConfiguration.Interval.ToString() + " for task " + TaskConfiguration.Task + " is below the minimum allowed value of " + taskItem.MinimumAllowedInterval + ". Skipping.");
}
// update task weekdays
Logging.Log(Logging.LogType.Information, "Update Background Task", "Updating task " + TaskConfiguration.Task + " with new weekdays " + String.Join(", ", TaskConfiguration.AllowedDays));
Config.SetSetting<string>("AllowedDays_" + TaskConfiguration.Task, Newtonsoft.Json.JsonConvert.SerializeObject(TaskConfiguration.AllowedDays));
// update existing process
foreach (ProcessQueue.QueueItem item in ProcessQueue.QueueItems)
{
if (item.ItemType.ToString().ToLower() == TaskConfiguration.Task.ToLower())
{
item.AllowedDays = TaskConfiguration.AllowedDays;
}
}
// update task hours
Logging.Log(Logging.LogType.Information, "Update Background Task", "Updating task " + TaskConfiguration.Task + " with new hours " + TaskConfiguration.AllowedStartHours + ":" + TaskConfiguration.AllowedStartMinutes.ToString("00") + " to " + TaskConfiguration.AllowedEndHours + ":" + TaskConfiguration.AllowedEndMinutes.ToString("00"));
Config.SetSetting<string>("AllowedStartHours_" + TaskConfiguration.Task, TaskConfiguration.AllowedStartHours.ToString());
Config.SetSetting<string>("AllowedStartMinutes_" + TaskConfiguration.Task, TaskConfiguration.AllowedStartMinutes.ToString());
Config.SetSetting<string>("AllowedEndHours_" + TaskConfiguration.Task, TaskConfiguration.AllowedEndHours.ToString());
Config.SetSetting<string>("AllowedEndMinutes_" + TaskConfiguration.Task, TaskConfiguration.AllowedEndMinutes.ToString());
// update existing process
foreach (ProcessQueue.QueueItem item in ProcessQueue.QueueItems)
{
if (item.ItemType.ToString().ToLower() == TaskConfiguration.Task.ToLower())
{
item.AllowedStartHours = TaskConfiguration.AllowedStartHours;
item.AllowedStartMinutes = TaskConfiguration.AllowedStartMinutes;
item.AllowedEndHours = TaskConfiguration.AllowedEndHours;
item.AllowedEndMinutes = TaskConfiguration.AllowedEndMinutes;
}
}
} }
else else
{ {
Logging.Log(Logging.LogType.Warning, "Update Background Task", "Interval " + Interval.Value + " for task " + Interval.Key + " is below the minimum allowed value of " + taskItem.MinimumAllowedValue + ". Skipping."); Logging.Log(Logging.LogType.Warning, "Update Background Task", "Unable to update non-user manageable task " + TaskConfiguration.Task + ". Skipping.");
} }
} }
catch catch
{ {
// task name not defined // task name not defined
Logging.Log(Logging.LogType.Warning, "Update Background Task", "Task " + Interval.Key + " is not user definable. Skipping."); Logging.Log(Logging.LogType.Warning, "Update Background Task", "Task " + TaskConfiguration.Task + " is not user definable. Skipping.");
} }
} }
} }
@@ -189,7 +253,8 @@ namespace gaseous_server.Controllers
{ {
SystemSettingsModel systemSettingsModel = new SystemSettingsModel{ SystemSettingsModel systemSettingsModel = new SystemSettingsModel{
AlwaysLogToDisk = Config.LoggingConfiguration.AlwaysLogToDisk, AlwaysLogToDisk = Config.LoggingConfiguration.AlwaysLogToDisk,
MinimumLogRetentionPeriod = Config.LoggingConfiguration.LogRetention MinimumLogRetentionPeriod = Config.LoggingConfiguration.LogRetention,
EmulatorDebugMode = Boolean.Parse(Config.ReadSetting<string>("emulatorDebugMode", false.ToString()))
}; };
return Ok(systemSettingsModel); return Ok(systemSettingsModel);
@@ -207,6 +272,7 @@ namespace gaseous_server.Controllers
{ {
Config.LoggingConfiguration.AlwaysLogToDisk = model.AlwaysLogToDisk; Config.LoggingConfiguration.AlwaysLogToDisk = model.AlwaysLogToDisk;
Config.LoggingConfiguration.LogRetention = model.MinimumLogRetentionPeriod; Config.LoggingConfiguration.LogRetention = model.MinimumLogRetentionPeriod;
Config.SetSetting<string>("emulatorDebugMode", model.EmulatorDebugMode.ToString());
Config.UpdateConfig(); Config.UpdateConfig();
} }
@@ -256,66 +322,393 @@ namespace gaseous_server.Controllers
public class BackgroundTaskItem public class BackgroundTaskItem
{ {
public BackgroundTaskItem()
{
}
public BackgroundTaskItem(ProcessQueue.QueueItemType TaskName) public BackgroundTaskItem(ProcessQueue.QueueItemType TaskName)
{ {
this.Task = TaskName.ToString(); this.Task = TaskName.ToString();
this.TaskEnum = TaskName;
switch (TaskName) switch (TaskName)
{ {
case ProcessQueue.QueueItemType.SignatureIngestor: case ProcessQueue.QueueItemType.SignatureIngestor:
this._UserManageable = true;
this.DefaultInterval = 60; this.DefaultInterval = 60;
this.MinimumAllowedValue = 20; this.MinimumAllowedInterval = 20;
this.DefaultAllowedDays = new List<DayOfWeek>{
DayOfWeek.Sunday,
DayOfWeek.Monday,
DayOfWeek.Tuesday,
DayOfWeek.Wednesday,
DayOfWeek.Thursday,
DayOfWeek.Friday,
DayOfWeek.Saturday
};
this.DefaultAllowedStartHours = 0;
this.DefaultAllowedStartMinutes = 0;
this.DefaultAllowedEndHours = 23;
this.DefaultAllowedEndMinutes = 59;
break; break;
case ProcessQueue.QueueItemType.TitleIngestor: case ProcessQueue.QueueItemType.TitleIngestor:
this._UserManageable = true;
this.DefaultInterval = 1; this.DefaultInterval = 1;
this.MinimumAllowedValue = 1; this.MinimumAllowedInterval = 1;
this.DefaultAllowedDays = new List<DayOfWeek>{
DayOfWeek.Sunday,
DayOfWeek.Monday,
DayOfWeek.Tuesday,
DayOfWeek.Wednesday,
DayOfWeek.Thursday,
DayOfWeek.Friday,
DayOfWeek.Saturday
};
this.DefaultAllowedStartHours = 0;
this.DefaultAllowedStartMinutes = 0;
this.DefaultAllowedEndHours = 23;
this.DefaultAllowedEndMinutes = 59;
this._Blocks = new List<ProcessQueue.QueueItemType>{
ProcessQueue.QueueItemType.OrganiseLibrary,
ProcessQueue.QueueItemType.LibraryScan,
ProcessQueue.QueueItemType.LibraryScanWorker
};
break; break;
case ProcessQueue.QueueItemType.MetadataRefresh: case ProcessQueue.QueueItemType.MetadataRefresh:
this._UserManageable = true;
this.DefaultInterval = 360; this.DefaultInterval = 360;
this.MinimumAllowedValue = 360; this.MinimumAllowedInterval = 360;
this.DefaultAllowedDays = new List<DayOfWeek>{
DayOfWeek.Sunday,
DayOfWeek.Monday,
DayOfWeek.Tuesday,
DayOfWeek.Wednesday,
DayOfWeek.Thursday,
DayOfWeek.Friday,
DayOfWeek.Saturday
};
this.DefaultAllowedStartHours = 0;
this.DefaultAllowedStartMinutes = 0;
this.DefaultAllowedEndHours = 23;
this.DefaultAllowedEndMinutes = 59;
break; break;
case ProcessQueue.QueueItemType.OrganiseLibrary: case ProcessQueue.QueueItemType.OrganiseLibrary:
this._UserManageable = true;
this.DefaultInterval = 1440; this.DefaultInterval = 1440;
this.MinimumAllowedValue = 120; this.MinimumAllowedInterval = 120;
this.DefaultAllowedDays = new List<DayOfWeek>{
DayOfWeek.Sunday,
DayOfWeek.Monday,
DayOfWeek.Tuesday,
DayOfWeek.Wednesday,
DayOfWeek.Thursday,
DayOfWeek.Friday,
DayOfWeek.Saturday
};
this.DefaultAllowedStartHours = 0;
this.DefaultAllowedStartMinutes = 0;
this.DefaultAllowedEndHours = 23;
this.DefaultAllowedEndMinutes = 59;
this._Blocks = new List<ProcessQueue.QueueItemType>{
ProcessQueue.QueueItemType.LibraryScan,
ProcessQueue.QueueItemType.LibraryScanWorker,
ProcessQueue.QueueItemType.TitleIngestor,
ProcessQueue.QueueItemType.Rematcher
};
break; break;
case ProcessQueue.QueueItemType.LibraryScan: case ProcessQueue.QueueItemType.LibraryScan:
this._UserManageable = true;
this.DefaultInterval = 1440; this.DefaultInterval = 1440;
this.MinimumAllowedValue = 120; this.MinimumAllowedInterval = 120;
this.DefaultAllowedDays = new List<DayOfWeek>{
DayOfWeek.Sunday,
DayOfWeek.Monday,
DayOfWeek.Tuesday,
DayOfWeek.Wednesday,
DayOfWeek.Thursday,
DayOfWeek.Friday,
DayOfWeek.Saturday
};
this.DefaultAllowedStartHours = 0;
this.DefaultAllowedStartMinutes = 0;
this.DefaultAllowedEndHours = 23;
this.DefaultAllowedEndMinutes = 59;
this._Blocks = new List<ProcessQueue.QueueItemType>{
ProcessQueue.QueueItemType.OrganiseLibrary,
ProcessQueue.QueueItemType.Rematcher
};
break; break;
case ProcessQueue.QueueItemType.Rematcher: case ProcessQueue.QueueItemType.Rematcher:
this._UserManageable = true;
this.DefaultInterval = 1440; this.DefaultInterval = 1440;
this.MinimumAllowedValue = 360; this.MinimumAllowedInterval = 360;
this.DefaultAllowedDays = new List<DayOfWeek>{
DayOfWeek.Sunday,
DayOfWeek.Monday,
DayOfWeek.Tuesday,
DayOfWeek.Wednesday,
DayOfWeek.Thursday,
DayOfWeek.Friday,
DayOfWeek.Saturday
};
this.DefaultAllowedStartHours = 0;
this.DefaultAllowedStartMinutes = 0;
this.DefaultAllowedEndHours = 23;
this.DefaultAllowedEndMinutes = 59;
this._Blocks = new List<ProcessQueue.QueueItemType>{
ProcessQueue.QueueItemType.OrganiseLibrary,
ProcessQueue.QueueItemType.LibraryScan,
ProcessQueue.QueueItemType.LibraryScanWorker
};
break; break;
case ProcessQueue.QueueItemType.Maintainer: case ProcessQueue.QueueItemType.DailyMaintainer:
this._UserManageable = true;
this.DefaultInterval = 1440;
this.MinimumAllowedInterval = 1440;
this.DefaultAllowedDays = new List<DayOfWeek>{
DayOfWeek.Sunday,
DayOfWeek.Monday,
DayOfWeek.Tuesday,
DayOfWeek.Wednesday,
DayOfWeek.Thursday,
DayOfWeek.Friday,
DayOfWeek.Saturday
};
this.DefaultAllowedStartHours = 1;
this.DefaultAllowedStartMinutes = 0;
this.DefaultAllowedEndHours = 5;
this.DefaultAllowedEndMinutes = 59;
this._Blocks = new List<ProcessQueue.QueueItemType>{
ProcessQueue.QueueItemType.All
};
break;
case ProcessQueue.QueueItemType.WeeklyMaintainer:
this._UserManageable = true;
this.DefaultInterval = 10080; this.DefaultInterval = 10080;
this.MinimumAllowedValue = 10080; this.MinimumAllowedInterval = 10080;
this.DefaultAllowedDays = new List<DayOfWeek>{
DayOfWeek.Monday
};
this.DefaultAllowedStartHours = 1;
this.DefaultAllowedStartMinutes = 0;
this.DefaultAllowedEndHours = 5;
this.DefaultAllowedEndMinutes = 59;
this._Blocks = new List<ProcessQueue.QueueItemType>{
ProcessQueue.QueueItemType.All
};
break;
case ProcessQueue.QueueItemType.BackgroundDatabaseUpgrade:
this._UserManageable = false;
this.DefaultInterval = 1;
this.MinimumAllowedInterval = 1;
this.DefaultAllowedDays = new List<DayOfWeek>{
DayOfWeek.Sunday,
DayOfWeek.Monday,
DayOfWeek.Tuesday,
DayOfWeek.Wednesday,
DayOfWeek.Thursday,
DayOfWeek.Friday,
DayOfWeek.Saturday
};
this.DefaultAllowedStartHours = 0;
this.DefaultAllowedStartMinutes = 0;
this.DefaultAllowedEndHours = 23;
this.DefaultAllowedEndMinutes = 59;
this._Blocks.Add(ProcessQueue.QueueItemType.All);
break;
case ProcessQueue.QueueItemType.TempCleanup:
this._UserManageable = true;
this.DefaultInterval = 1;
this.MinimumAllowedInterval = 1;
this.DefaultAllowedDays = new List<DayOfWeek>{
DayOfWeek.Sunday,
DayOfWeek.Monday,
DayOfWeek.Tuesday,
DayOfWeek.Wednesday,
DayOfWeek.Thursday,
DayOfWeek.Friday,
DayOfWeek.Saturday
};
this.DefaultAllowedStartHours = 0;
this.DefaultAllowedStartMinutes = 0;
this.DefaultAllowedEndHours = 23;
this.DefaultAllowedEndMinutes = 59;
break; break;
default: default:
throw new Exception("Invalid task"); this._UserManageable = false;
this.DefaultAllowedDays = new List<DayOfWeek>{
DayOfWeek.Sunday,
DayOfWeek.Monday,
DayOfWeek.Tuesday,
DayOfWeek.Wednesday,
DayOfWeek.Thursday,
DayOfWeek.Friday,
DayOfWeek.Saturday
};
this.DefaultAllowedStartHours = 0;
this.DefaultAllowedStartMinutes = 0;
this.DefaultAllowedEndHours = 23;
this.DefaultAllowedEndMinutes = 59;
break;
} }
} }
public string Task { get; set; } public string Task { get; set; }
public ProcessQueue.QueueItemType TaskEnum { get; set; }
public bool Enabled
{
get
{
if (_UserManageable == true)
{
return bool.Parse(Config.ReadSetting<string>("Enabled_" + Task, true.ToString()));
}
else
{
return true;
}
}
set
{
if (_UserManageable == true)
{
Config.SetSetting<string>("Enabled_" + Task, value.ToString());
}
}
}
private bool _UserManageable;
public bool UserManageable => _UserManageable;
public int Interval { public int Interval {
get get
{ {
return int.Parse(Config.ReadSetting("Interval_" + Task, DefaultInterval.ToString())); return int.Parse(Config.ReadSetting<string>("Interval_" + Task, DefaultInterval.ToString()));
} }
} }
public int DefaultInterval { get; set; } public int DefaultInterval { get; set; }
public int MinimumAllowedValue { get; set; } public int MinimumAllowedInterval { get; set; }
public List<DayOfWeek> AllowedDays
{
get
{
string jsonDefaultAllowedDays = Newtonsoft.Json.JsonConvert.SerializeObject(DefaultAllowedDays);
return Newtonsoft.Json.JsonConvert.DeserializeObject<List<DayOfWeek>>(Config.ReadSetting<string>("AllowedDays_" + Task, jsonDefaultAllowedDays));
}
}
public int AllowedStartHours
{
get
{
return int.Parse(Config.ReadSetting<string>("AllowedStartHours_" + Task, DefaultAllowedStartHours.ToString()));
}
}
public int AllowedStartMinutes
{
get
{
return int.Parse(Config.ReadSetting<string>("AllowedStartMinutes_" + Task, DefaultAllowedStartMinutes.ToString()));
}
}
public int AllowedEndHours
{
get
{
return int.Parse(Config.ReadSetting<string>("AllowedEndHours_" + Task, DefaultAllowedEndHours.ToString()));
}
}
public int AllowedEndMinutes
{
get
{
return int.Parse(Config.ReadSetting<string>("AllowedEndMinutes_" + Task, DefaultAllowedEndMinutes.ToString()));
}
}
public List<DayOfWeek> DefaultAllowedDays { get; set; }
public int DefaultAllowedStartHours { get; set; }
public int DefaultAllowedStartMinutes { get; set; }
public int DefaultAllowedEndHours { get; set; }
public int DefaultAllowedEndMinutes { get; set; }
private List<ProcessQueue.QueueItemType> _Blocks = new List<ProcessQueue.QueueItemType>();
public List<ProcessQueue.QueueItemType> Blocks
{
get
{
if (_Blocks.Contains(ProcessQueue.QueueItemType.All))
{
List<ProcessQueue.QueueItemType> blockList = new List<ProcessQueue.QueueItemType>();
List<ProcessQueue.QueueItemType> skipBlockItems = new List<ProcessQueue.QueueItemType>{
ProcessQueue.QueueItemType.All,
ProcessQueue.QueueItemType.NotConfigured,
this.TaskEnum
};
foreach (ProcessQueue.QueueItemType blockType in Enum.GetValues(typeof(ProcessQueue.QueueItemType)))
{
if (!skipBlockItems.Contains(blockType))
{
blockList.Add(blockType);
}
}
return blockList;
}
else
{
return _Blocks;
}
}
}
public List<ProcessQueue.QueueItemType> BlockedBy
{
get
{
List<ProcessQueue.QueueItemType> blockedBy = new List<ProcessQueue.QueueItemType>();
List<BackgroundTaskItem> backgroundTaskItems = new List<BackgroundTaskItem>();
foreach (ProcessQueue.QueueItemType blockType in Enum.GetValues(typeof(ProcessQueue.QueueItemType)))
{
if (blockType != this.TaskEnum)
{
BackgroundTaskItem taskItem = new BackgroundTaskItem(blockType);
if (taskItem.Blocks.Contains(this.TaskEnum))
{
if (!blockedBy.Contains(blockType))
{
blockedBy.Add(blockType);
}
}
}
}
return blockedBy;
}
}
}
public class BackgroundTaskSettingsItem
{
public string Task { get; set; }
public bool Enabled { get; set; }
public int Interval { get; set; }
public List<DayOfWeek> AllowedDays { get; set; }
public int AllowedStartHours { get; set; }
public int AllowedStartMinutes { get; set; }
public int AllowedEndHours { get; set; }
public int AllowedEndMinutes { get; set; }
} }
public class SystemSettingsModel public class SystemSettingsModel
{ {
public bool AlwaysLogToDisk { get; set; } public bool AlwaysLogToDisk { get; set; }
public int MinimumLogRetentionPeriod { get; set; } public int MinimumLogRetentionPeriod { get; set; }
public bool EmulatorDebugMode { get; set; }
} }
} }

View File

@@ -12,6 +12,7 @@ using gaseous_server.Classes.Metadata;
using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Identity; using Microsoft.AspNetCore.Identity;
using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc;
using Asp.Versioning;
namespace gaseous_server.Controllers namespace gaseous_server.Controllers
{ {
@@ -40,7 +41,7 @@ namespace gaseous_server.Controllers
[ProducesResponseType(StatusCodes.Status404NotFound)] [ProducesResponseType(StatusCodes.Status404NotFound)]
public async Task<ActionResult> CreateAdminAccount(Authentication.RegisterViewModel model) public async Task<ActionResult> CreateAdminAccount(Authentication.RegisterViewModel model)
{ {
if (Config.ReadSetting("FirstRunStatus", "0") == "0") if (Config.ReadSetting<string>("FirstRunStatus", "0") == "0")
{ {
if (ModelState.IsValid) if (ModelState.IsValid)
{ {
@@ -68,7 +69,16 @@ namespace gaseous_server.Controllers
await _signInManager.SignInAsync(user, isPersistent: true); await _signInManager.SignInAsync(user, isPersistent: true);
Logging.Log(Logging.LogType.Information, "First Run", "Setting first run state to 1"); Logging.Log(Logging.LogType.Information, "First Run", "Setting first run state to 1");
Config.SetSetting("FirstRunStatus", "1"); Config.SetSetting<string>("FirstRunStatus", "1");
Logging.Log(Logging.LogType.Information, "First Run", "Migrating existing collections to newly created user (for upgrades from v1.6.1 and earlier)");
Database db = new Database(Database.databaseType.MySql, Config.DatabaseConfiguration.ConnectionString);
string sql = "UPDATE RomCollections SET OwnedBy=@userid WHERE OwnedBy IS NULL;";
Dictionary<string, object> dbDict = new Dictionary<string, object>
{
{ "userid", user.Id }
};
db.ExecuteCMD(sql, dbDict);
return Ok(result); return Ok(result);
} }

View File

@@ -17,6 +17,8 @@ using Microsoft.AspNetCore.Mvc;
using Microsoft.CodeAnalysis.CSharp.Syntax; using Microsoft.CodeAnalysis.CSharp.Syntax;
using Microsoft.CodeAnalysis.Scripting; using Microsoft.CodeAnalysis.Scripting;
using static gaseous_server.Classes.Metadata.AgeRatings; using static gaseous_server.Classes.Metadata.AgeRatings;
using Asp.Versioning;
using Humanizer;
namespace gaseous_server.Controllers.v1_1 namespace gaseous_server.Controllers.v1_1
{ {
@@ -141,10 +143,13 @@ namespace gaseous_server.Controllers.v1_1
public List<string> GameMode { get; set; } public List<string> GameMode { get; set; }
public List<string> PlayerPerspective { get; set; } public List<string> PlayerPerspective { get; set; }
public List<string> Theme { get; set; } public List<string> Theme { get; set; }
public int MinimumReleaseYear { get; set; } = -1;
public int MaximumReleaseYear { get; set; } = -1;
public GameRatingItem GameRating { get; set; } = new GameRatingItem(); public GameRatingItem GameRating { get; set; } = new GameRatingItem();
public GameAgeRatingItem GameAgeRating { get; set; } = new GameAgeRatingItem(); public GameAgeRatingItem GameAgeRating { get; set; } = new GameAgeRatingItem();
public GameSortingItem Sorting { get; set; } = new GameSortingItem(); public GameSortingItem Sorting { get; set; } = new GameSortingItem();
public bool HasSavedGame { get; set; } public bool HasSavedGame { get; set; }
public bool IsFavourite { get; set; }
public class GameRatingItem public class GameRatingItem
@@ -210,6 +215,26 @@ namespace gaseous_server.Controllers.v1_1
whereClauses.Add(hasSavesTemp); whereClauses.Add(hasSavesTemp);
} }
if (model.IsFavourite == true)
{
string isFavTemp = "Favourite = 1";
havingClauses.Add(isFavTemp);
}
if (model.MinimumReleaseYear != -1)
{
string releaseTempMinVal = "FirstReleaseDate >= @minreleasedate";
whereParams.Add("minreleasedate", new DateTime(model.MinimumReleaseYear, 1, 1));
havingClauses.Add(releaseTempMinVal);
}
if (model.MaximumReleaseYear != -1)
{
string releaseTempMaxVal = "FirstReleaseDate <= @maxreleasedate";
whereParams.Add("maxreleasedate", new DateTime(model.MaximumReleaseYear, 12, 31, 23, 59, 59));
havingClauses.Add(releaseTempMaxVal);
}
if (model.GameRating != null) if (model.GameRating != null)
{ {
List<string> ratingClauses = new List<string>(); List<string> ratingClauses = new List<string>();
@@ -458,6 +483,7 @@ SELECT DISTINCT
Game.Id, Game.Id,
Game.`Name`, Game.`Name`,
Game.NameThe, Game.NameThe,
Game.Slug,
Game.PlatformId, Game.PlatformId,
Game.TotalRating, Game.TotalRating,
Game.TotalRatingCount, Game.TotalRatingCount,
@@ -470,7 +496,11 @@ SELECT DISTINCT
Game.AgeGroupId, Game.AgeGroupId,
Game.RomCount, Game.RomCount,
RomSavedStates.RomSaveCount, RomSavedStates.RomSaveCount,
RomGroupSavedStates.MediaGroupSaveCount RomGroupSavedStates.MediaGroupSaveCount,
CASE
WHEN Favourites.UserId IS NULL THEN 0
ELSE 1
END AS Favourite
FROM FROM
(SELECT DISTINCT (SELECT DISTINCT
Game.*, Game.*,
@@ -515,21 +545,26 @@ FROM
LEFT JOIN LEFT JOIN
Relation_Game_PlayerPerspectives ON Game.Id = Relation_Game_PlayerPerspectives.GameId Relation_Game_PlayerPerspectives ON Game.Id = Relation_Game_PlayerPerspectives.GameId
LEFT JOIN LEFT JOIN
Relation_Game_Themes ON Game.Id = Relation_Game_Themes.GameId " + whereClause + " " + havingClause + " " + orderByClause; Relation_Game_Themes ON Game.Id = Relation_Game_Themes.GameId
LEFT JOIN
Favourites ON Game.Id = Favourites.GameId AND Favourites.UserId = @userid " + whereClause + " " + havingClause + " " + orderByClause;
List<Games.MinimalGameItem> RetVal = new List<Games.MinimalGameItem>(); List<Games.MinimalGameItem> RetVal = new List<Games.MinimalGameItem>();
DataTable dbResponse = db.ExecuteCMD(sql, whereParams); DataTable dbResponse = db.ExecuteCMD(sql, whereParams);
// get count // get count
int RecordCount = dbResponse.Rows.Count; int RecordCount = dbResponse.Rows.Count;
// compile data for return // compile data for return
int pageOffset = pageSize * (pageNumber - 1); int pageOffset = pageSize * (pageNumber - 1);
for (int i = pageOffset; i < dbResponse.Rows.Count; i++) for (int i = pageOffset; i < dbResponse.Rows.Count; i++)
{ {
if (i >= (pageOffset + pageSize)) if (pageNumber != 0 && pageSize != 0)
{ {
break; if (i >= (pageOffset + pageSize))
{
break;
}
} }
Game retGame = Storage.BuildCacheObject<Game>(new Game() , dbResponse.Rows[i]); Game retGame = Storage.BuildCacheObject<Game>(new Game() , dbResponse.Rows[i]);
@@ -542,14 +577,45 @@ FROM
{ {
retMinGame.HasSavedGame = false; retMinGame.HasSavedGame = false;
} }
if ((int)dbResponse.Rows[i]["Favourite"] == 0)
{
retMinGame.IsFavourite = false;
}
else
{
retMinGame.IsFavourite = true;
}
RetVal.Add(retMinGame); RetVal.Add(retMinGame);
} }
// build alpha list
Dictionary<string, int> AlphaList = new Dictionary<string, int>();
int CurrentPage = 0;
int NextPageIndex = 0;
for (int i = 0; i < dbResponse.Rows.Count; i++)
{
string firstChar = dbResponse.Rows[i]["NameThe"].ToString().Substring(0, 1).ToUpperInvariant();
if (!"ABCDEFGHIJKLMNOPQRSTUVWXYZ".Contains(firstChar))
{
firstChar = "#";
}
if (!AlphaList.ContainsKey(firstChar))
{
AlphaList.Add(firstChar, CurrentPage);
}
if (NextPageIndex == i)
{
NextPageIndex += pageSize;
CurrentPage += 1;
}
}
GameReturnPackage gameReturn = new GameReturnPackage GameReturnPackage gameReturn = new GameReturnPackage
{ {
Count = RecordCount, Count = RecordCount,
Games = RetVal Games = RetVal,
AlphaList = AlphaList
}; };
return gameReturn; return gameReturn;
@@ -577,6 +643,7 @@ FROM
public int Count { get; set; } public int Count { get; set; }
public List<Games.MinimalGameItem> Games { get; set; } = new List<Games.MinimalGameItem>(); public List<Games.MinimalGameItem> Games { get; set; } = new List<Games.MinimalGameItem>();
public Dictionary<string, int> AlphaList { get; set; }
} }
} }
} }

View File

@@ -14,6 +14,7 @@ using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc;
using Microsoft.CodeAnalysis.Scripting; using Microsoft.CodeAnalysis.Scripting;
using static gaseous_server.Classes.Metadata.AgeRatings; using static gaseous_server.Classes.Metadata.AgeRatings;
using Asp.Versioning;
namespace gaseous_server.Controllers.v1_1 namespace gaseous_server.Controllers.v1_1
{ {

View File

@@ -5,6 +5,7 @@ using gaseous_server.Classes;
using Authentication; using Authentication;
using Microsoft.AspNetCore.Identity; using Microsoft.AspNetCore.Identity;
using System.Data; using System.Data;
using Asp.Versioning;
namespace gaseous_server.Controllers.v1_1 namespace gaseous_server.Controllers.v1_1
{ {
@@ -36,9 +37,11 @@ namespace gaseous_server.Controllers.v1_1
public async Task<ActionResult> SaveStateAsync(long RomId, UploadStateModel uploadState, bool IsMediaGroup = false) public async Task<ActionResult> SaveStateAsync(long RomId, UploadStateModel uploadState, bool IsMediaGroup = false)
{ {
var user = await _userManager.GetUserAsync(User); var user = await _userManager.GetUserAsync(User);
byte[] CompressedState = Common.Compress(uploadState.StateByteArray);
Database db = new Database(Database.databaseType.MySql, Config.DatabaseConfiguration.ConnectionString); Database db = new Database(Database.databaseType.MySql, Config.DatabaseConfiguration.ConnectionString);
string sql = "INSERT INTO GameState (UserId, RomId, IsMediaGroup, StateDateTime, Name, Screenshot, State) VALUES (@userid, @romid, @ismediagroup, @statedatetime, @name, @screenshot, @state); SELECT LAST_INSERT_ID();"; string sql = "INSERT INTO GameState (UserId, RomId, IsMediaGroup, StateDateTime, Name, Screenshot, State, Zipped) VALUES (@userid, @romid, @ismediagroup, @statedatetime, @name, @screenshot, @state, @zipped); SELECT LAST_INSERT_ID();";
Dictionary<string, object> dbDict = new Dictionary<string, object> Dictionary<string, object> dbDict = new Dictionary<string, object>
{ {
{ "userid", user.Id }, { "userid", user.Id },
@@ -47,10 +50,20 @@ namespace gaseous_server.Controllers.v1_1
{ "statedatetime", DateTime.UtcNow }, { "statedatetime", DateTime.UtcNow },
{ "name", "" }, { "name", "" },
{ "screenshot", uploadState.ScreenshotByteArray }, { "screenshot", uploadState.ScreenshotByteArray },
{ "state", uploadState.StateByteArray } { "state", CompressedState },
{ "zipped", true }
}; };
DataTable data = db.ExecuteCMD(sql, dbDict); DataTable data = db.ExecuteCMD(sql, dbDict);
if (IsMediaGroup == false)
{
Logging.Log(Logging.LogType.Information, "Save State", "Saved state for rom id " + RomId + ". State size: " + uploadState.StateByteArrayBase64.Length + " Compressed size: " + CompressedState.Length);
}
else
{
Logging.Log(Logging.LogType.Information, "Save State", "Saved state for media group id " + RomId + ". State size: " + uploadState.StateByteArrayBase64.Length + " Compressed size: " + CompressedState.Length);
}
return Ok(await GetStateAsync(RomId, (long)(ulong)data.Rows[0][0], IsMediaGroup)); return Ok(await GetStateAsync(RomId, (long)(ulong)data.Rows[0][0], IsMediaGroup));
} }
@@ -224,7 +237,7 @@ namespace gaseous_server.Controllers.v1_1
{ {
var user = await _userManager.GetUserAsync(User); var user = await _userManager.GetUserAsync(User);
Database db = new Database(Database.databaseType.MySql, Config.DatabaseConfiguration.ConnectionString); Database db = new Database(Database.databaseType.MySql, Config.DatabaseConfiguration.ConnectionString);
string sql = "SELECT State FROM GameState WHERE Id = @id AND RomId = @romid AND IsMediaGroup = @ismediagroup AND UserId = @userid;"; string sql = "SELECT Zipped, State FROM GameState WHERE Id = @id AND RomId = @romid AND IsMediaGroup = @ismediagroup AND UserId = @userid;";
Dictionary<string, object> dbDict = new Dictionary<string, object> Dictionary<string, object> dbDict = new Dictionary<string, object>
{ {
{ "id", StateId }, { "id", StateId },
@@ -242,7 +255,15 @@ namespace gaseous_server.Controllers.v1_1
else else
{ {
string filename = "savestate.state"; string filename = "savestate.state";
byte[] bytes = (byte[])data.Rows[0][0]; byte[] bytes;
if ((bool)data.Rows[0]["Zipped"] == false)
{
bytes = (byte[])data.Rows[0]["State"];
}
else
{
bytes = Common.Decompress((byte[])data.Rows[0]["State"]);
}
string contentType = "application/octet-stream"; string contentType = "application/octet-stream";
var cd = new System.Net.Mime.ContentDisposition var cd = new System.Net.Mime.ContentDisposition

View File

@@ -0,0 +1,104 @@
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Authorization;
using gaseous_server.Models;
using gaseous_server.Classes;
using Authentication;
using Microsoft.AspNetCore.Identity;
using Asp.Versioning;
namespace gaseous_server.Controllers.v1_1
{
[Route("api/v{version:apiVersion}/[controller]")]
[ApiVersion("1.0")]
[ApiVersion("1.1")]
[ApiController]
public class StatisticsController: ControllerBase
{
private readonly UserManager<ApplicationUser> _userManager;
private readonly SignInManager<ApplicationUser> _signInManager;
public StatisticsController(
UserManager<ApplicationUser> userManager,
SignInManager<ApplicationUser> signInManager
)
{
_userManager = userManager;
_signInManager = signInManager;
}
[MapToApiVersion("1.0")]
[MapToApiVersion("1.1")]
[HttpPost]
[Authorize]
[ProducesResponseType(typeof(Models.StatisticsModel), StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status404NotFound)]
[Route("Games/{GameId}/")]
public async Task<ActionResult> NewRecordStatistics(long GameId)
{
var user = await _userManager.GetUserAsync(User);
if (user != null)
{
Statistics statistics = new Statistics();
return Ok(statistics.RecordSession(Guid.Empty, GameId, user.Id));
}
else
{
return Unauthorized();
}
}
[MapToApiVersion("1.0")]
[MapToApiVersion("1.1")]
[HttpPut]
[Authorize]
[ProducesResponseType(typeof(Models.StatisticsModel), StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status404NotFound)]
[Route("Games/{GameId}/{SessionId}")]
public async Task<ActionResult> SubsequentRecordStatistics(long GameId, Guid SessionId)
{
var user = await _userManager.GetUserAsync(User);
if (user != null)
{
Statistics statistics = new Statistics();
return Ok(statistics.RecordSession(SessionId, GameId, user.Id));
}
else
{
return Unauthorized();
}
}
[MapToApiVersion("1.0")]
[MapToApiVersion("1.1")]
[HttpGet]
[Authorize]
[ProducesResponseType(typeof(Models.StatisticsModel), StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status204NoContent)]
[ProducesResponseType(StatusCodes.Status404NotFound)]
[Route("Games/{GameId}")]
public async Task<ActionResult> GetStatistics(long GameId)
{
var user = await _userManager.GetUserAsync(User);
if (user != null)
{
Statistics statistics = new Statistics();
StatisticsModel? model = statistics.GetSession(GameId, user.Id);
if (model == null)
{
return NoContent();
}
else
{
return Ok(model);
}
}
else
{
return Unauthorized();
}
}
}
}

View File

@@ -42,6 +42,7 @@ namespace gaseous_server.Models
// exists // exists
if (ResetToDefault == false) if (ResetToDefault == false)
{ {
WriteAvailableEmulators(mapItem);
Logging.Log(Logging.LogType.Information, "Platform Map", "Skipping import of " + mapItem.IGDBName + " - already in database."); Logging.Log(Logging.LogType.Information, "Platform Map", "Skipping import of " + mapItem.IGDBName + " - already in database.");
} }
else else
@@ -253,6 +254,30 @@ namespace gaseous_server.Models
} }
} }
public static void WriteAvailableEmulators (PlatformMapItem item)
{
Database db = new Database(Database.databaseType.MySql, Config.DatabaseConfiguration.ConnectionString);
string sql = "";
Dictionary<string, object> dbDict = new Dictionary<string, object>();
sql = "UPDATE PlatformMap SET RetroPieDirectoryName=@RetroPieDirectoryName, WebEmulator_Type=@WebEmulator_Type, WebEmulator_Core=@WebEmulator_Core, AvailableWebEmulators=@AvailableWebEmulators WHERE Id = @Id; ";
dbDict.Add("Id", item.IGDBId);
dbDict.Add("RetroPieDirectoryName", item.RetroPieDirectoryName);
if (item.WebEmulator != null)
{
dbDict.Add("WebEmulator_Type", item.WebEmulator.Type);
dbDict.Add("WebEmulator_Core", item.WebEmulator.Core);
dbDict.Add("AvailableWebEmulators", Newtonsoft.Json.JsonConvert.SerializeObject(item.WebEmulator.AvailableWebEmulators));
}
else
{
dbDict.Add("WebEmulator_Type", "");
dbDict.Add("WebEmulator_Core", "");
dbDict.Add("AvailableWebEmulators", "");
}
db.ExecuteCMD(sql, dbDict);
}
static PlatformMapItem BuildPlatformMapItem(DataRow row) static PlatformMapItem BuildPlatformMapItem(DataRow row)
{ {
long IGDBId = (long)row["Id"]; long IGDBId = (long)row["Id"];

View File

@@ -0,0 +1,17 @@
namespace gaseous_server.Models
{
public class StatisticsModel
{
public Guid SessionId { get; set; } = Guid.Empty;
public long GameId { get; set; }
public DateTime SessionStart { get; set; }
public int SessionLength { get; set; }
public DateTime SessionEnd
{
get
{
return SessionStart.AddMinutes(SessionLength);
}
}
}
}

View File

@@ -2,6 +2,8 @@
using System.ComponentModel.Design.Serialization; using System.ComponentModel.Design.Serialization;
using System.Data; using System.Data;
using gaseous_server.Classes; using gaseous_server.Classes;
using gaseous_server.Controllers;
using Microsoft.AspNetCore.Identity.UI.V4.Pages.Account.Manage.Internal;
using NuGet.Common; using NuGet.Common;
using NuGet.Packaging; using NuGet.Packaging;
@@ -13,25 +15,63 @@ namespace gaseous_server
public class QueueItem public class QueueItem
{ {
public QueueItem(QueueItemType ItemType, bool AllowManualStart = true, bool RemoveWhenStopped = false)
{
_ItemType = ItemType;
_ItemState = QueueItemState.NeverStarted;
_LastRunTime = Config.ReadSetting<DateTime>("LastRun_" + _ItemType.ToString(), DateTime.UtcNow.AddMinutes(-5));
_AllowManualStart = AllowManualStart;
_RemoveWhenStopped = RemoveWhenStopped;
// load queueitem configuration
BackgroundTaskItem defaultItem = new BackgroundTaskItem(ItemType);
Enabled(defaultItem.Enabled);
_Interval = defaultItem.Interval;
_AllowedDays = defaultItem.AllowedDays;
AllowedStartHours = defaultItem.AllowedStartHours;
AllowedStartMinutes = defaultItem.AllowedStartMinutes;
AllowedEndHours = defaultItem.AllowedEndHours;
AllowedEndMinutes = defaultItem.AllowedEndMinutes;
_Blocks = defaultItem.Blocks;
}
public QueueItem(QueueItemType ItemType, int ExecutionInterval, bool AllowManualStart = true, bool RemoveWhenStopped = false) public QueueItem(QueueItemType ItemType, int ExecutionInterval, bool AllowManualStart = true, bool RemoveWhenStopped = false)
{ {
_ItemType = ItemType; _ItemType = ItemType;
_ItemState = QueueItemState.NeverStarted; _ItemState = QueueItemState.NeverStarted;
_LastRunTime = DateTime.Parse(Config.ReadSetting("LastRun_" + _ItemType.ToString(), DateTime.UtcNow.ToString("yyyy-MM-ddThh:mm:ssZ"))).AddMinutes(-5); _LastRunTime = Config.ReadSetting<DateTime>("LastRun_" + _ItemType.ToString(), DateTime.UtcNow.AddMinutes(-5));
_Interval = ExecutionInterval; _Interval = ExecutionInterval;
_AllowManualStart = AllowManualStart; _AllowManualStart = AllowManualStart;
_RemoveWhenStopped = RemoveWhenStopped; _RemoveWhenStopped = RemoveWhenStopped;
// load timing defaults
BackgroundTaskItem defaultItem = new BackgroundTaskItem(ItemType);
Enabled(defaultItem.Enabled);
_AllowedDays = defaultItem.AllowedDays;
AllowedStartHours = defaultItem.AllowedStartHours;
AllowedStartMinutes = defaultItem.AllowedStartMinutes;
AllowedEndHours = defaultItem.AllowedEndHours;
AllowedEndMinutes = defaultItem.AllowedEndMinutes;
} }
public QueueItem(QueueItemType ItemType, int ExecutionInterval, List<QueueItemType> Blocks, bool AllowManualStart = true, bool RemoveWhenStopped = false) public QueueItem(QueueItemType ItemType, int ExecutionInterval, List<QueueItemType> Blocks, bool AllowManualStart = true, bool RemoveWhenStopped = false)
{ {
_ItemType = ItemType; _ItemType = ItemType;
_ItemState = QueueItemState.NeverStarted; _ItemState = QueueItemState.NeverStarted;
_LastRunTime = DateTime.Parse(Config.ReadSetting("LastRun_" + _ItemType.ToString(), DateTime.UtcNow.ToString("yyyy-MM-ddThh:mm:ssZ"))).AddMinutes(-5); _LastRunTime = Config.ReadSetting<DateTime>("LastRun_" + _ItemType.ToString(), DateTime.UtcNow.AddMinutes(-5));
_Interval = ExecutionInterval; _Interval = ExecutionInterval;
_AllowManualStart = AllowManualStart; _AllowManualStart = AllowManualStart;
_RemoveWhenStopped = RemoveWhenStopped; _RemoveWhenStopped = RemoveWhenStopped;
_Blocks = Blocks; _Blocks = Blocks;
// load timing defaults
BackgroundTaskItem defaultItem = new BackgroundTaskItem(ItemType);
Enabled(defaultItem.Enabled);
_AllowedDays = defaultItem.AllowedDays;
AllowedStartHours = defaultItem.AllowedStartHours;
AllowedStartMinutes = defaultItem.AllowedStartMinutes;
AllowedEndHours = defaultItem.AllowedEndHours;
AllowedEndMinutes = defaultItem.AllowedEndMinutes;
} }
private QueueItemType _ItemType = QueueItemType.NotConfigured; private QueueItemType _ItemType = QueueItemType.NotConfigured;
@@ -42,13 +82,15 @@ namespace gaseous_server
{ {
get get
{ {
return DateTime.Parse(Config.ReadSetting("LastRun_" + _ItemType.ToString(), DateTime.UtcNow.ToString("yyyy-MM-ddThh:mm:ssZ"))); // return DateTime.Parse(Config.ReadSetting("LastRun_" + _ItemType.ToString(), DateTime.UtcNow.ToString("yyyy-MM-ddThh:mm:ssZ")));
return Config.ReadSetting<DateTime>("LastRun_" + _ItemType.ToString(), DateTime.UtcNow);
} }
set set
{ {
if (_SaveLastRunTime == true) if (_SaveLastRunTime == true)
{ {
Config.SetSetting("LastRun_" + _ItemType.ToString(), value.ToString("yyyy-MM-ddThh:mm:ssZ")); //Config.SetSetting("LastRun_" + _ItemType.ToString(), value.ToString("yyyy-MM-ddThh:mm:ssZ"));
Config.SetSetting<DateTime>("LastRun_" + _ItemType.ToString(), value);
} }
} }
} }
@@ -61,8 +103,33 @@ namespace gaseous_server
private bool _RemoveWhenStopped = false; private bool _RemoveWhenStopped = false;
private bool _IsBlocked = false; private bool _IsBlocked = false;
private string _CorrelationId = ""; private string _CorrelationId = "";
private List<DayOfWeek> _AllowedDays = new List<DayOfWeek>
{
DayOfWeek.Sunday,
DayOfWeek.Monday,
DayOfWeek.Tuesday,
DayOfWeek.Wednesday,
DayOfWeek.Thursday,
DayOfWeek.Friday,
DayOfWeek.Saturday
};
private List<QueueItemType> _Blocks = new List<QueueItemType>(); private List<QueueItemType> _Blocks = new List<QueueItemType>();
public List<DayOfWeek> AllowedDays
{
get
{
return _AllowedDays;
}
set
{
_AllowedDays = value;
}
}
public int AllowedStartHours { get; set; } = 0;
public int AllowedStartMinutes { get; set; } = 0;
public int AllowedEndHours { get; set; } = 23;
public int AllowedEndMinutes { get; set; } = 59;
public QueueItemType ItemType => _ItemType; public QueueItemType ItemType => _ItemType;
public QueueItemState ItemState => _ItemState; public QueueItemState ItemState => _ItemState;
public DateTime LastRunTime => _LastRunTime; public DateTime LastRunTime => _LastRunTime;
@@ -72,9 +139,56 @@ namespace gaseous_server
{ {
get get
{ {
return LastRunTime.AddMinutes(Interval); // next run time
DateTime tempNextRun = LastRunTime.ToLocalTime().AddMinutes(Interval);
// if (tempNextRun < DateTime.Now)
// {
// tempNextRun = DateTime.Now;
// }
DayOfWeek nextWeekDay = tempNextRun.DayOfWeek;
// create local start and end times
DateTime tempStartTime = new DateTime(tempNextRun.Year, tempNextRun.Month, tempNextRun.Day, AllowedStartHours, AllowedStartMinutes, 0, DateTimeKind.Local);
DateTime tempEndTime = new DateTime(tempNextRun.Year, tempNextRun.Month, tempNextRun.Day, AllowedEndHours, AllowedEndMinutes, 0, DateTimeKind.Local);
// bump the next run time to the next allowed day and hour range
if (AllowedDays.Contains(nextWeekDay))
{
// next run day is allowed, nothing to do
}
else
{
// keep bumping the day forward until the a weekday is found
do
{
tempNextRun = tempNextRun.AddDays(1);
nextWeekDay = tempNextRun.DayOfWeek;
}
while (!AllowedDays.Contains(nextWeekDay));
// update windows
tempStartTime = new DateTime(tempNextRun.Year, tempNextRun.Month, tempNextRun.Day, AllowedStartHours, AllowedStartMinutes, 0, DateTimeKind.Local);
tempEndTime = new DateTime(tempNextRun.Year, tempNextRun.Month, tempNextRun.Day, AllowedEndHours, AllowedEndMinutes, 0, DateTimeKind.Local);
}
// are the hours in the right range
TimeSpan spanNextRun = tempNextRun.TimeOfDay;
if (LastRunTime.ToLocalTime().AddMinutes(Interval) < tempStartTime)
{
return tempStartTime.ToUniversalTime();
}
else if (spanNextRun >= tempStartTime.TimeOfDay && spanNextRun <= tempEndTime.TimeOfDay)
{
// all good - return nextRun
return tempNextRun.ToUniversalTime();
}
else
{
return tempStartTime.ToUniversalTime();
}
} }
} }
public int Interval public int Interval
{ {
get get
@@ -220,7 +334,8 @@ namespace gaseous_server
case QueueItemType.CollectionCompiler: case QueueItemType.CollectionCompiler:
Logging.Log(Logging.LogType.Debug, "Timered Event", "Starting Collection Compiler"); Logging.Log(Logging.LogType.Debug, "Timered Event", "Starting Collection Compiler");
Classes.Collections.CompileCollections((long)Options); Dictionary<string, object> collectionOptions = (Dictionary<string, object>)Options;
Classes.Collections.CompileCollections((long)collectionOptions["Id"], (string)collectionOptions["UserId"]);
break; break;
case QueueItemType.MediaGroupCompiler: case QueueItemType.MediaGroupCompiler:
@@ -233,12 +348,25 @@ namespace gaseous_server
DatabaseMigration.UpgradeScriptBackgroundTasks(); DatabaseMigration.UpgradeScriptBackgroundTasks();
break; break;
case QueueItemType.Maintainer: case QueueItemType.DailyMaintainer:
Logging.Log(Logging.LogType.Debug, "Timered Event", "Starting Maintenance"); Logging.Log(Logging.LogType.Debug, "Timered Event", "Starting Daily Maintenance");
Classes.Maintenance maintenance = new Maintenance{ Classes.Maintenance maintenance = new Maintenance{
CallingQueueItem = this CallingQueueItem = this
}; };
maintenance.RunMaintenance(); maintenance.RunDailyMaintenance();
_SaveLastRunTime = true;
break;
case QueueItemType.WeeklyMaintainer:
Logging.Log(Logging.LogType.Debug, "Timered Event", "Starting Weekly Maintenance");
Classes.Maintenance weeklyMaintenance = new Maintenance{
CallingQueueItem = this
};
weeklyMaintenance.RunWeeklyMaintenance();
_SaveLastRunTime = true;
break; break;
case QueueItemType.TempCleanup: case QueueItemType.TempCleanup:
@@ -277,7 +405,14 @@ namespace gaseous_server
} }
_ForceExecute = false; _ForceExecute = false;
_ItemState = QueueItemState.Stopped; if (_DisableWhenComplete == false)
{
_ItemState = QueueItemState.Stopped;
}
else
{
_ItemState = QueueItemState.Disabled;
}
_LastFinishTime = DateTime.UtcNow; _LastFinishTime = DateTime.UtcNow;
_LastRunDuration = Math.Round((DateTime.UtcNow - _LastRunTime).TotalSeconds, 2); _LastRunDuration = Math.Round((DateTime.UtcNow - _LastRunTime).TotalSeconds, 2);
@@ -296,6 +431,26 @@ namespace gaseous_server
_IsBlocked = BlockState; _IsBlocked = BlockState;
} }
private bool _DisableWhenComplete = false;
public void Enabled(bool Enabled)
{
_DisableWhenComplete = !Enabled;
if (Enabled == true)
{
if (_ItemState == QueueItemState.Disabled)
{
_ItemState = QueueItemState.Stopped;
}
}
else
{
if (_ItemState == QueueItemState.Stopped || _ItemState == QueueItemState.NeverStarted)
{
_ItemState = QueueItemState.Disabled;
}
}
}
public HasErrorsItem HasErrors public HasErrorsItem HasErrors
{ {
get get
@@ -420,9 +575,14 @@ namespace gaseous_server
BackgroundDatabaseUpgrade, BackgroundDatabaseUpgrade,
/// <summary> /// <summary>
/// Performs a clean up of old files, and optimises the database /// Performs a clean up of old files, and purge old logs
/// </summary> /// </summary>
Maintainer, DailyMaintainer,
/// <summary>
/// Performs more intensive cleanups and optimises the database
/// </summary>
WeeklyMaintainer,
/// <summary> /// <summary>
/// Cleans up marked paths in the temporary directory /// Cleans up marked paths in the temporary directory

View File

@@ -3,18 +3,14 @@ using System.Text.Json.Serialization;
using gaseous_server; using gaseous_server;
using gaseous_server.Classes; using gaseous_server.Classes;
using gaseous_server.Models; using gaseous_server.Models;
using gaseous_server.SignatureIngestors.XML;
using Microsoft.AspNetCore.Http.Features; using Microsoft.AspNetCore.Http.Features;
using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.Versioning;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.AspNetCore.Server.Kestrel.Core; using Microsoft.AspNetCore.Server.Kestrel.Core;
using Microsoft.OpenApi.Models; using Microsoft.OpenApi.Models;
using Authentication; using Authentication;
using Microsoft.AspNetCore.Identity; using Microsoft.AspNetCore.Identity;
using Microsoft.AspNetCore.Identity.UI.Services;
using IGDB.Models;
using gaseous_server.Classes.Metadata; using gaseous_server.Classes.Metadata;
using Asp.Versioning;
Logging.WriteToDiskOnly = true; Logging.WriteToDiskOnly = true;
Logging.Log(Logging.LogType.Information, "Startup", "Starting Gaseous Server " + Assembly.GetExecutingAssembly().GetName().Version); Logging.Log(Logging.LogType.Information, "Startup", "Starting Gaseous Server " + Assembly.GetExecutingAssembly().GetName().Version);
@@ -55,15 +51,6 @@ Communications.MetadataSource = Config.MetadataConfiguration.MetadataSource;
// set up hasheous client // set up hasheous client
HasheousClient.WebApp.HttpHelper.BaseUri = Config.MetadataConfiguration.HasheousHost; HasheousClient.WebApp.HttpHelper.BaseUri = Config.MetadataConfiguration.HasheousHost;
// set initial values
Guid APIKey = Guid.NewGuid();
if (Config.ReadSetting("API Key", "Test API Key") == "Test API Key")
{
// it's a new api key save it
Logging.Log(Logging.LogType.Information, "Startup", "Setting initial API key");
Config.SetSetting("API Key", APIKey.ToString());
}
// clean up storage // clean up storage
if (Directory.Exists(Config.LibraryConfiguration.LibraryTempDirectory)) if (Directory.Exists(Config.LibraryConfiguration.LibraryTempDirectory))
{ {
@@ -140,12 +127,7 @@ builder.Services.AddApiVersioning(config =>
config.ApiVersionReader = ApiVersionReader.Combine(new UrlSegmentApiVersionReader(), config.ApiVersionReader = ApiVersionReader.Combine(new UrlSegmentApiVersionReader(),
new HeaderApiVersionReader("x-api-version"), new HeaderApiVersionReader("x-api-version"),
new MediaTypeApiVersionReader("x-api-version")); new MediaTypeApiVersionReader("x-api-version"));
}); }).AddApiExplorer(setup =>
// builder.Services.AddApiVersioning(setup =>
// {
// setup.ApiVersionReader = new UrlSegmentApiVersionReader();
// });
builder.Services.AddVersionedApiExplorer(setup =>
{ {
setup.GroupNameFormat = "'v'VVV"; setup.GroupNameFormat = "'v'VVV";
setup.SubstituteApiVersionInUrl = true; setup.SubstituteApiVersionInUrl = true;
@@ -242,15 +224,6 @@ builder.Services.ConfigureApplicationCookie(options =>
options.Cookie.SecurePolicy = CookieSecurePolicy.SameAsRequest; options.Cookie.SecurePolicy = CookieSecurePolicy.SameAsRequest;
options.Cookie.SameSite = SameSiteMode.Strict; options.Cookie.SameSite = SameSiteMode.Strict;
}); });
// builder.Services.AddIdentityCore<ApplicationUser>(options => {
// options.SignIn.RequireConfirmedAccount = false;
// options.User.RequireUniqueEmail = true;
// options.Password.RequireDigit = false;
// options.Password.RequiredLength = 10;
// options.Password.RequireNonAlphanumeric = false;
// options.Password.RequireUppercase = false;
// options.Password.RequireLowercase = false;
// });
builder.Services.AddScoped<UserStore>(); builder.Services.AddScoped<UserStore>();
builder.Services.AddScoped<RoleStore>(); builder.Services.AddScoped<RoleStore>();
@@ -277,8 +250,16 @@ var app = builder.Build();
app.UseSwagger(); app.UseSwagger();
app.UseSwaggerUI(options => app.UseSwaggerUI(options =>
{ {
options.SwaggerEndpoint($"/swagger/v1/swagger.json", "v1.0"); // options.SwaggerEndpoint($"/swagger/v1/swagger.json", "v1.0");
options.SwaggerEndpoint($"/swagger/v1.1/swagger.json", "v1.1"); // options.SwaggerEndpoint($"/swagger/v1.1/swagger.json", "v1.1");
var descriptions = app.DescribeApiVersions();
foreach (var description in descriptions)
{
var url = $"/swagger/{description.GroupName}/swagger.json";
var name = description.GroupName.ToUpperInvariant();
options.SwaggerEndpoint(url, name);
}
} }
); );
//} //}
@@ -421,69 +402,38 @@ var platformMap = PlatformMapping.PlatformMap;
// add background tasks // add background tasks
ProcessQueue.QueueItems.Add(new ProcessQueue.QueueItem( ProcessQueue.QueueItems.Add(new ProcessQueue.QueueItem(
ProcessQueue.QueueItemType.SignatureIngestor, ProcessQueue.QueueItemType.SignatureIngestor)
int.Parse(Config.ReadSetting("Interval_SignatureIngestor", "60"))
)
); );
ProcessQueue.QueueItems.Add(new ProcessQueue.QueueItem( ProcessQueue.QueueItems.Add(new ProcessQueue.QueueItem(
ProcessQueue.QueueItemType.TitleIngestor, ProcessQueue.QueueItemType.TitleIngestor)
int.Parse(Config.ReadSetting("Interval_TitleIngestor", "1")),
new List<ProcessQueue.QueueItemType>
{
ProcessQueue.QueueItemType.OrganiseLibrary,
ProcessQueue.QueueItemType.LibraryScan
})
); );
ProcessQueue.QueueItems.Add(new ProcessQueue.QueueItem( ProcessQueue.QueueItems.Add(new ProcessQueue.QueueItem(
ProcessQueue.QueueItemType.MetadataRefresh, ProcessQueue.QueueItemType.MetadataRefresh)
int.Parse(Config.ReadSetting("Interval_MetadataRefresh", "360"))
)
); );
ProcessQueue.QueueItems.Add(new ProcessQueue.QueueItem( ProcessQueue.QueueItems.Add(new ProcessQueue.QueueItem(
ProcessQueue.QueueItemType.OrganiseLibrary, ProcessQueue.QueueItemType.OrganiseLibrary)
int.Parse(Config.ReadSetting("Interval_OrganiseLibrary", "1440")),
new List<ProcessQueue.QueueItemType>
{
ProcessQueue.QueueItemType.LibraryScan,
ProcessQueue.QueueItemType.TitleIngestor,
ProcessQueue.QueueItemType.Rematcher
})
); );
ProcessQueue.QueueItems.Add(new ProcessQueue.QueueItem( ProcessQueue.QueueItems.Add(new ProcessQueue.QueueItem(
ProcessQueue.QueueItemType.LibraryScan, ProcessQueue.QueueItemType.LibraryScan)
int.Parse(Config.ReadSetting("Interval_LibraryScan", "1440")),
new List<ProcessQueue.QueueItemType>
{
ProcessQueue.QueueItemType.OrganiseLibrary,
ProcessQueue.QueueItemType.Rematcher
})
); );
ProcessQueue.QueueItems.Add(new ProcessQueue.QueueItem( ProcessQueue.QueueItems.Add(new ProcessQueue.QueueItem(
ProcessQueue.QueueItemType.Rematcher, ProcessQueue.QueueItemType.Rematcher)
int.Parse(Config.ReadSetting("Interval_Rematcher", "1440")),
new List<ProcessQueue.QueueItemType>
{
ProcessQueue.QueueItemType.OrganiseLibrary,
ProcessQueue.QueueItemType.LibraryScan
})
);
ProcessQueue.QueueItems.Add(new ProcessQueue.QueueItem(
ProcessQueue.QueueItemType.Maintainer,
int.Parse(Config.ReadSetting("Interval_Maintainer", "10080")),
new List<ProcessQueue.QueueItemType>
{
ProcessQueue.QueueItemType.All
})
); );
ProcessQueue.QueueItem tempCleanup = new ProcessQueue.QueueItem( // maintenance tasks
ProcessQueue.QueueItemType.TempCleanup, ProcessQueue.QueueItem dailyMaintenance = new ProcessQueue.QueueItem(
1, ProcessQueue.QueueItemType.DailyMaintainer
new List<ProcessQueue.QueueItemType>(), );
false, ProcessQueue.QueueItems.Add(dailyMaintenance);
false
ProcessQueue.QueueItem weeklyMaintenance = new ProcessQueue.QueueItem(
ProcessQueue.QueueItemType.WeeklyMaintainer
);
ProcessQueue.QueueItems.Add(weeklyMaintenance);
ProcessQueue.QueueItem tempCleanup = new ProcessQueue.QueueItem(
ProcessQueue.QueueItemType.TempCleanup
); );
tempCleanup.ForceExecute();
ProcessQueue.QueueItems.Add(tempCleanup); ProcessQueue.QueueItems.Add(tempCleanup);
Logging.WriteToDiskOnly = false; Logging.WriteToDiskOnly = false;

View File

@@ -1,41 +1,15 @@
{ {
"$schema": "https://json.schemastore.org/launchsettings.json", "$schema": "https://json.schemastore.org/launchsettings.json",
"iisSettings": {
"windowsAuthentication": false,
"anonymousAuthentication": true,
"iisExpress": {
"applicationUrl": "http://localhost:38715",
"sslPort": 44314
}
},
"profiles": { "profiles": {
"http": { "http": {
"commandName": "Project", "commandName": "Project",
"launchBrowser": true, "launchBrowser": true,
"launchUrl": "swagger", "launchUrl": "/",
"applicationUrl": "http://localhost:5198", "applicationUrl": "http://localhost:5198",
"environmentVariables": { "environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development" "ASPNETCORE_ENVIRONMENT": "Development"
}, },
"dotnetRunMessages": true "dotnetRunMessages": true
},
"https": {
"commandName": "Project",
"launchBrowser": true,
"launchUrl": "swagger",
"applicationUrl": "https://localhost:7282;http://localhost:5198",
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development"
},
"dotnetRunMessages": true
},
"IIS Express": {
"commandName": "IISExpress",
"launchBrowser": true,
"launchUrl": "swagger",
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development"
}
} }
} }
} }

View File

@@ -26,18 +26,17 @@ SELECT
* *
FROM FROM
(SELECT DISTINCT (SELECT DISTINCT
row_number() over (partition by Id order by AgeGroupId desc) as seqnum, view_GamesWithRoms.*, row_number() over (partition by Id order by AgeGroup.AgeGroupId desc) as seqnum, view_GamesWithRoms.*,
(SELECT AgeGroup.AgeGroupId AS AgeGroupId
AgeGroupId
FROM
ClassificationMap
WHERE
RatingId = AgeRating.Rating
ORDER BY AgeGroupId DESC) AgeGroupId
FROM FROM
view_GamesWithRoms view_GamesWithRoms
LEFT JOIN Relation_Game_AgeRatings ON view_GamesWithRoms.Id = Relation_Game_AgeRatings.GameId LEFT JOIN Relation_Game_AgeRatings ON view_GamesWithRoms.Id = Relation_Game_AgeRatings.GameId
LEFT JOIN AgeRating ON Relation_Game_AgeRatings.AgeRatingsId = AgeRating.Id LEFT JOIN AgeRating ON Relation_Game_AgeRatings.AgeRatingsId = AgeRating.Id
LEFT JOIN (SELECT
AgeGroupId, RatingId
FROM
ClassificationMap
ORDER BY AgeGroupId DESC) AgeGroup ON AgeRating.Rating = AgeGroup.RatingId
) g ) g
WHERE g.seqnum = 1; WHERE g.seqnum = 1;

View File

@@ -0,0 +1,6 @@
ALTER TABLE `Settings`
ADD COLUMN `ValueType` INT NULL DEFAULT 0 AFTER `Setting`,
ADD COLUMN `ValueDate` DATETIME NULL DEFAULT NULL AFTER `Value`;
ALTER TABLE `GameState`
ADD COLUMN `Zipped` BOOLEAN NOT NULL DEFAULT 0;

View File

@@ -0,0 +1,4 @@
ALTER TABLE `RomCollections`
ADD COLUMN `OwnedBy` VARCHAR(45) NULL,
ADD COLUMN `AgeGroup` INT NULL,
ADD COLUMN `AgeGroupUnclassified` BOOLEAN NULL;

View File

@@ -0,0 +1,17 @@
ALTER TABLE `Collection`
ADD COLUMN `AsParentRelations` LONGTEXT NULL AFTER `Id`,
ADD COLUMN `AsChildRelations` LONGTEXT NULL AFTER `AsParentRelations`,
ADD COLUMN `Type` INT NULL AFTER `UpdatedAt`;
ALTER TABLE `Cover`
ADD COLUMN `GameLocalization` BIGINT NULL AFTER `Game`;
ALTER TABLE `Game`
ADD COLUMN `Collections` LONGTEXT NULL AFTER `Collection`,
ADD COLUMN `ExpandedGames` LONGTEXT NULL AFTER `Dlcs`,
ADD COLUMN `Forks` LONGTEXT NULL AFTER `Follows`,
ADD COLUMN `GameLocalizations` LONGTEXT NULL AFTER `GameEngines`,
ADD COLUMN `LanguageSupports` LONGTEXT NULL AFTER `Keywords`,
ADD COLUMN `Ports` LONGTEXT NULL AFTER `PlayerPerspectives`,
ADD COLUMN `Remakes` LONGTEXT NULL AFTER `ReleaseDates`,
ADD COLUMN `Remasters` LONGTEXT NULL AFTER `Remakes`;

View File

@@ -0,0 +1,11 @@
CREATE TABLE `UserAvatars` (
`UserId` VARCHAR(128) NOT NULL,
`Id` VARCHAR(45) NOT NULL,
`Avatar` LONGBLOB NULL,
PRIMARY KEY (`UserId`),
INDEX `idx_AvatarId` (`Id` ASC) VISIBLE,
CONSTRAINT `ApplicationUser_Avatar`
FOREIGN KEY (`UserId`)
REFERENCES `Users` (`Id`)
ON DELETE CASCADE
ON UPDATE NO ACTION);

View File

@@ -0,0 +1,13 @@
CREATE TABLE `UserTimeTracking` (
`GameId` BIGINT NULL,
`UserId` VARCHAR(45) NULL,
`SessionId` VARCHAR(45) NULL,
`SessionTime` DATETIME NULL,
`SessionLength` INT NULL,
INDEX `UserId_idx` (`UserId` ASC) VISIBLE,
INDEX `SessionId_idx` (`SessionId` ASC) VISIBLE,
CONSTRAINT `UserId`
FOREIGN KEY (`UserId`)
REFERENCES `Users` (`Id`)
ON DELETE CASCADE
ON UPDATE NO ACTION);

View File

@@ -0,0 +1,8 @@
CREATE TABLE `Favourites` (
`UserId` varchar(45) NOT NULL,
`GameId` bigint(20) NOT NULL,
PRIMARY KEY (`UserId`,`GameId`),
KEY `idx_GameId` (`GameId`),
KEY `idx_UserId` (`UserId`),
CONSTRAINT `ApplicationUser_Favourite` FOREIGN KEY (`UserId`) REFERENCES `Users` (`Id`) ON DELETE CASCADE ON UPDATE NO ACTION
);

View File

@@ -0,0 +1,49 @@
CREATE TABLE `Relation_Game_AgeRatings` (
`GameId` BIGINT NOT NULL,
`AgeRatingsId` BIGINT NOT NULL,
PRIMARY KEY (`GameId`, `AgeRatingsId`),
INDEX `idx_PrimaryColumn` (`GameId` ASC) VISIBLE
);
CREATE TABLE `Relation_Game_Genres` (
`GameId` BIGINT NOT NULL,
`GenresId` BIGINT NOT NULL,
PRIMARY KEY (`GameId`, `GenresId`),
INDEX `idx_PrimaryColumn` (`GameId` ASC) VISIBLE
);
CREATE TABLE `Relation_Game_GameModes` (
`GameId` BIGINT NOT NULL,
`GameModesId` BIGINT NOT NULL,
PRIMARY KEY (`GameId`, `GameModesId`),
INDEX `idx_PrimaryColumn` (`GameId` ASC) VISIBLE
);
CREATE TABLE `Relation_Game_PlayerPerspectives` (
`GameId` BIGINT NOT NULL,
`PlayerPerspectivesId` BIGINT NOT NULL,
PRIMARY KEY (`GameId`, `PlayerPerspectivesId`),
INDEX `idx_PrimaryColumn` (`GameId` ASC) VISIBLE
);
CREATE TABLE `Relation_Game_Themes` (
`GameId` BIGINT NOT NULL,
`ThemesId` BIGINT NOT NULL,
PRIMARY KEY (`GameId`, `ThemesId`),
INDEX `idx_PrimaryColumn` (`GameId` ASC) VISIBLE
);
ALTER TABLE `Games_Roms`
ADD COLUMN `LastMatchAttemptDate` DATETIME NULL AFTER `LibraryId`;
CREATE TABLE `RomMediaGroup` (
`Id` BIGINT NOT NULL AUTO_INCREMENT,
`Status` INT NULL,
`PlatformId` BIGINT NULL,
`GameId` BIGINT NULL,
PRIMARY KEY (`Id`));
CREATE TABLE `RomMediaGroup_Members` (
`GroupId` BIGINT NOT NULL,
`RomId` BIGINT NOT NULL,
PRIMARY KEY (`GroupId`, `RomId`));

View File

@@ -352,6 +352,9 @@
}, },
{ {
"core": "fbalpha2012_cps2" "core": "fbalpha2012_cps2"
},
{
"core": "mame"
} }
] ]
} }
@@ -717,8 +720,11 @@
"availableWebEmulatorCores": [ "availableWebEmulatorCores": [
{ {
"core": "c64", "core": "c64",
"alternateCoreName": "vice_x64", "alternateCoreName": "vice_x64sc",
"default": true "default": true
},
{
"core": "vice_x64"
} }
] ]
} }
@@ -747,6 +753,169 @@
} }
] ]
}, },
{
"igdbId": 90,
"igdbName": "Commodore PET",
"igdbSlug": "cpet",
"alternateNames": [
"cpet",
"PET",
"Commodore PET"
],
"extensions": {
"supportedFileExtensions": [
".CRT",
".D64",
".D80",
".D81",
".G64",
".M3U",
".PRG",
".T64",
".TAP",
".X64",
".ZIP"
],
"uniqueFileExtensions": [
".CRT",
".D64",
".D80",
".D81",
".G64",
".PRG",
".T64",
".X64"
]
},
"retroPieDirectoryName": "c64",
"webEmulator": {
"type": "EmulatorJS",
"core": "vice_xpet",
"availableWebEmulators": [
{
"emulatorType": "EmulatorJS",
"availableWebEmulatorCores": [
{
"core": "pet",
"alternateCoreName": "vice_xpet",
"default": true
}
]
}
]
},
"bios": [
]
},
{
"igdbId": 94,
"igdbName": "Commodore Plus/4",
"igdbSlug": "c-plus-4",
"alternateNames": [
"C+4",
"c-plus-4",
"Plus/4",
"Commodore Plus/4"
],
"extensions": {
"supportedFileExtensions": [
".CRT",
".D64",
".D80",
".D81",
".G64",
".M3U",
".PRG",
".T64",
".TAP",
".X64",
".ZIP"
],
"uniqueFileExtensions": [
".CRT",
".D64",
".D80",
".D81",
".G64",
".PRG",
".T64",
".X64"
]
},
"retroPieDirectoryName": "c64",
"webEmulator": {
"type": "EmulatorJS",
"core": "vice_xplus4",
"availableWebEmulators": [
{
"emulatorType": "EmulatorJS",
"availableWebEmulatorCores": [
{
"core": "plus4",
"alternateCoreName": "vice_xplus4",
"default": true
}
]
}
]
},
"bios": [
]
},
{
"igdbId": 71,
"igdbName": "Commodore VIC-20",
"igdbSlug": "vic-20",
"alternateNames": [
"VIC20",
"VIC-20",
"Commodore VIC-20"
],
"extensions": {
"supportedFileExtensions": [
".CRT",
".D64",
".D80",
".D81",
".G64",
".M3U",
".PRG",
".T64",
".TAP",
".X64",
".ZIP"
],
"uniqueFileExtensions": [
".CRT",
".D64",
".D80",
".D81",
".G64",
".PRG",
".T64",
".X64"
]
},
"retroPieDirectoryName": "c64",
"webEmulator": {
"type": "EmulatorJS",
"core": "vice_xvic",
"availableWebEmulators": [
{
"emulatorType": "EmulatorJS",
"availableWebEmulatorCores": [
{
"core": "vic20",
"alternateCoreName": "vice_xvic",
"default": true
}
]
}
]
},
"bios": [
]
},
{ {
"igdbId": 158, "igdbId": 158,
"igdbName": "Commodore CDTV", "igdbName": "Commodore CDTV",
@@ -1421,15 +1590,18 @@
"retroPieDirectoryName": "mastersystem", "retroPieDirectoryName": "mastersystem",
"webEmulator": { "webEmulator": {
"type": "EmulatorJS", "type": "EmulatorJS",
"core": "segaMS", "core": "picodrive",
"availableWebEmulators": [ "availableWebEmulators": [
{ {
"emulatorType": "EmulatorJS", "emulatorType": "EmulatorJS",
"availableWebEmulatorCores": [ "availableWebEmulatorCores": [
{ {
"core": "segaMS", "core": "picodrive",
"alternateCoreName": "genesis_plus_gx",
"default": true "default": true
},
{
"core": "segaMS",
"alternateCoreName": "genesis_plus_gx"
} }
] ]
} }

View File

@@ -20,8 +20,8 @@ namespace gaseous_server
//_logger.LogInformation("Timed Hosted Service running."); //_logger.LogInformation("Timed Hosted Service running.");
Logging.Log(Logging.LogType.Debug, "Background", "Starting background task monitor"); Logging.Log(Logging.LogType.Debug, "Background", "Starting background task monitor");
_timer = new Timer(DoWork, null, TimeSpan.Zero, _timer = new Timer(DoWork, null, TimeSpan.FromSeconds(10),
TimeSpan.FromSeconds(5)); TimeSpan.FromSeconds(30));
return Task.CompletedTask; return Task.CompletedTask;
} }
@@ -36,20 +36,23 @@ namespace gaseous_server
List<ProcessQueue.QueueItem> ActiveList = new List<ProcessQueue.QueueItem>(); List<ProcessQueue.QueueItem> ActiveList = new List<ProcessQueue.QueueItem>();
ActiveList.AddRange(ProcessQueue.QueueItems); ActiveList.AddRange(ProcessQueue.QueueItems);
foreach (ProcessQueue.QueueItem qi in ActiveList) { foreach (ProcessQueue.QueueItem qi in ActiveList) {
if (CheckIfProcessIsBlockedByOthers(qi) == false) { if (qi.ItemState != ProcessQueue.QueueItemState.Disabled)
qi.BlockedState(false); {
if (DateTime.UtcNow > qi.NextRunTime || qi.Force == true) if (CheckIfProcessIsBlockedByOthers(qi) == false) {
{ qi.BlockedState(false);
qi.Execute(); if (DateTime.UtcNow > qi.NextRunTime || qi.Force == true)
if (qi.RemoveWhenStopped == true && qi.ItemState == ProcessQueue.QueueItemState.Stopped)
{ {
ProcessQueue.QueueItems.Remove(qi); qi.Execute();
if (qi.RemoveWhenStopped == true && qi.ItemState == ProcessQueue.QueueItemState.Stopped)
{
ProcessQueue.QueueItems.Remove(qi);
}
} }
} }
} else
else {
{ qi.BlockedState(true);
qi.BlockedState(true); }
} }
} }
} }

View File

@@ -0,0 +1,17 @@
{
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft.AspNetCore": "Warning"
}
},
"AllowedHosts": "*",
"Kestrel": {
"Endpoints": {
"Production": {
"Url": "http://*:80"
}
}
}
}

View File

@@ -1,101 +1,112 @@
<Project Sdk="Microsoft.NET.Sdk.Web"> <Project Sdk="Microsoft.NET.Sdk.Web">
<PropertyGroup>
<PropertyGroup> <TargetFramework>net8.0</TargetFramework>
<TargetFramework>net7.0</TargetFramework> <Nullable>enable</Nullable>
<Nullable>enable</Nullable> <ImplicitUsings>enable</ImplicitUsings>
<ImplicitUsings>enable</ImplicitUsings> <RootNamespace>gaseous_server</RootNamespace>
<RootNamespace>gaseous_server</RootNamespace> </PropertyGroup>
</PropertyGroup> <PropertyGroup Condition=" '$(RunConfiguration)' == 'https' " />
<PropertyGroup Condition=" '$(RunConfiguration)' == 'http' " />
<PropertyGroup Condition=" '$(RunConfiguration)' == 'https' " /> <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
<PropertyGroup Condition=" '$(RunConfiguration)' == 'http' " /> <WarningLevel>4</WarningLevel>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' "> <DocumentationFile>bin\Debug\net8.0\gaseous-server.xml</DocumentationFile>
<WarningLevel>4</WarningLevel> </PropertyGroup>
<DocumentationFile>bin\Debug\net7.0\gaseous-server.xml</DocumentationFile> <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
</PropertyGroup> <WarningLevel>4</WarningLevel>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' "> <DocumentationFile>bin\Release\net8.0\gaseous-server.xml</DocumentationFile>
<WarningLevel>4</WarningLevel> </PropertyGroup>
<DocumentationFile>bin\Release\net7.0\gaseous-server.xml</DocumentationFile> <ItemGroup>
</PropertyGroup> <PackageReference Include="Asp.Versioning.Mvc" Version="8.0.0" />
<ItemGroup> <PackageReference Include="Asp.Versioning.Mvc.ApiExplorer" Version="8.0.0" />
<PackageReference Include="gaseous-signature-parser" Version="2.0.0" /> <PackageReference Include="gaseous-signature-parser" Version="2.1.0" />
<PackageReference Include="gaseous.IGDB" Version="1.0.1" /> <PackageReference Include="gaseous.IGDB" Version="1.0.2" />
<PackageReference Include="hasheous-client" Version="0.1.0" /> <PackageReference Include="hasheous-client" Version="0.2.0" />
<PackageReference Include="Magick.NET-Q8-AnyCPU" Version="13.5.0" /> <PackageReference Include="Magick.NET-Q8-AnyCPU" Version="13.5.0" />
<PackageReference Include="Microsoft.AspNetCore.Identity.UI" Version="7.0.13" /> <PackageReference Include="sharpcompress" Version="0.36.0" />
<PackageReference Include="Microsoft.AspNetCore.Mvc.Versioning" Version="5.1.0" /> <PackageReference Include="Squid-Box.SevenZipSharp" Version="1.6.1.23" />
<PackageReference Include="Microsoft.AspNetCore.Mvc.Versioning.ApiExplorer" Version="5.1.0" /> <PackageReference Include="Swashbuckle.AspNetCore" Version="6.5.0" />
<PackageReference Include="Microsoft.AspNetCore.OpenApi" Version="7.0.12" /> <PackageReference Include="MySqlConnector" Version="2.3.5" />
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="7.0.0" /> <PackageReference Include="Microsoft.AspNetCore.Identity.UI" Version="8.0.1" />
<PackageReference Include="sharpcompress" Version="0.35.0" /> <PackageReference Include="Microsoft.AspNetCore.OpenApi" Version="8.0.1" />
<PackageReference Include="Squid-Box.SevenZipSharp" Version="1.6.1.23" /> <PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="8.0.0" />
<PackageReference Include="Swashbuckle.AspNetCore" Version="6.5.0" /> <PackageReference Include="Microsoft.VisualStudio.Web.CodeGeneration.Design" Version="8.0.0" />
<PackageReference Include="Microsoft.VisualStudio.Web.CodeGeneration.Design" Version="7.0.10" /> <PackageReference Include="Newtonsoft.Json" Version="13.0.3" />
<PackageReference Include="MySqlConnector" Version="2.3.1" /> </ItemGroup>
<PackageReference Include="Newtonsoft.Json" Version="13.0.3" /> <ItemGroup>
</ItemGroup> <None Remove="Controllers\" />
<None Remove="Models\" />
<ItemGroup> <None Remove="Models\Signatures.Status" />
<None Remove="Controllers\" /> <None Remove="Classes\" />
<None Remove="Models\" /> <None Remove="Classes\SignatureIngestors\" />
<None Remove="Models\Signatures.Status" /> <None Remove="Support\" />
<None Remove="Classes\" /> <None Remove="Support\Database\" />
<None Remove="Classes\SignatureIngestors\" /> <None Remove="Support\Database\MySQL\" />
<None Remove="Support\" /> <None Remove="Support\Database\MySQL\gaseous-1000.sql" />
<None Remove="Support\Database\" /> <None Remove="Support\Database\MySQL\gaseous-1001.sql" />
<None Remove="Support\Database\MySQL\" /> <None Remove="Support\Database\MySQL\gaseous-1002.sql" />
<None Remove="Support\Database\MySQL\gaseous-1000.sql" /> <None Remove="Support\Database\MySQL\gaseous-1003.sql" />
<None Remove="Support\Database\MySQL\gaseous-1001.sql" /> <None Remove="Support\Database\MySQL\gaseous-1004.sql" />
<None Remove="Support\Database\MySQL\gaseous-1002.sql" /> <None Remove="Support\Database\MySQL\gaseous-fix-1005.sql" />
<None Remove="Support\Database\MySQL\gaseous-1003.sql" /> <None Remove="Support\Database\MySQL\gaseous-1005.sql" />
<None Remove="Support\Database\MySQL\gaseous-1004.sql" /> <None Remove="Support\Database\MySQL\gaseous-1006.sql" />
<None Remove="Support\Database\MySQL\gaseous-1005.sql" /> <None Remove="Support\Database\MySQL\gaseous-1007.sql" />
<None Remove="Support\Database\MySQL\gaseous-1006.sql" /> <None Remove="Support\Database\MySQL\gaseous-1008.sql" />
<None Remove="Support\Database\MySQL\gaseous-1007.sql" /> <None Remove="Support\Database\MySQL\gaseous-1009.sql" />
<None Remove="Support\Database\MySQL\gaseous-1008.sql" /> <None Remove="Support\Database\MySQL\gaseous-1010.sql" />
<None Remove="Support\Database\MySQL\gaseous-1009.sql" /> <None Remove="Support\Database\MySQL\gaseous-1011.sql" />
<None Remove="Support\Database\MySQL\gaseous-1010.sql" /> <None Remove="Support\Database\MySQL\gaseous-1012.sql" />
<None Remove="Support\Database\MySQL\gaseous-1011.sql" /> <None Remove="Support\Database\MySQL\gaseous-1013.sql" />
<None Remove="Support\Database\MySQL\gaseous-1012.sql" /> <None Remove="Support\Database\MySQL\gaseous-1014.sql" />
<None Remove="Support\Database\MySQL\gaseous-1013.sql" /> <None Remove="Support\Database\MySQL\gaseous-1015.sql" />
<None Remove="Support\Database\MySQL\gaseous-1014.sql" /> <None Remove="Support\Database\MySQL\gaseous-1016.sql" />
<None Remove="Support\Database\MySQL\gaseous-1015.sql" /> <None Remove="Support\Database\MySQL\gaseous-1017.sql" />
<None Remove="Classes\Metadata\" /> <None Remove="Support\Database\MySQL\gaseous-1018.sql" />
</ItemGroup> <None Remove="Support\Database\MySQL\gaseous-1019.sql" />
<ItemGroup> <None Remove="Support\Database\MySQL\gaseous-1020.sql" />
<Folder Include="Controllers\" /> <None Remove="Support\Database\MySQL\gaseous-1021.sql" />
<Folder Include="Models\" /> <None Remove="Classes\Metadata\" />
<Folder Include="Classes\" /> </ItemGroup>
<Folder Include="Classes\SignatureIngestors\" /> <ItemGroup>
<Folder Include="Support\" /> <Folder Include="Controllers\" />
<Folder Include="Classes\Metadata\" /> <Folder Include="Models\" />
<Folder Remove="Reference" /> <Folder Include="Classes\" />
</ItemGroup> <Folder Include="Classes\SignatureIngestors\" />
<ItemGroup> <Folder Include="Support\" />
<None Include="wwwroot\**"> <Folder Include="Classes\Metadata\" />
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory> <Folder Remove="Reference" />
</None> </ItemGroup>
</ItemGroup> <ItemGroup>
<ItemGroup> <None Include="wwwroot\**">
<EmbeddedResource Include="Support\PlatformMap.json" Condition="'$(ExcludeConfigFilesFromBuildOutput)'!='true'"> <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
<ExcludeFromSingleFile>true</ExcludeFromSingleFile> </None>
<CopyToPublishDirectory>PreserveNewest</CopyToPublishDirectory> </ItemGroup>
</EmbeddedResource> <ItemGroup>
<EmbeddedResource Include="Support\Database\MySQL\gaseous-1000.sql" /> <EmbeddedResource Include="Support\PlatformMap.json" Condition="'$(ExcludeConfigFilesFromBuildOutput)'!='true'">
<EmbeddedResource Include="Support\Database\MySQL\gaseous-1001.sql" /> <ExcludeFromSingleFile>true</ExcludeFromSingleFile>
<EmbeddedResource Include="Support\Database\MySQL\gaseous-1002.sql" /> <CopyToPublishDirectory>PreserveNewest</CopyToPublishDirectory>
<EmbeddedResource Include="Support\Database\MySQL\gaseous-1003.sql" /> </EmbeddedResource>
<EmbeddedResource Include="Support\Database\MySQL\gaseous-1004.sql" /> <EmbeddedResource Include="Support\Database\MySQL\gaseous-1000.sql" />
<EmbeddedResource Include="Support\Database\MySQL\gaseous-1005.sql" /> <EmbeddedResource Include="Support\Database\MySQL\gaseous-1001.sql" />
<EmbeddedResource Include="Support\Database\MySQL\gaseous-1006.sql" /> <EmbeddedResource Include="Support\Database\MySQL\gaseous-1002.sql" />
<EmbeddedResource Include="Support\Database\MySQL\gaseous-1007.sql" /> <EmbeddedResource Include="Support\Database\MySQL\gaseous-1003.sql" />
<EmbeddedResource Include="Support\Database\MySQL\gaseous-1008.sql" /> <EmbeddedResource Include="Support\Database\MySQL\gaseous-1004.sql" />
<EmbeddedResource Include="Support\Database\MySQL\gaseous-1009.sql" /> <EmbeddedResource Include="Support\Database\MySQL\gaseous-fix-1005.sql" />
<EmbeddedResource Include="Support\Database\MySQL\gaseous-1010.sql" /> <EmbeddedResource Include="Support\Database\MySQL\gaseous-1005.sql" />
<EmbeddedResource Include="Support\Database\MySQL\gaseous-1011.sql" /> <EmbeddedResource Include="Support\Database\MySQL\gaseous-1006.sql" />
<EmbeddedResource Include="Support\Database\MySQL\gaseous-1012.sql" /> <EmbeddedResource Include="Support\Database\MySQL\gaseous-1007.sql" />
<EmbeddedResource Include="Support\Database\MySQL\gaseous-1013.sql" /> <EmbeddedResource Include="Support\Database\MySQL\gaseous-1008.sql" />
<EmbeddedResource Include="Support\Database\MySQL\gaseous-1014.sql" /> <EmbeddedResource Include="Support\Database\MySQL\gaseous-1009.sql" />
<EmbeddedResource Include="Support\Database\MySQL\gaseous-1015.sql" /> <EmbeddedResource Include="Support\Database\MySQL\gaseous-1010.sql" />
</ItemGroup> <EmbeddedResource Include="Support\Database\MySQL\gaseous-1011.sql" />
</Project> <EmbeddedResource Include="Support\Database\MySQL\gaseous-1012.sql" />
<EmbeddedResource Include="Support\Database\MySQL\gaseous-1013.sql" />
<EmbeddedResource Include="Support\Database\MySQL\gaseous-1014.sql" />
<EmbeddedResource Include="Support\Database\MySQL\gaseous-1015.sql" />
<EmbeddedResource Include="Support\Database\MySQL\gaseous-1016.sql" />
<EmbeddedResource Include="Support\Database\MySQL\gaseous-1017.sql" />
<EmbeddedResource Include="Support\Database\MySQL\gaseous-1018.sql" />
<EmbeddedResource Include="Support\Database\MySQL\gaseous-1019.sql" />
<EmbeddedResource Include="Support\Database\MySQL\gaseous-1020.sql" />
<EmbeddedResource Include="Support\Database\MySQL\gaseous-1021.sql" />
</ItemGroup>
</Project>

Binary file not shown.

Binary file not shown.

Before

Width:  |  Height:  |  Size: 16 KiB

After

Width:  |  Height:  |  Size: 19 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 72 KiB

After

Width:  |  Height:  |  Size: 22 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 15 KiB

After

Width:  |  Height:  |  Size: 16 KiB

View File

@@ -28,7 +28,8 @@
// Path to the data directory // Path to the data directory
EJS_pathtodata = '/emulators/EmulatorJS/data/'; EJS_pathtodata = '/emulators/EmulatorJS/data/';
EJS_DEBUG_XX = false; EJS_DEBUG_XX = emulatorDebugMode;
console.log("Debug enabled: " + EJS_DEBUG_XX);
EJS_backgroundImage = emuBackground; EJS_backgroundImage = emuBackground;
EJS_backgroundBlur = true; EJS_backgroundBlur = true;

Binary file not shown.

Before

Width:  |  Height:  |  Size: 518 B

After

Width:  |  Height:  |  Size: 786 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.1 KiB

After

Width:  |  Height:  |  Size: 2.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 15 KiB

After

Width:  |  Height:  |  Size: 15 KiB

View File

@@ -0,0 +1,2 @@
<?xml version="1.0" encoding="utf-8"?><!-- Uploaded to: SVG Repo, www.svgrepo.com, Generator: SVG Repo Mixer Tools -->
<svg fill="#000000" width="800px" height="800px" viewBox="0 0 24 24" id="favourite" data-name="Line Color" xmlns="http://www.w3.org/2000/svg" class="icon line-color"><path id="primary" d="M19.57,5.44a4.91,4.91,0,0,1,0,6.93L12,20,4.43,12.37A4.91,4.91,0,0,1,7.87,4a4.9,4.9,0,0,1,3.44,1.44,4.46,4.46,0,0,1,.69.88,4.46,4.46,0,0,1,.69-.88,4.83,4.83,0,0,1,6.88,0Z" style="fill: none; stroke: rgb(0, 0, 0); stroke-linecap: round; stroke-linejoin: round; stroke-width: 2;"></path></svg>

After

Width:  |  Height:  |  Size: 597 B

View File

@@ -0,0 +1,2 @@
<?xml version="1.0" encoding="utf-8"?><!-- Uploaded to: SVG Repo, www.svgrepo.com, Generator: SVG Repo Mixer Tools -->
<svg fill="#000000" width="800px" height="800px" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg" id="favourite" class="icon glyph"><path d="M20.28,4.74a5.82,5.82,0,0,0-8.28,0,5.82,5.82,0,0,0-8.28,0,5.94,5.94,0,0,0,0,8.34l7.57,7.62a1,1,0,0,0,1.42,0l7.57-7.62a5.91,5.91,0,0,0,0-8.34Z"></path></svg>

After

Width:  |  Height:  |  Size: 422 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.2 KiB

After

Width:  |  Height:  |  Size: 11 KiB

View File

@@ -130,6 +130,8 @@
console.log("User is logged in"); console.log("User is logged in");
userProfile = result; userProfile = result;
loadAvatar(userProfile.avatar);
// hide the upload button if it's not permitted // hide the upload button if it's not permitted
var uploadButton = document.getElementById('banner_upload'); var uploadButton = document.getElementById('banner_upload');
if (!userProfile.roles.includes("Admin") && !userProfile.roles.includes("Gamer")) { if (!userProfile.roles.includes("Admin") && !userProfile.roles.includes("Gamer")) {

View File

@@ -59,7 +59,7 @@
<tr> <tr>
<th>Directory Layout</th> <th>Directory Layout</th>
<td> <td>
<select id="collection_directorylayout" style="width: 100%;" onchange="DisplayDirectoryLabel();"> <select id="collection_directorylayout" style="width: 100%;" data-minimum-results-for-search="Infinity" onchange="DisplayDirectoryLabel();">
<option id="collection_directorylayout_gaseous" selected="selected" value="Gaseous">Standard</option> <option id="collection_directorylayout_gaseous" selected="selected" value="Gaseous">Standard</option>
<option id="collection_directorylayout_retropie" value="RetroPie">RetroPie</option> <option id="collection_directorylayout_retropie" value="RetroPie">RetroPie</option>
</select> </select>
@@ -85,7 +85,7 @@
Include BIOS files (if available) Include BIOS files (if available)
</th> </th>
<td> <td>
<select id="collection_includebios" style="width: 100%;"> <select id="collection_includebios" style="width: 100%;" data-minimum-results-for-search="Infinity">
<option id="collection_includebios_yes" selected="selected" value="true">Yes</option> <option id="collection_includebios_yes" selected="selected" value="true">Yes</option>
<option id="collection_includebios_no" value="false">No</option> <option id="collection_includebios_no" value="false">No</option>
</select> </select>
@@ -589,11 +589,11 @@
gameCoverCell.className = 'collections_preview_gamecovercell'; gameCoverCell.className = 'collections_preview_gamecovercell';
var gameImage = document.createElement('img'); var gameImage = document.createElement('img');
gameImage.className = 'game_tile_image game_tile_image_small'; gameImage.className = 'game_tile_image game_tile_image_small lazy';
gameImage.src = '/images/unknowngame.png';
if (gameItem.cover) { if (gameItem.cover) {
gameImage.src = '/api/v1.1/Games/' + gameItem.id + '/cover/image/cover_small/' + gameItem.coverItem.imageId + '.jpg'; gameImage.setAttribute('data-src', '/api/v1.1/Games/' + gameItem.id + '/cover/image/cover_small/' + gameItem.coverItem.imageId + '.jpg');
} else { } else {
gameImage.src = '/images/unknowngame.png';
gameImage.className = 'game_tile_image game_tile_image_small unknown'; gameImage.className = 'game_tile_image game_tile_image_small unknown';
} }
gameCoverCell.appendChild(gameImage); gameCoverCell.appendChild(gameImage);
@@ -626,6 +626,13 @@
var collectionSize = document.getElementById('collectionedit_previewbox_size'); var collectionSize = document.getElementById('collectionedit_previewbox_size');
collectionSize.innerHTML = "Estimated uncompressed collection size: " + formatBytes(data.collectionProjectedSizeBytes); collectionSize.innerHTML = "Estimated uncompressed collection size: " + formatBytes(data.collectionProjectedSizeBytes);
} }
$('#collectionedit_previewbox .lazy').Lazy({
scrollDirection: 'vertical',
effect: 'fadeIn',
visibleOnly: true,
appendScroll: $('#collectionedit_previewbox')
});
} }
function DisplayFormattedBytes(inputElement, labelElement) { function DisplayFormattedBytes(inputElement, labelElement) {

View File

@@ -268,7 +268,7 @@
if (newPassword.length > 0) { if (newPassword.length > 0) {
if (newPassword == conPassword) { if (newPassword == conPassword) {
// check if password meets requirements // check if password meets requirements
if (newPassword.length > 10) { if (newPassword.length >= 10) {
errorLabel.innerHTML = ""; errorLabel.innerHTML = "";
submitButton.removeAttribute('disabled'); submitButton.removeAttribute('disabled');
return true; return true;

View File

@@ -49,7 +49,7 @@
if (userNameVal.includes("@")) { if (userNameVal.includes("@")) {
if (newPassword == conPassword) { if (newPassword == conPassword) {
// check if password meets requirements // check if password meets requirements
if (newPassword.length > 10) { if (newPassword.length >= 10) {
errorLabel.innerHTML = ""; errorLabel.innerHTML = "";
submitButton.removeAttribute('disabled'); submitButton.removeAttribute('disabled');
} else { } else {

View File

@@ -1,5 +1,6 @@
<div id="properties_toc"> <div id="properties_toc">
<div id="properties_profile_toc_general" name="properties_profile_toc_item" onclick="ProfileSelectTab('general');">Preferences</div> <div id="properties_profile_toc_general" name="properties_profile_toc_item" onclick="ProfileSelectTab('general');">Preferences</div>
<div id="properties_profile_toc_avatar" name="properties_profile_toc_item" onclick="ProfileSelectTab('avatar');">Avatar</div>
<div id="properties_profile_toc_account" name="properties_profile_toc_item" onclick="ProfileSelectTab('account');">Account</div> <div id="properties_profile_toc_account" name="properties_profile_toc_item" onclick="ProfileSelectTab('account');">Account</div>
</div> </div>
<div id="properties_bodypanel"> <div id="properties_bodypanel">
@@ -78,6 +79,17 @@
</tr> </tr>
</table> </table>
</div> </div>
<div id="properties_bodypanel_avatar" name="properties_profile_tab" style="display: none;">
<h3>Avatar</h3>
<div style="width: 100%; text-align: center;">
<div>
<img id="properties_bodypanel_avatar_image" style="width: 200px; height: 200px;" src="/images/user.svg"/>
</div>
<form id="properties_bodypanel_avatar_form" onsubmit="return false">
<input type="file" name="file" id="properties_bodypanel_avatar_upload" accept="image/*" /><button value="Save" onclick="SaveAvatar();">Save</button><button value="Delete" onclick="SaveAvatar(true);">Delete</button>
</form>
</div>
</div>
<div id="properties_bodypanel_account" name="properties_profile_tab" style="display: none;"> <div id="properties_bodypanel_account" name="properties_profile_tab" style="display: none;">
<h3>Reset Password</h3> <h3>Reset Password</h3>
<table style="width: 100%;"> <table style="width: 100%;">
@@ -285,7 +297,7 @@
} else { } else {
if (newPassword == conPassword) { if (newPassword == conPassword) {
// check if password meets requirements // check if password meets requirements
if (newPassword.length > 10) { if (newPassword.length >= 10) {
errorLabel.innerHTML = ""; errorLabel.innerHTML = "";
submitButton.removeAttribute('disabled'); submitButton.removeAttribute('disabled');
} else { } else {
@@ -344,8 +356,86 @@
} }
} }
function SaveAvatar(DeleteExisting) {
if (DeleteExisting == true) {
ajaxCall(
'/api/v1.1/Account/Avatar/' + userProfile.avatar + '.jpg',
'DELETE',
function (success) {
userProfile.avatar = "00000000-0000-0000-0000-000000000000";
loadAvatar(userProfile.avatar);
displayAvatarPreview("/images/user.svg");
},
function (error) {
userProfile.avatar = "00000000-0000-0000-0000-000000000000";
loadAvatar(userProfile.avatar);
displayAvatarPreview("/images/user.svg");
}
);
} else {
var form = $('#properties_bodypanel_avatar_form')[0];
var formData = new FormData(form);
formData.append("file", document.getElementById("properties_bodypanel_avatar_upload").files[0]);
$.ajax({
// Our sample url to make request
url:
'/api/v1.1/Account/Avatar',
// Type of Request
type: 'POST',
// data to send to the server
data: formData,
contentType: false,
processData: false,
// Function to call when to
// request is ok
success: function (data) {
var x = JSON.stringify(data);
console.log(x);
loadAvatar(data);
userProfile.avatar = data;
displayAvatarPreview("/api/v1.1/Account/Avatar/" + data + ".jpg");
},
// Error handling
error: function (error) {
console.log(`Error ${JSON.stringify(error)}`);
}
});
}
}
function displayAvatarPreview(previewImg) {
var previewPath;
if (previewImg) {
previewPath = previewImg;
} else {
if (userProfile.avatar == "00000000-0000-0000-0000-000000000000") {
previewPath = "/images/user.svg";
} else {
previewPath = "/api/v1.1/Account/Avatar/" + userProfile.avatar + ".jpg";
}
}
var previewElement = document.getElementById('properties_bodypanel_avatar_image')
previewElement.setAttribute("src", previewPath);
if (previewPath != "/images/user.svg") {
previewElement.style.filter = "";
} else {
previewElement.style.filter = "invert(100%)";
}
}
ProfileSelectTab('general'); ProfileSelectTab('general');
GetPrefInitialValues(); GetPrefInitialValues();
displayAvatarPreview();
$('#profile_pref-LibraryPagination').select2(); $('#profile_pref-LibraryPagination').select2();
$('#profile_pref_LibraryPrimaryClassificationBadge').select2(); $('#profile_pref_LibraryPrimaryClassificationBadge').select2();

View File

@@ -70,4 +70,30 @@
bg.setAttribute('style', 'background-image: url("/api/v1.1/Games/' + gameId + '/artwork/' + artworks[artworksPosition] + '/image/original/' + artworks[artworksPosition] + '.jpg"); background-position: center; background-repeat: no-repeat; background-size: cover; filter: blur(10px); -webkit-filter: blur(10px);'); bg.setAttribute('style', 'background-image: url("/api/v1.1/Games/' + gameId + '/artwork/' + artworks[artworksPosition] + '/image/original/' + artworks[artworksPosition] + '.jpg"); background-position: center; background-repeat: no-repeat; background-size: cover; filter: blur(10px); -webkit-filter: blur(10px);');
} }
} }
// statistics
var SessionId = undefined;
function SaveStatistics() {
var model;
if (SessionId == undefined) {
ajaxCall(
'/api/v1.1/Statistics/Games/' + gameId,
'POST',
function (success) {
SessionId = success.sessionId;
}
);
} else {
ajaxCall(
'/api/v1.1/Statistics/Games/' + gameId + '/' + SessionId,
'PUT',
function (success) {
}
);
}
}
setInterval(SaveStatistics, 60000);
</script> </script>

View File

@@ -48,7 +48,7 @@
<div id="content"> <div id="content">
<div class="loginwindow" id="first_welcome"> <div class="loginwindow" id="first_welcome">
<div id="welcomeform" class="loginwindow-content"> <div id="welcomeform" class="loginwindow-content">
<img src="/images/logo.png" style="display: block; margin: 20px auto;" /> <img src="/images/logo.png" style="display: block; margin: 20px auto; width: 100px;" />
<div id="loginwindow_header_label" style="display: block; text-align: center;">Gaseous Games</div> <div id="loginwindow_header_label" style="display: block; text-align: center;">Gaseous Games</div>
@@ -57,7 +57,7 @@
</div> </div>
<div class="loginwindow" id="first_newadmin" style="display: none;"> <div class="loginwindow" id="first_newadmin" style="display: none;">
<div id="loginform" class="loginwindow-content"> <div id="loginform" class="loginwindow-content">
<img src="/images/logo.png" style="display: block; margin: 20px auto;" /> <img src="/images/logo.png" style="display: block; margin: 20px auto; width: 100px;" />
<div id="loginwindow_header_label" style="display: block; text-align: center;">Gaseous Games</div> <div id="loginwindow_header_label" style="display: block; text-align: center;">Gaseous Games</div>
@@ -117,7 +117,7 @@
} else { } else {
if (newPassword == conPassword) { if (newPassword == conPassword) {
// check if password meets requirements // check if password meets requirements
if (newPassword.length > 10) { if (newPassword.length >= 10) {
errorLabel.innerHTML = "&nbsp;"; errorLabel.innerHTML = "&nbsp;";
submitButton.removeAttribute('disabled'); submitButton.removeAttribute('disabled');
} else { } else {

View File

@@ -63,6 +63,22 @@
<div id="gamescreenshots_gallery_panel"></div> <div id="gamescreenshots_gallery_panel"></div>
</div> </div>
</div> </div>
<div id="gamestatistics">
<div id="gamestatistics_lastplayed" class="gamestatistics_box">
<span class="gamestatistics_label">Last Played</span>
<span id="gamestatistics_lastplayed_value" class="gamestatistics_value">-</span>
</div>
<div id="gamestatistics_timeplayed" class="gamestatistics_box">
<span class="gamestatistics_label">Total Play Time</span>
<span id="gamestatistics_timeplayed_value" class="gamestatistics_value">-</span>
</div>
<div id="gamestatistics_favourite" class="gamestatistics_box">
<div id="gamestatistics_favourite_button">
</div>
</div>
</div>
<div id="gamesummarytext"> <div id="gamesummarytext">
<span id="gamesummarytext_label" class="line-clamp-4"></span> <span id="gamesummarytext_label" class="line-clamp-4"></span>
<p id="gamesummarytext_label_button_expand" class="text_link" style="display: none;" onclick="document.querySelector('#gamesummarytext_label').classList.remove('line-clamp-4'); document.querySelector('#gamesummarytext_label_button_expand').setAttribute('style', 'display: none;'); document.querySelector('#gamesummarytext_label_button_contract').setAttribute('style', '');">Read more...</p> <p id="gamesummarytext_label_button_expand" class="text_link" style="display: none;" onclick="document.querySelector('#gamesummarytext_label').classList.remove('line-clamp-4'); document.querySelector('#gamesummarytext_label_button_expand').setAttribute('style', 'display: none;'); document.querySelector('#gamesummarytext_label_button_contract').setAttribute('style', '');">Read more...</p>
@@ -118,8 +134,6 @@
var selectedScreenshot = 0; var selectedScreenshot = 0;
ajaxCall('/api/v1.1/Games/' + gameId, 'GET', function (result) { ajaxCall('/api/v1.1/Games/' + gameId, 'GET', function (result) {
console.log(result);
// populate games page // populate games page
gameData = result; gameData = result;
@@ -256,6 +270,51 @@
gamePublisherLabel.setAttribute('style', 'display: none;'); gamePublisherLabel.setAttribute('style', 'display: none;');
} }
// load statistics
ajaxCall('/api/v1.1/Statistics/Games/' + gameId, 'GET', function (result) {
var gameStat_lastPlayed = document.getElementById('gamestatistics_lastplayed_value');
var gameStat_timePlayed = document.getElementById('gamestatistics_timeplayed_value');
if (result) {
// gameStat_lastPlayed.innerHTML = moment(result.sessionEnd).format("YYYY-MM-DD h:mm:ss a");
const dateOptions = {
//weekday: 'long',
year: 'numeric',
month: 'long',
day: 'numeric'
};
gameStat_lastPlayed.innerHTML = new Date(result.sessionEnd).toLocaleDateString(undefined, dateOptions);
if (result.sessionLength >= 60) {
gameStat_timePlayed.innerHTML = Number(result.sessionLength / 60) + " hours";
} else {
gameStat_timePlayed.innerHTML = Number(result.sessionLength) + " minutes";
}
} else {
gameStat_lastPlayed.innerHTML = '-';
gameStat_timePlayed.innerHTML = '-';
}
});
// load favourites
ajaxCall('/api/v1.1/games/' + gameId + '/favourite', 'GET', function (result) {
var gameFavButton = document.getElementById('gamestatistics_favourite_button');
var gameFavIcon = document.createElement('img');
gameFavIcon.id = "gamestatistics_favourite";
gameFavIcon.className = "favouriteicon";
gameFavIcon.title = "Favourite";
gameFavIcon.alt = "Favourite";
if (result == true) {
gameFavIcon.setAttribute("src", '/images/favourite-filled.svg');
gameFavIcon.setAttribute('onclick', "SetGameFavourite(false);");
} else {
gameFavIcon.setAttribute("src", '/images/favourite-empty.svg');
gameFavIcon.setAttribute('onclick', "SetGameFavourite(true);");
}
gameFavButton.innerHTML = '';
gameFavButton.appendChild(gameFavIcon);
});
// load release date // load release date
var gameSummaryRelease = document.getElementById('gamesummary_firstrelease'); var gameSummaryRelease = document.getElementById('gamesummary_firstrelease');
if (result.firstReleaseDate) { if (result.firstReleaseDate) {
@@ -469,7 +528,6 @@
if (result.length == 0) { if (result.length == 0) {
mediaGroup.style.display = 'none'; mediaGroup.style.display = 'none';
} else { } else {
console.log(result);
mediaGroup.style.display = ''; mediaGroup.style.display = '';
mediaGroupDiv.innerHTML = ''; mediaGroupDiv.innerHTML = '';
var mgTable = document.createElement('table'); var mgTable = document.createElement('table');
@@ -965,12 +1023,9 @@
$('#rom_edit_fixplatform').on('select2:select', function (e) { $('#rom_edit_fixplatform').on('select2:select', function (e) {
var platformData = e.params.data; var platformData = e.params.data;
console.log(platformData);
var gameValue = $('#rom_edit_fixgame').select2('data'); var gameValue = $('#rom_edit_fixgame').select2('data');
if (gameValue) { if (gameValue) {
console.log(gameValue[0]);
setRomFixGameDropDown(); setRomFixGameDropDown();
} }
}); });
@@ -1136,4 +1191,30 @@
JSON.stringify(romIds) JSON.stringify(romIds)
); );
} }
function SetGameFavourite(status) {
ajaxCall(
'/api/v1.1/games/' + gameId + '/favourite?favourite=' + status,
'POST',
function (result) {
var gameFavButton = document.getElementById('gamestatistics_favourite_button');
var gameFavIcon = document.createElement('img');
gameFavIcon.id = "gamestatistics_favourite";
gameFavIcon.className = "favouriteicon";
gameFavIcon.title = "Favourite";
gameFavIcon.alt = "Favourite";
if (result == true) {
gameFavIcon.setAttribute("src", '/images/favourite-filled.svg');
gameFavIcon.setAttribute('onclick', "SetGameFavourite(false);");
} else {
gameFavIcon.setAttribute("src", '/images/favourite-empty.svg');
gameFavIcon.setAttribute('onclick', "SetGameFavourite(true);");
}
gameFavButton.innerHTML = '';
gameFavButton.appendChild(gameFavIcon);
}
);
}
</script> </script>

View File

@@ -27,6 +27,9 @@
</div> </div>
</div> </div>
<div id="games_library"></div> <div id="games_library"></div>
<div id="games_library_alpha_box">
<div id="games_library_alpha_pager"></div>
</div>
<div id="games_library_pagerstore" style="display: none;">0</div> <div id="games_library_pagerstore" style="display: none;">0</div>
</div> </div>
</div> </div>
@@ -40,7 +43,7 @@
var scrollerElement = document.getElementById('games_filter_scroller'); var scrollerElement = document.getElementById('games_filter_scroller');
formatFilterPanel(scrollerElement, result); formatFilterPanel(scrollerElement, result);
executeFilter1_1(); //executeFilter1_1();
}); });
$('#games_library_orderby_select').select2(); $('#games_library_orderby_select').select2();

View File

@@ -48,7 +48,7 @@
<div id="content"> <div id="content">
<div class="loginwindow"> <div class="loginwindow">
<div id="loginform" class="loginwindow-content"> <div id="loginform" class="loginwindow-content">
<img src="/images/logo.png" style="display: block; margin: 20px auto;" /> <img src="/images/logo.png" style="display: block; margin: 20px auto; width: 100px;" />
<div id="loginwindow_header_label" style="display: block; text-align: center;">Gaseous Games</div> <div id="loginwindow_header_label" style="display: block; text-align: center;">Gaseous Games</div>

View File

@@ -6,6 +6,10 @@
<tr> <tr>
<th style="width: 20%;">Home Page</th> <th style="width: 20%;">Home Page</th>
<td><a href="https://github.com/gaseous-project/gaseous-server" class="romlink">https://github.com/gaseous-project/gaseous-server</a></td> <td><a href="https://github.com/gaseous-project/gaseous-server" class="romlink">https://github.com/gaseous-project/gaseous-server</a></td>
<td rowspan="5" style="text-align: center; width: 128px;">
<img src="/images/logo.png" style="display: block; margin: 20px auto; width: 100px;" />
<span style="display: block;">The Gaseous logo was designed by Tom2.0</span>
</td>
</tr> </tr>
<tr> <tr>
<th>Bugs and Feature Requests</th> <th>Bugs and Feature Requests</th>
@@ -24,19 +28,19 @@
<td id="settings_dbversion"></td> <td id="settings_dbversion"></td>
</tr> </tr>
<tr> <tr>
<td colspan="2"> <td colspan="3">
<h3>Projects That Make Gaseous Possible</h3> <h3>Projects That Make Gaseous Possible</h3>
</td> </td>
</tr> </tr>
<tr> <tr>
<td style="text-align: center;"><a href="https://github.com/EmulatorJS/EmulatorJS" target="_blank"><img src="/images/EmulatorJS.png" style="height: 36px;" /></a></td> <td style="text-align: center;"><a href="https://github.com/EmulatorJS/EmulatorJS" target="_blank"><img src="/images/EmulatorJS.png" style="height: 36px;" /></a></td>
<td> <td colspan="3">
The EmulatorJS Project<br /> The EmulatorJS Project<br />
<a href="https://github.com/EmulatorJS/EmulatorJS" target="_blank" class="romlink">https://github.com/EmulatorJS/EmulatorJS</a> <a href="https://github.com/EmulatorJS/EmulatorJS" target="_blank" class="romlink">https://github.com/EmulatorJS/EmulatorJS</a>
</td> </td>
</tr> </tr>
<tr> <tr>
<td colspan="2"> <td colspan="3">
<h3>Data Sources</h2> <h3>Data Sources</h2>
<h4>Game data</h4> <h4>Game data</h4>
</td> </td>
@@ -45,13 +49,13 @@
<td style="text-align: center;"> <td style="text-align: center;">
<a href="https://www.igdb.com/" target="_blank"><img src="/images/IGDB_logo.svg" style="filter: invert(100%); height: 36px;" /></a> <a href="https://www.igdb.com/" target="_blank"><img src="/images/IGDB_logo.svg" style="filter: invert(100%); height: 36px;" /></a>
</td> </td>
<td> <td colspan="2">
The Internet Game Database<br /> The Internet Game Database<br />
<a href="https://www.igdb.com/" target="_blank" class="romlink">https://www.igdb.com/</a> <a href="https://www.igdb.com/" target="_blank" class="romlink">https://www.igdb.com/</a>
</td> </td>
</tr> </tr>
<tr> <tr>
<td colspan="2"> <td colspan="3">
<h4>Signature data sources</h4> <h4>Signature data sources</h4>
</td> </td>
</tr> </tr>
@@ -59,7 +63,7 @@
<td style="text-align: center;"> <td style="text-align: center;">
<a href="https://www.tosecdev.org/" target="_blank"><img src="/images/TOSEC_logo.gif" style="height: 36px;" /></a> <a href="https://www.tosecdev.org/" target="_blank"><img src="/images/TOSEC_logo.gif" style="height: 36px;" /></a>
</td> </td>
<td> <td colspan="2">
The Old School Emulation Center<br /> The Old School Emulation Center<br />
<a href="https://www.tosecdev.org/" target="_blank" class="romlink">https://www.tosecdev.org/</a> <a href="https://www.tosecdev.org/" target="_blank" class="romlink">https://www.tosecdev.org/</a>
</td> </td>
@@ -68,7 +72,7 @@
<td style="text-align: center;"> <td style="text-align: center;">
<a href="https://www.progettosnaps.net/index.php" target="_blank"><img src="/images/ProgettoSnaps.gif" style="height: 36px;" /></a> <a href="https://www.progettosnaps.net/index.php" target="_blank"><img src="/images/ProgettoSnaps.gif" style="height: 36px;" /></a>
</td> </td>
<td> <td colspan="2">
Progetto-Snaps<br /> Progetto-Snaps<br />
<a href="https://www.progettosnaps.net/index.php" target="_blank" class="romlink">https://www.progettosnaps.net/index.php</a> <a href="https://www.progettosnaps.net/index.php" target="_blank" class="romlink">https://www.progettosnaps.net/index.php</a>
</td> </td>
@@ -77,7 +81,11 @@
<script type="text/javascript"> <script type="text/javascript">
var versionBox = document.getElementById('settings_appversion'); var versionBox = document.getElementById('settings_appversion');
versionBox.innerHTML = AppVersion; if (AppVersion == "1.5.0.0") {
versionBox.innerHTML = "Built from source";
} else {
versionBox.innerHTML = AppVersion;
}
var versionBox = document.getElementById('settings_dbversion'); var versionBox = document.getElementById('settings_dbversion');
versionBox.innerHTML = DBSchemaVersion; versionBox.innerHTML = DBSchemaVersion;

View File

@@ -11,14 +11,16 @@
<h2>Advanced Settings</h2> <h2>Advanced Settings</h2>
<p><strong>Warning</strong> Do not modify the below settings unless you know what you're doing.</p> <p><strong>Warning</strong> Do not modify the below settings unless you know what you're doing.</p>
<h3>Background Task Timers</h3> <h3>Background Task Timers</h3>
<p>All intervals are in minutes.</p>
<table id="settings_tasktimers" class="romtable" style="width: 100%;" cellspacing="0"> <table id="settings_tasktimers" class="romtable" style="width: 100%;" cellspacing="0">
</table> </table>
<div style="text-align: right;"><button id="settings_tasktimers_default" onclick="defaultTaskTimers();">Reset to Default</button><button id="settings_tasktimers_new" onclick="saveTaskTimers();">Save</button></div> <div style="text-align: right;"><button id="settings_tasktimers_default" onclick="defaultTaskTimers();">Reset to Default</button><button id="settings_tasktimers_new" onclick="saveTaskTimers();">Save</button></div>
<h3>Logging</h3> <h3>System Settings</h3>
<table cellspacing="0" style="width: 100%;"> <table cellspacing="0" style="width: 100%;">
<tr>
<th colspan="2">Logging</th>
</tr>
<tr> <tr>
<th> <th>
Write logs Write logs
@@ -44,9 +46,16 @@
<input type="number" min="1" id="settings_logs_retention" /> <input type="number" min="1" id="settings_logs_retention" />
</td> </td>
</tr> </tr>
<tr>
<th colspan="2">Emulator</th>
</tr>
<tr>
<th>Enable debug mode</th>
<td><input type="checkbox" name="settings_emulator" id="settings_emulator_debug" checked="checked" /></td>
</tr>
<tr> <tr>
<td colspan="2" style="text-align: right;"> <td colspan="2" style="text-align: right;">
<button id="settings_tasktimers_new" onclick="setLoggingSettings();">Save</button> <button id="settings_tasktimers_new" onclick="setSystemSettings();">Save</button>
</td> </td>
</tr> </tr>
</table> </table>
@@ -104,44 +113,241 @@
function getBackgroundTaskTimers() { function getBackgroundTaskTimers() {
ajaxCall( ajaxCall(
'/api/v1/System/Settings/BackgroundTasks/Intervals', '/api/v1/System/Settings/BackgroundTasks/Configuration',
'GET', 'GET',
function(result) { function(result) {
var targetTable = document.getElementById('settings_tasktimers'); var targetTable = document.getElementById('settings_tasktimers');
targetTable.innerHTML = ''; targetTable.innerHTML = '';
targetTable.appendChild(
createTableRow(true, ['Background Task', 'Timer Interval', 'Default Interval', 'Minimum Allowed Interval'])
);
for (const [key, value] of Object.entries(result)) { for (const [key, value] of Object.entries(result)) {
var newTableRow = createTableRow( var newTableRowBody = document.createElement('tbody');
newTableRowBody.className = 'romrow';
var enabledString = "";
if (value.enabled == true) {
enabledString = 'checked="checked"';
}
var newTableIntervalRow = createTableRow(
false, false,
[ [
GetTaskFriendlyName(value.task), GetTaskFriendlyName(value.task),
'<input id="settings_tasktimers_' + value.task + '" name="settings_tasktimers_values" data-name="' + value.task + '" data-default="' + value.defaultInterval + '" type="number" placeholder="0" min="' + value.minimumAllowedValue + '" value="' + value.interval + '" />', 'Enabled',
value.defaultInterval, '<input id="settings_enabled_' + value.task + '" name="settings_tasktimers_enabled" type="checkbox" ' + enabledString + '/>',
value.minimumAllowedValue
], ],
'romrow', '',
'romcell' 'romcell'
); );
targetTable.appendChild(newTableRow); newTableRowBody.appendChild(newTableIntervalRow);
var newTableRow = createTableRow(
false,
[
'',
'Minimum Interval (Minutes):',
'<input id="settings_tasktimers_' + value.task + '" name="settings_tasktimers_values" data-name="' + value.task + '" data-default="' + value.defaultInterval + '" type="number" placeholder="' + value.defaultInterval + '" min="' + value.minimumAllowedInterval + '" value="' + value.interval + '" />'
],
'',
'romcell'
);
newTableRowBody.appendChild(newTableRow);
// allowed time periods row
var newTableRowTime = document.createElement('tr');
var rowTimeSpace = document.createElement('td');
newTableRowTime.appendChild(rowTimeSpace);
var rowTimeContentTitle = document.createElement('td');
rowTimeContentTitle.className = 'romcell';
rowTimeContentTitle.innerHTML = "Allowed Days:";
newTableRowTime.appendChild(rowTimeContentTitle);
var rowTimeContent = document.createElement('td');
// rowTimeContent.setAttribute('colspan', 2);
rowTimeContent.className = 'romcell';
var daySelector = document.createElement('select');
daySelector.id = 'settings_alloweddays_' + value.task;
daySelector.name = 'settings_alloweddays';
daySelector.multiple = 'multiple';
daySelector.setAttribute('data-default', value.defaultAllowedDays.join(","));
daySelector.style.width = '95%';
var days = [ "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday", "Sunday" ];
for (var d = 0; d < days.length; d++) {
var dayOpt = document.createElement('option');
dayOpt.value = days[d];
dayOpt.innerHTML = days[d];
if (value.allowedDays.includes(days[d])) {
dayOpt.selected = 'selected';
}
daySelector.appendChild(dayOpt);
}
rowTimeContent.appendChild(daySelector);
$(daySelector).select2({
tags: false
});
newTableRowTime.appendChild(rowTimeContent);
newTableRowBody.appendChild(newTableRowTime);
// add start and end times
var newTableRowClock = document.createElement('tr');
var rowClockSpace = document.createElement('td');
newTableRowClock.appendChild(rowClockSpace);
var rowClockContentTitle = document.createElement('td');
rowClockContentTitle.className = 'romcell';
rowClockContentTitle.innerHTML = "Time Range:";
newTableRowClock.appendChild(rowClockContentTitle);
var rowClockContent = document.createElement('td');
rowClockContent.className = 'romcell';
// rowClockContent.setAttribute('colspan', 2);
rowClockContent.appendChild(generateTimeDropDowns(value.task, 'Start', value.defaultAllowedStartHours, value.defaultAllowedStartMinutes, value.allowedStartHours, value.allowedStartMinutes));
rowClockContentSeparator = document.createElement('span');
rowClockContentSeparator.innerHTML = '&nbsp;-&nbsp;';
rowClockContent.appendChild(rowClockContentSeparator);
rowClockContent.appendChild(generateTimeDropDowns(value.task, 'End', value.defaultAllowedEndHours, value.defaultAllowedEndMinutes, value.allowedEndHours, value.allowedEndMinutes));
newTableRowClock.appendChild(rowClockContent);
newTableRowBody.appendChild(newTableRowClock);
// blocks tasks
var newTableRowBlocks = document.createElement('tr');
var rowBlocksSpace = document.createElement('td');
newTableRowBlocks.appendChild(rowBlocksSpace);
var rowBlocksContentTitle = document.createElement('td');
rowBlocksContentTitle.className = 'romcell';
rowBlocksContentTitle.innerHTML = "Blocks:";
newTableRowBlocks.appendChild(rowBlocksContentTitle);
var rowBlocksContent = document.createElement('td');
rowBlocksContent.className = 'romcell';
// rowBlocksContent.setAttribute('colspan', 2);
var blocksString = "";
for (var i = 0; i < value.blocks.length; i++) {
if (blocksString.length > 0) { blocksString += ", "; }
blocksString += GetTaskFriendlyName(value.blocks[i]);
}
if (blocksString.length == 0) { blocksString = 'None'; }
rowBlocksContent.innerHTML = blocksString;
newTableRowBlocks.appendChild(rowBlocksContent);
newTableRowBody.appendChild(newTableRowBlocks);
// blocked by tasks
var newTableRowBlockedBy = document.createElement('tr');
var rowBlockedBySpace = document.createElement('td');
newTableRowBlockedBy.appendChild(rowBlockedBySpace);
var rowBlockedByContentTitle = document.createElement('td');
rowBlockedByContentTitle.className = 'romcell';
rowBlockedByContentTitle.innerHTML = "Blocked By:";
newTableRowBlockedBy.appendChild(rowBlockedByContentTitle);
var rowBlockedByContent = document.createElement('td');
rowBlockedByContent.className = 'romcell';
// rowBlockedByContent.setAttribute('colspan', 2);
var BlockedByString = "";
for (var i = 0; i < value.blockedBy.length; i++) {
if (BlockedByString.length > 0) { BlockedByString += ", "; }
BlockedByString += GetTaskFriendlyName(value.blockedBy[i]);
}
if (BlockedByString.length == 0) { BlockedByString = 'None'; }
rowBlockedByContent.innerHTML = BlockedByString;
newTableRowBlockedBy.appendChild(rowBlockedByContent);
newTableRowBody.appendChild(newTableRowBlockedBy);
// complete row
targetTable.appendChild(newTableRowBody);
} }
} }
); );
} }
function generateTimeDropDowns(taskName, rangeName, defaultHour, defaultMinute, valueHour, valueMinute) {
var container = document.createElement('div');
container.style.display = 'inline';
var elementName = 'settings_tasktimers_time';
var hourSelector = document.createElement('input');
hourSelector.id = 'settings_tasktimers_' + taskName + '_' + rangeName + '_Hour';
hourSelector.name = elementName;
hourSelector.setAttribute('data-name', taskName);
hourSelector.setAttribute('type', 'number');
hourSelector.setAttribute('min', '0');
hourSelector.setAttribute('max', '23');
hourSelector.setAttribute('placeholder', defaultHour);
hourSelector.value = valueHour;
container.appendChild(hourSelector);
var separator = document.createElement('span');
separator.innerHTML = " : ";
container.appendChild(separator);
var minSelector = document.createElement('input');
minSelector.id = 'settings_tasktimers_' + taskName + '_' + rangeName + '_Minute';
minSelector.name = elementName;
minSelector.setAttribute('type', 'number');
minSelector.setAttribute('min', '0');
minSelector.setAttribute('max', '59');
minSelector.setAttribute('placeholder', defaultMinute);
minSelector.value = valueMinute;
container.appendChild(minSelector);
return container;
}
function saveTaskTimers() { function saveTaskTimers() {
var timerValues = document.getElementsByName('settings_tasktimers_values'); var timerValues = document.getElementsByName('settings_tasktimers_values');
var model = {}; var model = [];
for (var i = 0; i < timerValues.length; i++) { for (var i = 0; i < timerValues.length; i++) {
model[timerValues[i].getAttribute('data-name')] = timerValues[i].value; var taskName = timerValues[i].getAttribute('data-name');
var taskEnabled = document.getElementById('settings_enabled_' + taskName).checked;
var taskIntervalObj = document.getElementById('settings_tasktimers_' + taskName);
var taskInterval = function() { if (taskIntervalObj.value) { return taskIntervalObj.value; } else { return taskIntervalObj.getAttribute('placeholder'); } };
var taskDaysRaw = $('#settings_alloweddays_' + taskName).select2('data');
var taskDays = [];
if (taskDaysRaw.length > 0) {
for (var d = 0; d < taskDaysRaw.length; d++) {
taskDays.push(taskDaysRaw[d].id);
}
} else {
taskDays.push("Monday");
}
var taskStartHourObj = document.getElementById('settings_tasktimers_' + taskName + '_Start_Hour');
var taskStartMinuteObj = document.getElementById('settings_tasktimers_' + taskName + '_Start_Minute');
var taskEndHourObj = document.getElementById('settings_tasktimers_' + taskName + '_End_Hour');
var taskEndMinuteObj = document.getElementById('settings_tasktimers_' + taskName + '_End_Minute');
var taskStartHour = function() { if (taskStartHourObj.value) { return taskStartHourObj.value; } else { return taskStartHourObj.getAttribute('placeholder'); } };
var taskStartMinute = function() { if (taskStartMinuteObj.value) { return taskStartMinuteObj.value; } else { return taskStartMinuteObj.getAttribute('placeholder'); } };
var taskEndHour = function() { if (taskEndHourObj.value) { return taskEndHourObj.value; } else { return taskEndHourObj.getAttribute('placeholder'); } };
var taskEndMinute = function() { if (taskEndMinuteObj.value) { return taskEndMinuteObj.value; } else { return taskEndMinuteObj.getAttribute('placeholder'); } };
model.push(
{
"task": taskName,
"enabled": taskEnabled,
"interval": taskInterval(),
"allowedDays": taskDays,
"allowedStartHours": taskStartHour(),
"allowedStartMinutes": taskStartMinute(),
"allowedEndHours": taskEndHour(),
"allowedEndMinutes": taskEndMinute()
}
);
} }
ajaxCall( ajaxCall(
'/api/v1/System/Settings/BackgroundTasks/Intervals', '/api/v1/System/Settings/BackgroundTasks/Configuration',
'POST', 'POST',
function(result) { function(result) {
getBackgroundTaskTimers(); getBackgroundTaskTimers();
@@ -154,16 +360,36 @@
} }
function defaultTaskTimers() { function defaultTaskTimers() {
var timerValues = document.getElementsByName('settings_tasktimers_enabled');
for (var i = 0; i < timerValues.length; i++) {
timerValues[i].checked = true;
}
var timerValues = document.getElementsByName('settings_tasktimers_values'); var timerValues = document.getElementsByName('settings_tasktimers_values');
for (var i = 0; i < timerValues.length; i++) { for (var i = 0; i < timerValues.length; i++) {
timerValues[i].value = timerValues[i].getAttribute('data-default'); timerValues[i].value = timerValues[i].getAttribute('data-default');
} }
var timerValues = document.getElementsByName('settings_alloweddays');
for (var i = 0; i < timerValues.length; i++) {
var defaultSelections = timerValues[i].getAttribute('data-default').split(',');
$(timerValues[i]).val(defaultSelections);
$(timerValues[i]).trigger('change');
}
var timerValues = document.getElementsByName('settings_tasktimers_time');
for (var i = 0; i < timerValues.length; i++) {
timerValues[i].value = timerValues[i].getAttribute('placeholder');
}
saveTaskTimers(); saveTaskTimers();
} }
function getLoggingSettings() { function getSystemSettings() {
ajaxCall( ajaxCall(
'/api/v1/System/Settings/System', '/api/v1/System/Settings/System',
'GET', 'GET',
@@ -175,11 +401,13 @@
document.getElementById(optionToSelect).checked = true; document.getElementById(optionToSelect).checked = true;
document.getElementById('settings_logs_retention').value = result.minimumLogRetentionPeriod; document.getElementById('settings_logs_retention').value = result.minimumLogRetentionPeriod;
document.getElementById('settings_emulator_debug').checked = result.emulatorDebugMode;
} }
); );
} }
function setLoggingSettings() { function setSystemSettings() {
var alwaysLogToDisk = false; var alwaysLogToDisk = false;
if ($("input[type='radio'][name='settings_logs_write']:checked").val() == "true") { if ($("input[type='radio'][name='settings_logs_write']:checked").val() == "true") {
alwaysLogToDisk = true; alwaysLogToDisk = true;
@@ -195,17 +423,18 @@
var model = { var model = {
"alwaysLogToDisk": alwaysLogToDisk, "alwaysLogToDisk": alwaysLogToDisk,
"minimumLogRetentionPeriod": retentionValue "minimumLogRetentionPeriod": retentionValue,
"emulatorDebugMode": document.getElementById('settings_emulator_debug').checked
}; };
ajaxCall( ajaxCall(
'/api/v1/System/Settings/System', '/api/v1/System/Settings/System',
'POST', 'POST',
function(result) { function(result) {
getLoggingSettings(); getSystemSettings();
}, },
function(error) { function(error) {
getLoggingSettings(); getSystemSettings();
}, },
JSON.stringify(model) JSON.stringify(model)
); );
@@ -213,5 +442,5 @@
drawLibrary(); drawLibrary();
getBackgroundTaskTimers(); getBackgroundTaskTimers();
getLoggingSettings(); getSystemSettings();
</script> </script>

View File

@@ -26,6 +26,7 @@
createTableRow( createTableRow(
true, true,
[ [
'',
'Email', 'Email',
'Role', 'Role',
'Age Restriction', 'Age Restriction',
@@ -37,17 +38,17 @@
); );
for (var i = 0; i < result.length; i++) { for (var i = 0; i < result.length; i++) {
var roleDiv = document.createElement('div'); var userAvatar = document.createElement('img');
// for (var r = 0; r < result[i].roles.length; r++) { userAvatar.className = "user_list_icon";
// var roleItem = document.createElement('div'); if (result[i].avatar != "00000000-0000-0000-0000-000000000000") {
// roleItem.className = 'dropdownroleitem'; userAvatar.setAttribute("src", "/api/v1.1/Account/Avatar/" + result[i].avatar + ".jpg");
// roleItem.innerHTML = result[i].roles[r].toUpperCase(); } else {
// var colorVal = intToRGB(hashCode(result[i].roles[r])); userAvatar.setAttribute("src", "/images/user.svg");
// roleItem.style.backgroundColor = '#' + colorVal; userAvatar.classList.add("user_list_icon_reversed");
// roleItem.style.borderColor = '#' + colorVal; }
// roleDiv.appendChild(roleItem);
// }
var roleDiv = document.createElement('div');
var roleItem = CreateBadge(result[i].highestRole); var roleItem = CreateBadge(result[i].highestRole);
roleDiv.appendChild(roleItem); roleDiv.appendChild(roleItem);
@@ -79,6 +80,7 @@
createTableRow( createTableRow(
false, false,
[ [
userAvatar,
result[i].emailAddress, result[i].emailAddress,
roleDiv, roleDiv,
ageRestrictionPolicyDescription, ageRestrictionPolicyDescription,

View File

@@ -11,6 +11,7 @@ function formatFilterPanel(containerElement, result) {
panel.appendChild(buildFilterPanelHeader('filter', 'Filter')); panel.appendChild(buildFilterPanelHeader('filter', 'Filter'));
// free text search
var containerPanelSearch = document.createElement('div'); var containerPanelSearch = document.createElement('div');
containerPanelSearch.className = 'filter_panel_box'; containerPanelSearch.className = 'filter_panel_box';
var containerPanelSearchField = document.createElement('input'); var containerPanelSearchField = document.createElement('input');
@@ -33,56 +34,36 @@ function formatFilterPanel(containerElement, result) {
panel.appendChild(containerPanelSearch); panel.appendChild(containerPanelSearch);
// user rating
panel.appendChild(buildFilterPanelHeader('userrating', 'User Rating', true, false)); panel.appendChild(buildFilterPanelHeader('userrating', 'User Rating', true, false));
var containerPanelUserRating = document.createElement('div'); var containerPanelUserRating = buildFilterRange('userrating', 0, 100);
containerPanelUserRating.id = 'filter_panel_box_userrating';
containerPanelUserRating.className = 'filter_panel_box';
var containerPanelUserRatingCheckBox = document.createElement('input');
containerPanelUserRatingCheckBox.id = 'filter_panel_userrating_enabled';
containerPanelUserRatingCheckBox.type = 'checkbox';
containerPanelUserRatingCheckBox.className = 'filter_panel_item_checkbox';
containerPanelUserRatingCheckBox.setAttribute('onclick', 'filter_panel_userrating_enabled_check();');
var ratingEnabledCookie = getCookie('filter_panel_userrating_enabled');
if (ratingEnabledCookie) {
if (ratingEnabledCookie == "true") {
containerPanelUserRatingCheckBox.checked = true;
} else {
containerPanelUserRatingCheckBox.checked = false;
}
}
containerPanelUserRating.appendChild(containerPanelUserRatingCheckBox);
var containerPanelUserRatingMinField = document.createElement('input');
var minRatingCookie = getCookie('filter_panel_userrating_min');
if (minRatingCookie) {
containerPanelUserRatingMinField.value = minRatingCookie;
}
containerPanelUserRatingMinField.id = 'filter_panel_userrating_min';
containerPanelUserRatingMinField.type = 'number';
containerPanelUserRatingMinField.placeholder = '0';
containerPanelUserRatingMinField.setAttribute('min', '0');
containerPanelUserRatingMinField.setAttribute('max', '100');
containerPanelUserRatingMinField.setAttribute('oninput', 'filter_panel_userrating_value();');
containerPanelUserRating.appendChild(containerPanelUserRatingMinField);
var containerPanelUserRatingMaxField = document.createElement('input');
var maxRatingCookie = getCookie('filter_panel_userrating_max');
if (maxRatingCookie) {
containerPanelUserRatingMaxField.value = maxRatingCookie;
}
containerPanelUserRatingMaxField.id = 'filter_panel_userrating_max';
containerPanelUserRatingMaxField.type = 'number';
containerPanelUserRatingMaxField.placeholder = '100';
containerPanelUserRatingMaxField.setAttribute('min', '0');
containerPanelUserRatingMaxField.setAttribute('max', '100');
containerPanelUserRatingMaxField.setAttribute('oninput', 'filter_panel_userrating_value();');
containerPanelUserRating.appendChild(containerPanelUserRatingMaxField);
panel.appendChild(containerPanelUserRating); panel.appendChild(containerPanelUserRating);
buildFilterPanel(panel, 'settings', 'Settings', [{ "id": "savestatesavailable", "name": "Game has save states avaialble", "gameCount": 0 }], true, true); // user vote count
panel.appendChild(buildFilterPanelHeader('uservotes', 'User Votes', true, false));
var containerPanelUserVotes = buildFilterRange('uservotes', 0, 1000000);
panel.appendChild(containerPanelUserVotes);
// release year
panel.appendChild(buildFilterPanelHeader('releaseyear', 'Release Year', true, false));
var containerPanelReleaseYear = buildFilterRange('releaseyear', 1960, (new Date()).getFullYear());
panel.appendChild(containerPanelReleaseYear);
// settings
buildFilterPanel(panel, 'settings', 'Settings', [
{
"id": "savestatesavailable",
"name": "Game has save states avaialble",
"gameCount": 0
},
{
"id": "favourite",
"name": "Favourite",
"gameCount": 0
}
], true, true);
// server provided filters
if (result.platforms) { if (result.platforms) {
buildFilterPanel(panel, 'platform', 'Platforms', result.platforms, true, true); buildFilterPanel(panel, 'platform', 'Platforms', result.platforms, true, true);
} }
@@ -111,6 +92,7 @@ function formatFilterPanel(containerElement, result) {
targetElement.appendChild(panel); targetElement.appendChild(panel);
// filter controls
var buttonsDiv = document.createElement('div'); var buttonsDiv = document.createElement('div');
buttonsDiv.id = 'games_library_searchbuttons' buttonsDiv.id = 'games_library_searchbuttons'
@@ -133,16 +115,30 @@ function formatFilterPanel(containerElement, result) {
// set order by values // set order by values
var orderByCookie = getCookie('games_library_orderby_select'); var orderByCookie = getCookie('games_library_orderby_select');
if (orderByCookie) { if (orderByCookie) {
document.getElementById('games_library_orderby_select').value = orderByCookie; var orderBySelector = document.getElementById('games_library_orderby_select');
$(orderBySelector).select2('destroy');
$(orderBySelector).val(orderByCookie).select2();
} }
var orderByDirectionCookie = getCookie('games_library_orderby_direction_select'); var orderByDirectionCookie = getCookie('games_library_orderby_direction_select');
if (orderByDirectionCookie) { if (orderByDirectionCookie) {
document.getElementById('games_library_orderby_direction_select').value = orderByDirectionCookie; var orderByDirectionSelector = document.getElementById('games_library_orderby_direction_select');
$(orderByDirectionSelector).select2('destroy');
$(orderByDirectionSelector).val(orderByDirectionCookie).select2();
} }
containerElement.appendChild(targetElement); containerElement.appendChild(targetElement);
containerElement.appendChild(buttonsDiv); containerElement.appendChild(buttonsDiv);
console.log('Filter generated - execute filter');
var pageNumber = undefined;
if (getCookie('games_library_last_page') == "") {
pageNumber = undefined;
} else {
pageNumber = Number(getCookie('games_library_last_page'));
}
executeFilter1_1(pageNumber);
} }
function buildFilterPanel(targetElement, headerString, friendlyHeaderString, valueList, showToggle, initialDisplay) { function buildFilterPanel(targetElement, headerString, friendlyHeaderString, valueList, showToggle, initialDisplay) {
@@ -260,6 +256,59 @@ function buildFilterPanelItem(filterType, itemString, friendlyItemString, tags)
return filterPanelItem; return filterPanelItem;
} }
function buildFilterRange(name, min, max) {
var containerPanelUserRating = document.createElement('div');
containerPanelUserRating.id = 'filter_panel_box_' + name + '';
containerPanelUserRating.className = 'filter_panel_box';
var containerPanelUserRatingCheckBox = document.createElement('input');
containerPanelUserRatingCheckBox.id = 'filter_panel_' + name + '_enabled';
containerPanelUserRatingCheckBox.name = 'filter_panel_range_enabled_check';
containerPanelUserRatingCheckBox.setAttribute('data-name', name);
containerPanelUserRatingCheckBox.type = 'checkbox';
containerPanelUserRatingCheckBox.className = 'filter_panel_item_checkbox';
containerPanelUserRatingCheckBox.setAttribute('onclick', 'filter_panel_range_enabled_check("' + name + '");');
var ratingEnabledCookie = getCookie('filter_panel_' + name + '_enabled');
if (ratingEnabledCookie) {
if (ratingEnabledCookie == "true") {
containerPanelUserRatingCheckBox.checked = true;
} else {
containerPanelUserRatingCheckBox.checked = false;
}
}
containerPanelUserRating.appendChild(containerPanelUserRatingCheckBox);
var containerPanelUserRatingMinField = document.createElement('input');
var minRatingCookie = getCookie('filter_panel_' + name + '_min');
if (minRatingCookie) {
containerPanelUserRatingMinField.value = minRatingCookie;
}
containerPanelUserRatingMinField.id = 'filter_panel_' + name + '_min';
containerPanelUserRatingMinField.name = 'filter_panel_range_min';
containerPanelUserRatingMinField.type = 'number';
containerPanelUserRatingMinField.placeholder = min;
containerPanelUserRatingMinField.setAttribute('min', min);
containerPanelUserRatingMinField.setAttribute('max', max);
containerPanelUserRatingMinField.setAttribute('oninput', 'filter_panel_range_value("' + name + '");');
containerPanelUserRating.appendChild(containerPanelUserRatingMinField);
var containerPanelUserRatingMaxField = document.createElement('input');
var maxRatingCookie = getCookie('filter_panel_' + name + '_max');
if (maxRatingCookie) {
containerPanelUserRatingMaxField.value = maxRatingCookie;
}
containerPanelUserRatingMaxField.id = 'filter_panel_' + name + '_max';
containerPanelUserRatingMaxField.name = 'filter_panel_range_max';
containerPanelUserRatingMaxField.type = 'number';
containerPanelUserRatingMaxField.placeholder = max;
containerPanelUserRatingMaxField.setAttribute('min', min);
containerPanelUserRatingMaxField.setAttribute('max', max);
containerPanelUserRatingMaxField.setAttribute('oninput', 'filter_panel_range_value("' + name + '");');
containerPanelUserRating.appendChild(containerPanelUserRatingMaxField);
return containerPanelUserRating;
}
var filterExecutor = null; var filterExecutor = null;
function executeFilterDelayed() { function executeFilterDelayed() {
if (filterExecutor) { if (filterExecutor) {
@@ -290,24 +339,24 @@ function buildFilterTag(tags) {
return boundingDiv; return boundingDiv;
} }
function filter_panel_userrating_enabled_check() { function filter_panel_range_enabled_check(name) {
var ratingCheck = document.getElementById('filter_panel_userrating_enabled'); var ratingCheck = document.getElementById('filter_panel_' + name + '_enabled');
var minRatingValue = document.getElementById('filter_panel_userrating_min'); var minRatingValue = document.getElementById('filter_panel_' + name + '_min');
var maxRatingValue = document.getElementById('filter_panel_userrating_max'); var maxRatingValue = document.getElementById('filter_panel_' + name + '_max');
if (ratingCheck.checked == false) { if (ratingCheck.checked == false) {
minRatingValue.value = ''; minRatingValue.value = '';
maxRatingValue.value = ''; maxRatingValue.value = '';
} else { } else {
minRatingValue.value = 0; minRatingValue.value = minRatingValue.min;
maxRatingValue.value = 100; maxRatingValue.value = maxRatingValue.max;
} }
} }
function filter_panel_userrating_value() { function filter_panel_range_value(name) {
var ratingCheck = document.getElementById('filter_panel_userrating_enabled'); var ratingCheck = document.getElementById('filter_panel_' + name + '_enabled');
var minRatingValue = document.getElementById('filter_panel_userrating_min'); var minRatingValue = document.getElementById('filter_panel_' + name + '_min');
var maxRatingValue = document.getElementById('filter_panel_userrating_max'); var maxRatingValue = document.getElementById('filter_panel_' + name + '_max');
if (minRatingValue.value || maxRatingValue.value) { if (minRatingValue.value || maxRatingValue.value) {
ratingCheck.checked = true; ratingCheck.checked = true;
@@ -327,7 +376,10 @@ function resetFilters() {
} }
// fire checkbox specific scripts // fire checkbox specific scripts
filter_panel_userrating_enabled_check(); var rangeCheckboxes = document.getElementsByName('filter_panel_range_enabled_check');
for (var i = 0; i < rangeCheckboxes.length; i++) {
filter_panel_range_enabled_check(rangeCheckboxes[i].getAttribute('data-name'));
}
executeFilter1_1(); executeFilter1_1();
} }
@@ -348,10 +400,10 @@ function executeFilter1_1(pageNumber, pageSize) {
var model; var model;
// get order by // get order by
var orderBy = document.getElementById('games_library_orderby_select').value; var orderBy = $('#games_library_orderby_select').val();
setCookie('games_library_orderby_select', orderBy); setCookie('games_library_orderby_select', orderBy);
var orderByDirection = true; var orderByDirection = true;
var orderByDirectionSelect = document.getElementById('games_library_orderby_direction_select').value; var orderByDirectionSelect = $('#games_library_orderby_direction_select').val();
if (orderByDirectionSelect == "Ascending") { if (orderByDirectionSelect == "Ascending") {
orderByDirection = true; orderByDirection = true;
} else { } else {
@@ -399,6 +451,78 @@ function executeFilter1_1(pageNumber, pageSize) {
setCookie("filter_panel_userrating_enabled", true); setCookie("filter_panel_userrating_enabled", true);
} }
// user votes
var userVotesEnabled = document.getElementById('filter_panel_uservotes_enabled');
var minUserVotes = -1;
var minUserVotesInput = document.getElementById('filter_panel_uservotes_min');
if (minUserVotesInput.value) {
minUserVotes = minUserVotesInput.value;
userVotesEnabled.checked = true;
}
setCookie(minUserVotesInput.id, minUserVotesInput.value);
var maxUserVotes = -1;
var maxUserVotesInput = document.getElementById('filter_panel_uservotes_max');
if (maxUserVotesInput.value) {
maxUserVotes = maxUserVotesInput.value;
userVotesEnabled.checked = true;
}
setCookie(maxUserVotesInput.id, maxUserVotesInput.value);
if (minUserVotes == -1 && maxUserVotes == -1) {
userVotesEnabled.checked = false;
}
if (userVotesEnabled.checked == false) {
setCookie("filter_panel_uservotes_enabled", false);
minUserVotes = -1;
minUserVotesInput.value = "";
setCookie(minUserVotesInput.id, minUserVotesInput.value);
maxUserVotes = -1;
maxUserVotesInput.value = "";
setCookie(maxUserVotesInput.id, maxUserVotesInput.value);
} else {
setCookie("filter_panel_uservotes_enabled", true);
}
// release year
var releaseYearEnabled = document.getElementById('filter_panel_releaseyear_enabled');
var minReleaseYear = -1;
var minReleaseYearInput = document.getElementById('filter_panel_releaseyear_min');
if (minReleaseYearInput.value) {
minReleaseYear = minReleaseYearInput.value;
releaseYearEnabled.checked = true;
}
setCookie(minReleaseYearInput.id, minReleaseYearInput.value);
var maxReleaseYear = -1;
var maxReleaseYearInput = document.getElementById('filter_panel_releaseyear_max');
if (maxReleaseYearInput.value) {
maxReleaseYear = maxReleaseYearInput.value;
releaseYearEnabled.checked = true;
}
setCookie(maxReleaseYearInput.id, maxReleaseYearInput.value);
if (minReleaseYear == -1 && maxReleaseYear == -1) {
releaseYearEnabled.checked = false;
}
if (releaseYearEnabled.checked == false) {
setCookie("filter_panel_releaseyear_enabled", false);
minReleaseYear = -1;
minReleaseYearInput.value = "";
setCookie(minReleaseYearInput.id, minReleaseYearInput.value);
maxReleaseYear = -1;
maxReleaseYearInput.value = "";
setCookie(maxReleaseYearInput.id, maxReleaseYearInput.value);
} else {
setCookie("filter_panel_releaseyear_enabled", true);
}
// save cookies for settings // save cookies for settings
GetFilterQuery1_1('settings'); GetFilterQuery1_1('settings');
@@ -412,16 +536,19 @@ function executeFilter1_1(pageNumber, pageSize) {
model = { model = {
"Name": document.getElementById('filter_panel_search').value, "Name": document.getElementById('filter_panel_search').value,
"HasSavedGame": document.getElementById('filter_panel_item_settings_checkbox_savestatesavailable').checked, "HasSavedGame": document.getElementById('filter_panel_item_settings_checkbox_savestatesavailable').checked,
"isFavourite": document.getElementById('filter_panel_item_settings_checkbox_favourite').checked,
"Platform": GetFilterQuery1_1('platform'), "Platform": GetFilterQuery1_1('platform'),
"Genre": GetFilterQuery1_1('genre'), "Genre": GetFilterQuery1_1('genre'),
"GameMode": GetFilterQuery1_1('gamemode'), "GameMode": GetFilterQuery1_1('gamemode'),
"PlayerPerspective": GetFilterQuery1_1('playerperspective'), "PlayerPerspective": GetFilterQuery1_1('playerperspective'),
"Theme": GetFilterQuery1_1('theme'), "Theme": GetFilterQuery1_1('theme'),
"MinimumReleaseYear": minReleaseYear,
"MaximumReleaseYear": maxReleaseYear,
"GameRating": { "GameRating": {
"MinimumRating": minUserRating, "MinimumRating": minUserRating,
"MinimumRatingCount": -1, "MinimumRatingCount": minUserVotes,
"MaximumRating": maxUserRating, "MaximumRating": maxUserRating,
"MaximumRatingCount": -1, "MaximumRatingCount": maxUserVotes,
"IncludeUnrated": !userRatingEnabled "IncludeUnrated": !userRatingEnabled
}, },
"GameAgeRating": { "GameAgeRating": {
@@ -433,6 +560,7 @@ function executeFilter1_1(pageNumber, pageSize) {
"SortAscending": orderByDirection "SortAscending": orderByDirection
} }
}; };
console.log(model);
existingSearchModel = model; existingSearchModel = model;
} else { } else {
@@ -446,6 +574,7 @@ function executeFilter1_1(pageNumber, pageSize) {
'POST', 'POST',
function (result) { function (result) {
var gameElement = document.getElementById('games_library'); var gameElement = document.getElementById('games_library');
setCookie('games_library_last_page', pageNumber);
formatGamesPanel(gameElement, result, pageNumber, pageSize, true); formatGamesPanel(gameElement, result, pageNumber, pageSize, true);
}, },
function (error) { function (error) {

View File

@@ -112,6 +112,9 @@ function formatGamesPanel(targetElement, result, pageNumber, pageSize, forceScro
var pager = document.getElementById('games_pager'); var pager = document.getElementById('games_pager');
pager.style.display = 'none'; pager.style.display = 'none';
var alphaPager = document.getElementById('games_library_alpha_pager');
alphaPager.innerHTML = '';
switch(pageMode) { switch(pageMode) {
case 'infinite': case 'infinite':
if (result.games.length == pageSize) { if (result.games.length == pageSize) {
@@ -126,6 +129,14 @@ function formatGamesPanel(targetElement, result, pageNumber, pageSize, forceScro
break; break;
case 'paged': case 'paged':
for (const [key, value] of Object.entries(result.alphaList)) {
var letterPager = document.createElement('span');
letterPager.className = 'games_library_alpha_pager_letter';
letterPager.setAttribute('onclick', 'executeFilter1_1(' + value + ');');
letterPager.innerHTML = key;
alphaPager.appendChild(letterPager);
}
if (result.count > pageSize) { if (result.count > pageSize) {
// add some padding to the bottom of the games list // add some padding to the bottom of the games list
var loadPageButton = document.createElement("div"); var loadPageButton = document.createElement("div");
@@ -134,6 +145,16 @@ function formatGamesPanel(targetElement, result, pageNumber, pageSize, forceScro
var pageCount = Math.ceil(result.count / pageSize); var pageCount = Math.ceil(result.count / pageSize);
// add first page button
var firstPage = document.createElement('span');
firstPage.innerHTML = '&#124;&lt;';
if (pageNumber == 1) {
firstPage.className = 'games_pager_number_disabled';
} else {
firstPage.className = 'games_pager_number';
firstPage.setAttribute('onclick', 'executeFilter1_1(1);');
}
// add previous page button // add previous page button
var prevPage = document.createElement('span'); var prevPage = document.createElement('span');
prevPage.innerHTML = '&lt;'; prevPage.innerHTML = '&lt;';
@@ -187,10 +208,22 @@ function formatGamesPanel(targetElement, result, pageNumber, pageSize, forceScro
nextPage.setAttribute('onclick', 'executeFilter1_1(' + (pageNumber + 1) + ');'); nextPage.setAttribute('onclick', 'executeFilter1_1(' + (pageNumber + 1) + ');');
} }
// add last page button
var lastPage = document.createElement('span');
lastPage.innerHTML = '&gt;&#124;';
if (pageNumber == pageCount) {
lastPage.className = 'games_pager_number_disabled';
} else {
lastPage.className = 'games_pager_number';
lastPage.setAttribute('onclick', 'executeFilter1_1(' + pageCount + ');');
}
pager.innerHTML = ''; pager.innerHTML = '';
pager.appendChild(firstPage);
pager.appendChild(prevPage); pager.appendChild(prevPage);
pager.appendChild(pageNumbers); pager.appendChild(pageNumbers);
pager.appendChild(nextPage); pager.appendChild(nextPage);
pager.appendChild(lastPage);
pager.style.display = ''; pager.style.display = '';
} }
@@ -236,30 +269,36 @@ function IsInView() {
$(window).scroll(IsInView); $(window).scroll(IsInView);
function renderGameIcon(gameObject, showTitle, showRatings, showClassification, classificationDisplayOrder, useSmallCover) { function renderGameIcon(gameObject, showTitle, showRatings, showClassification, classificationDisplayOrder, useSmallCover, listView) {
if (listView == undefined) {
listView = false;
}
var classes = getViewModeClasses(listView);
var gameBox = document.createElement('div'); var gameBox = document.createElement('div');
gameBox.id = "game_tile_" + gameObject.id; gameBox.id = "game_tile_" + gameObject.id;
if (useSmallCover == true) { if (useSmallCover == true) {
gameBox.className = 'game_tile game_tile_small'; gameBox.className = classes['game_tile game_tile_small'];
} else { } else {
gameBox.className = 'game_tile'; gameBox.className = classes['game_tile'];
} }
gameBox.setAttribute('onclick', 'window.location.href = "/index.html?page=game&id=' + gameObject.id + '";'); gameBox.setAttribute('onclick', 'window.location.href = "/index.html?page=game&id=' + gameObject.id + '";');
var gameImageBox = document.createElement('div'); var gameImageBox = document.createElement('div');
gameImageBox.className = 'game_tile_box'; gameImageBox.className = classes['game_tile_box'];
var gameImage = document.createElement('img'); var gameImage = document.createElement('img');
if (useSmallCover == true) { if (useSmallCover == true) {
gameImage.className = 'game_tile_image game_tile_image_small lazy'; gameImage.className = classes['game_tile_image game_tile_image_small lazy'];
} else { } else {
gameImage.className = 'game_tile_image lazy'; gameImage.className = classes['game_tile_image lazy'];
} }
gameImage.src = '/images/unknowngame.png'; gameImage.src = '/images/unknowngame.png';
if (gameObject.cover) { if (gameObject.cover) {
gameImage.setAttribute('data-src', '/api/v1.1/Games/' + gameObject.id + '/cover/image/cover_big/' + gameObject.cover.imageId + '.jpg'); gameImage.setAttribute('data-src', '/api/v1.1/Games/' + gameObject.id + '/cover/image/cover_big/' + gameObject.cover.imageId + '.jpg');
} else { } else {
gameImage.className = 'game_tile_image unknown'; gameImage.className = classes['game_tile_image unknown'];
} }
gameImageBox.appendChild(gameImage); gameImageBox.appendChild(gameImage);
@@ -287,13 +326,21 @@ function renderGameIcon(gameObject, showTitle, showRatings, showClassification,
if (gameObject.hasSavedGame == true) { if (gameObject.hasSavedGame == true) {
var gameSaveIcon = document.createElement('img'); var gameSaveIcon = document.createElement('img');
gameSaveIcon.src = '/images/SaveStates.png'; gameSaveIcon.src = '/images/SaveStates.png';
gameSaveIcon.className = 'game_tile_box_savedgame savedstateicon'; gameSaveIcon.className = classes['game_tile_box_savedgame savedstateicon'];
gameImageBox.appendChild(gameSaveIcon); gameImageBox.appendChild(gameSaveIcon);
} }
// add favourite game icon
if (gameObject.isFavourite == true) {
var gameFavIcon = document.createElement('img');
gameFavIcon.src = '/images/favourite-filled.svg';
gameFavIcon.className = classes['game_tile_box_favouritegame favouriteicon'];
gameImageBox.appendChild(gameFavIcon);
}
if (gameObject.totalRating || displayClassification == true) { if (gameObject.totalRating || displayClassification == true) {
var gameImageRatingBanner = document.createElement('div'); var gameImageRatingBanner = document.createElement('div');
gameImageRatingBanner.className = 'game_tile_box_ratingbanner'; gameImageRatingBanner.className = classes['game_tile_box_ratingbanner'];
if (showRatings == true || displayClassification == true) { if (showRatings == true || displayClassification == true) {
if (showRatings == true) { if (showRatings == true) {
@@ -314,7 +361,7 @@ function renderGameIcon(gameObject, showTitle, showRatings, showClassification,
if (displayClassification == true) { if (displayClassification == true) {
var gameImageClassificationLogo = document.createElement('img'); var gameImageClassificationLogo = document.createElement('img');
gameImageClassificationLogo.src = classificationPath; gameImageClassificationLogo.src = classificationPath;
gameImageClassificationLogo.className = 'rating_image_overlay'; gameImageClassificationLogo.className = classes['rating_image_overlay'];
gameImageBox.appendChild(gameImageClassificationLogo); gameImageBox.appendChild(gameImageClassificationLogo);
} }
} }
@@ -323,10 +370,42 @@ function renderGameIcon(gameObject, showTitle, showRatings, showClassification,
if (showTitle == true) { if (showTitle == true) {
var gameBoxTitle = document.createElement('div'); var gameBoxTitle = document.createElement('div');
gameBoxTitle.class = 'game_tile_label'; gameBoxTitle.className = classes['game_tile_label'];
gameBoxTitle.innerHTML = gameObject.name; gameBoxTitle.innerHTML = gameObject.name;
gameBox.appendChild(gameBoxTitle); gameBox.appendChild(gameBoxTitle);
} }
return gameBox; return gameBox;
}
function getViewModeClasses(listView) {
if (listView == false) {
return {
"game_tile game_tile_small": "game_tile game_tile_small",
"game_tile": "game_tile",
"game_tile_box": "game_tile_box",
"game_tile_image game_tile_image_small lazy": "game_tile_image game_tile_image_small lazy",
"game_tile_image lazy": "game_tile_image lazy",
"game_tile_image unknown": "game_tile_image unknown",
"game_tile_box_savedgame savedstateicon": "game_tile_box_savedgame savedstateicon",
"game_tile_box_favouritegame favouriteicon": "game_tile_box_favouritegame favouriteicon",
"game_tile_box_ratingbanner": "game_tile_box_ratingbanner",
"rating_image_overlay": "rating_image_overlay",
"game_tile_label": "game_tile_label"
};
} else {
return {
"game_tile game_tile_small": "game_tile_row game_tile_small",
"game_tile": "game_tile_row",
"game_tile_box": "game_tile_box_row",
"game_tile_image game_tile_image_small lazy": "game_tile_image_row game_tile_image_small lazy",
"game_tile_image lazy": "game_tile_image_row lazy",
"game_tile_image unknown": "game_tile_image_row unknown",
"game_tile_box_savedgame savedstateicon": "game_tile_box_savedgame_row savedstateicon",
"game_tile_box_favouritegame favouriteicon": "game_tile_box_favouritegame_row favouriteicon",
"game_tile_box_ratingbanner": "game_tile_box_ratingbanner_row",
"rating_image_overlay": "rating_image_overlay_row",
"game_tile_label": "game_tile_label_row"
};
}
} }

View File

@@ -410,13 +410,25 @@ function GetTaskFriendlyName(TaskName, options) {
case 'LibraryScan': case 'LibraryScan':
return "Library scan"; return "Library scan";
case 'LibraryScanWorker': case 'LibraryScanWorker':
return "Library scan worker: " + options.name; if (options) {
return "Library scan worker: " + options.name;
} else {
return "Library scan worker";
}
case 'CollectionCompiler': case 'CollectionCompiler':
return "Compress collection id: " + options; if (options) {
return "Compress collection id: " + options;
} else {
return "Compress collection";
}
case 'BackgroundDatabaseUpgrade': case 'BackgroundDatabaseUpgrade':
return "Background database upgrade"; return "Background database upgrade";
case 'TempCleanup': case 'TempCleanup':
return "Temporary directory cleanup"; return "Temporary directory cleanup";
case 'DailyMaintainer':
return "Daily maintenance";
case 'WeeklyMaintainer':
return "Weekly maintenance";
default: default:
return TaskName; return TaskName;
} }
@@ -504,4 +516,26 @@ function Uint8ToString(u8a){
c.push(String.fromCharCode.apply(null, u8a.subarray(i, i+CHUNK_SZ))); c.push(String.fromCharCode.apply(null, u8a.subarray(i, i+CHUNK_SZ)));
} }
return c.join(""); return c.join("");
}
function loadAvatar(AvatarId) {
// load user avatar
var bannerAvatar = document.getElementById('banner_user_image');
var bannerAvatarButton = document.getElementById('banner_user');
if (bannerAvatar && bannerAvatarButton) {
if (AvatarId != "00000000-0000-0000-0000-000000000000") {
bannerAvatar.setAttribute("src", "/api/v1.1/Account/Avatar/" + AvatarId + ".jpg");
bannerAvatar.className = "banner_button_avatar";
bannerAvatarButton.classList.add('banner_button_avatar_image');
bannerAvatarButton.classList.remove('banner_button');
} else {
bannerAvatar.setAttribute("src", "/images/user.svg");
bannerAvatar.className = "banner_button_image";
bannerAvatarButton.classList.remove('banner_button_avatar_image');
bannerAvatarButton.classList.add('banner_button');
}
}
} }

View File

@@ -146,12 +146,33 @@ h3 {
filter: invert(100%); filter: invert(100%);
} }
.user_list_icon {
width: 35px;
height: 35px;
margin-bottom: -5px;
}
.user_list_icon_reversed {
filter: invert(100%);
}
.banner_button_image_smaller { .banner_button_image_smaller {
height: 16px; height: 16px;
width: 16px; width: 16px;
margin-left: 5px; margin-left: 5px;
} }
.banner_button_avatar {
margin-top: -10px;
height: 40px;
width: 40px;
cursor: pointer;
}
.banner_button_avatar_image:hover {
cursor: pointer;
}
#banner_header { #banner_header {
background-color: rgba(0, 22, 56, 0.8); background-color: rgba(0, 22, 56, 0.8);
backdrop-filter: blur(8px); backdrop-filter: blur(8px);
@@ -300,12 +321,12 @@ input[id='filter_panel_search'] {
width: 160px; width: 160px;
} }
input[id='filter_panel_userrating_min'] { input[name='filter_panel_range_min'] {
width: 50px; width: 50px;
margin-right: 5px; margin-right: 5px;
} }
input[id='filter_panel_userrating_max'] { input[name='filter_panel_range_max'] {
width: 50px; width: 50px;
} }
@@ -352,12 +373,13 @@ input[id='filter_panel_userrating_max'] {
#games_pager { #games_pager {
position: fixed; position: fixed;
bottom: 50px; bottom: 0px;
left: 50%; left: 50%;
transform: translate(-50%, 0); transform: translate(-50%, 0);
border-style: solid; border-style: solid;
border-width: 1px; border-width: 1px;
border-radius: 7px; border-top-left-radius: 7px;
border-top-right-radius: 7px;
border-color: rgba(0, 22, 56, 0.8); border-color: rgba(0, 22, 56, 0.8);
padding-left: 20px; padding-left: 20px;
padding-right: 20px; padding-right: 20px;
@@ -469,6 +491,7 @@ input[id='filter_panel_userrating_max'] {
#games_library { #games_library {
display: block; display: block;
margin-right: 50px;
/* width: 90%; */ /* width: 90%; */
/* border-style: solid; /* border-style: solid;
border-width: 1px; border-width: 1px;
@@ -489,7 +512,40 @@ input[id='filter_panel_userrating_max'] {
width: 100%; width: 100%;
text-align: center; text-align: center;
padding-top: 10px; padding-top: 10px;
height: 100px; height: 55px;
}
#games_library_alpha_box {
position: fixed;
right: 0px;
top: 120px;
bottom: 0px;
width: 50px;
overflow-x: auto;
overflow-y: auto;
/* display: flex; */
justify-content: center;
align-items: center;
}
#games_library_alpha_pager {
width: 50px;
justify-content: center;
align-items: center;
}
.games_library_alpha_pager_letter {
display: block;
font-family: Commodore64;
font-size: 14px;
text-align: center;
padding-top: 7px;
padding-bottom: 7px;
}
.games_library_alpha_pager_letter:hover {
cursor: pointer;
background-color: blue;
} }
.game_tile { .game_tile {
@@ -534,6 +590,22 @@ input[id='filter_panel_userrating_max'] {
border: 1px solid #2b2b2b; border: 1px solid #2b2b2b;
} }
.game_tile_row {
padding: 5px;
display: block;
position: relative;
width: auto;
min-height: 100px;
align-items: left;
vertical-align: top;
}
.game_tile_row:hover {
cursor: pointer;
text-decoration: underline;
background-color: #2b2b2b;
}
.game_tile_box { .game_tile_box {
position: relative; position: relative;
display: inline-block; display: inline-block;
@@ -541,6 +613,14 @@ input[id='filter_panel_userrating_max'] {
max-height: 200px; max-height: 200px;
} }
.game_tile_box_row {
position: absolute;
display: inline-block;
padding: 10px;
left: 0px;
right: 0px;
}
.game_tile_box_ratingbanner { .game_tile_box_ratingbanner {
position: absolute; position: absolute;
bottom: 0px; bottom: 0px;
@@ -557,12 +637,52 @@ input[id='filter_panel_userrating_max'] {
-webkit-backdrop-filter: blur(8px); -webkit-backdrop-filter: blur(8px);
} }
.game_tile_box_ratingbanner_row {
position: absolute;
left: 80%;
top: 10px;
bottom: 10px;
right: 10px;
width: 20%;
line-height: 80px;
background-color: transparent;
}
.game_tile_label_row {
position: absolute;
text-align: left;
vertical-align: middle;
left: 110px;
top: 0px;
bottom: 0px;
width: 50%;
line-height: 100px;
}
.game_tile_box_savedgame { .game_tile_box_savedgame {
position: absolute; position: absolute;
top: 1px; top: 1px;
right: 5px; right: 5px;
} }
.game_tile_box_savedgame_row {
position: absolute;
top: 30%;
right: 10px;
}
.game_tile_box_favouritegame {
position: absolute;
top: 1px;
left: 5px;
}
.game_tile_box_favouritegame_row {
position: absolute;
top: 30%;
right: 10px;
}
.game_tile_image { .game_tile_image {
max-width: 200px; max-width: 200px;
min-width: 150px; min-width: 150px;
@@ -574,10 +694,26 @@ input[id='filter_panel_userrating_max'] {
-moz-box-shadow: 5px 5px 19px 0px rgba(0,0,0,0.44); -moz-box-shadow: 5px 5px 19px 0px rgba(0,0,0,0.44);
} }
.game_tile_image_row {
max-width: 75px;
min-width: 50px;
max-height: 75px;
min-height: 75px;
margin-left: 10px;
background-color: transparent;
box-shadow: 5px 5px 19px 0px rgba(0,0,0,0.44);
-webkit-box-shadow: 5px 5px 19px 0px rgba(0,0,0,0.44);
-moz-box-shadow: 5px 5px 19px 0px rgba(0,0,0,0.44);
}
.game_tile_image, .unknown { .game_tile_image, .unknown {
background-color: transparent; background-color: transparent;
} }
.game_tile_image_row, .unknown {
background-color: transparent;
}
.game_tile_image_small { .game_tile_image_small {
min-width: 50px; min-width: 50px;
min-height: 50px; min-height: 50px;
@@ -680,6 +816,13 @@ input[id='filter_panel_userrating_max'] {
bottom: 5px; bottom: 5px;
} }
.rating_image_overlay_row {
position: absolute;
left: 50%;
top: 25px;
height: 50px;
}
#gamescreenshots { #gamescreenshots {
background-color: black; background-color: black;
padding: 10px; padding: 10px;
@@ -774,6 +917,56 @@ iframe {
border-color: white; border-color: white;
} }
#gamestatistics {
display: block;
width: 100%;
margin-bottom: 10px;
background-color: rgba(56, 56, 56, 0.3);
}
.gamestatistics_box {
display: inline-block;
padding: 10px;
}
.gamestatistics_label {
display: block;
font-weight: bold;
font-size: 14px;
text-transform: uppercase;
padding: 3px;
}
.gamestatistics_value {
display: block;
padding: 3px;
}
#gamestatistics_favourite {
float: right;
}
#gamestatistics_favourite_button {
margin: 5px;
padding-top: 5px;
padding-bottom: 5px;
padding-left: 2px;
padding-right: 7px;
width: 30px;
height: 30px;
text-align: center;
background-color: rgba(56, 56, 56, 0.3);
border-radius: 5px;
border-style: solid;
border-color: transparent;
border-width: 0px;
cursor: pointer;
}
#gamestatistics_favourite_button:hover {
background-color: rgba(56, 56, 56, 0.9);
}
#gamesummarytext_label { #gamesummarytext_label {
} }
@@ -813,6 +1006,14 @@ table .romrow:nth-child(odd) {
cursor: pointer; cursor: pointer;
} }
.favouriteicon {
height: 24px;
width: 24px;
margin-top: 4px;
cursor: pointer;
filter: invert(11%) sepia(100%) saturate(6854%) hue-rotate(359deg) brightness(103%) contrast(122%) drop-shadow(3px 5px 2px rgb(0 0 0 / 0.4));
}
th { th {
text-align: left; text-align: left;
padding: 5px; padding: 5px;

Some files were not shown because too many files have changed in this diff Show More