Compare commits

...

22 Commits

Author SHA1 Message Date
Michael Green
8519d4c745 Fixed EJS directory 2024-06-29 23:04:14 +10:00
Michael Green
822a40b61b Workflows updated to push Docker images to GitHub 2024-06-29 22:55:50 +10:00
Michael Green
b463bb6064 EJS Amiga fix 2024-06-29 22:49:34 +10:00
Michael Green
3e8696e6e8 Removed Hasheous POC - full Hasheous support will be available in 1.8.0 (#379) 2024-06-28 16:20:22 +10:00
Michael Green
d1f157ac08 Disable platform logo loading - these aren't used, added try/catch block around Hasheous to deal with failures. 2024-06-28 11:03:59 +10:00
Michael Green
30be179367 Improved maintenance tasks (#378)
During the daily maintenance task, server logs are now deleted in 1000
record chunks. It repeats this a maximum of 1000 times or until there
are no more records left to delete.

During the weekly maintenance task, the optimise task now has a longer
timeout.

Closes #352
2024-06-26 22:45:38 +10:00
Michael Green
787bb47bd3 Resolved many database errors (#377)
* Resolved missing table errors.
* These were due to some dynamically created tables being queried before
they were created.
  * These tables are now created at start up.
* Resolved many "INSERT" errors that were polluting the logs:
* These were due to a race condition where sometimes the database would
return the data as not being in the database causing Gaseous to try to
insert it - even though the data was already there.
2024-06-26 15:00:09 +10:00
Michael Green
ccf9afd561 Added extra logging when generating hashes (#376)
When hashing large files, it can appear that nothing is happening. This
change adds log entries before hashes are generated to indicate that
hashing is about to begin.
2024-06-26 06:25:05 +10:00
Michael Green
c7b6233ad6 Build a docker image that includes an embedded MariaDB server (#373)
This PR modifies the build process to generate two docker images; the
standard image, and one with MariaDB embedded into it for one click
installs.

This embedded MariaDB image is meant to support users on systems like
Unraid that don't easily support docker-compose.

Also; for users on Unraid, it will allow the creation of a single click
install template to be submitted to the Unraid marketplace (this will be
done at a later date after the release of 1.7.4).
2024-06-26 06:02:20 +10:00
Michael Green
afb72c3d74 Updated release actions 2024-06-25 23:48:08 +10:00
Michael Green
299e24793f Another change 2024-06-25 23:23:21 +10:00
Michael Green
de9b9bf706 More changes 2024-06-25 23:18:47 +10:00
Michael Green
28bc50a82e Path change 2024-06-25 23:03:28 +10:00
Michael Green
f9f65f3ffb Permission change 2024-06-25 22:57:39 +10:00
Michael Green
87f3a1aa89 More testing 2024-06-25 22:46:13 +10:00
Michael Green
b3e7696292 Added permission clause 2024-06-25 22:29:44 +10:00
Michael Green
29c685c791 Docker build test 2024-06-25 22:00:31 +10:00
Michael Green
f0020e5b1f Built a docker file that builds Gaseous with MariaDB built in 2024-06-25 22:00:03 +10:00
Michael Green
3897ed65ef Fix for first time start where new users weren't offered the opportunity to create a new account (#372) 2024-06-25 08:57:56 +10:00
Michael Green
cebab38dd6 Provide ability to upload save states (#371)
This change provides the ability to upload save states.

When a state is downloaded, the state (savestate.state), screenshot
(screenshot.jpg), and a json file (rominfo.json) describing the save
state are zipped and downloaded.

The json file description is defined as follows:
```json
{
  "Name": "Super Mario Bros. (1985-09-13)(Nintendo)(JP-US).zip",
  "StateDateTime": "2024-06-24T05:34:38",
  "StateName": "",
  "MD5": "7d158dcd242e77ba249ac8342474aa77",
  "SHA1": "3d4b04dc78f9d998f17d9fe9ad982a83b5ed72df",
  "Type": "ROM"
}
```

| Attribute | Value |
| -------- | ------|
| Name | The name of the ROM that the state belongs to. This is merely a
convenience attribute. |
| StateDateTime | The date and time (in UTC) when the state was
initially saved. |
| StateName | The name of the state |
| MD5 | The MD5 hash of the ROM that the state belongs to. |
| SHA1 | The SHA1 hash of the ROM that the state belongs to. |
| Type | Whether the state belongs to a ROM or ROM Group |

If the zip is re-uploaded, the above json file will be used to
automatically match the saved state to the ROM that created it.

If a zip is uploaded without the above three files, the upload will
fail.

If a file is uploaded that is not a zip, it will be stored against the
currently running ROM and a warning will be displayed that Gaseous was
unable to verify that the state belongs to the ROM, and may not function
as expected.

Closes #336
2024-06-24 22:38:54 +10:00
Michael Green
69da6ee346 Merge branch 'main' into branch-v1.7.0 2024-05-01 15:27:14 +10:00
Michael Green
f85109abd2 Merge Branch v1.7.0 into main (#348) 2024-04-16 11:54:28 +10:00
28 changed files with 1157 additions and 517 deletions

View File

@@ -9,9 +9,14 @@ on:
jobs: jobs:
docker: docker:
runs-on: ubuntu-latest runs-on: ubuntu-latest
permissions:
packages: write
contents: read
attestations: write
id-token: write
steps: steps:
- name: Checkout - name: Checkout
uses: actions/checkout@v3 uses: actions/checkout@v4
with: with:
submodules: 'true' submodules: 'true'
- name: Install dotnet tool - name: Install dotnet tool
@@ -21,18 +26,37 @@ jobs:
- 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: Set up QEMU - name: Set up QEMU
uses: docker/setup-qemu-action@v2 uses: docker/setup-qemu-action@v3
- name: Set up Docker Buildx - name: Set up Docker Buildx
uses: docker/setup-buildx-action@v2 uses: docker/setup-buildx-action@v3
- name: Login to Docker Hub - name: Login to Docker Hub
uses: docker/login-action@v2 uses: docker/login-action@v3
with: with:
username: ${{ secrets.DOCKERHUB_USERNAME }} username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_TOKEN }} password: ${{ secrets.DOCKERHUB_TOKEN }}
- name: Build and push - name: Login to GitHub Package Registry
uses: docker/build-push-action@v4 uses: docker/login-action@v3
with:
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Build and push standard image
uses: docker/build-push-action@v6
with: with:
context: . context: .
file: ./build/Dockerfile
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}}
ghcr.io/gaseous-project/gaseousserver:${{ github.ref_name}}
- name: Build and push image with embedded mariadb
uses: docker/build-push-action@v6
with:
context: .
file: ./build/Dockerfile-EmbeddedDB
platforms: linux/amd64,linux/arm64
push: true
tags: |
gaseousgames/gaseousserver:${{ github.ref_name}}-embeddeddb
ghcr.io/gaseous-project/gaseousserver:${{ github.ref_name}}-embeddeddb

View File

@@ -8,9 +8,14 @@ on:
jobs: jobs:
docker: docker:
runs-on: ubuntu-latest runs-on: ubuntu-latest
permissions:
packages: write
contents: read
attestations: write
id-token: write
steps: steps:
- name: Checkout - name: Checkout
uses: actions/checkout@v3 uses: actions/checkout@v4
with: with:
submodules: 'true' submodules: 'true'
- name: Install dotnet tool - name: Install dotnet tool
@@ -20,18 +25,39 @@ jobs:
- 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: Set up QEMU - name: Set up QEMU
uses: docker/setup-qemu-action@v2 uses: docker/setup-qemu-action@v3
- name: Set up Docker Buildx - name: Set up Docker Buildx
uses: docker/setup-buildx-action@v2 uses: docker/setup-buildx-action@v3
- name: Login to Docker Hub - name: Login to Docker Hub
uses: docker/login-action@v2 uses: docker/login-action@v3
with: with:
username: ${{ secrets.DOCKERHUB_USERNAME }} username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_TOKEN }} password: ${{ secrets.DOCKERHUB_TOKEN }}
- name: Build and push - name: Login to GitHub Package Registry
uses: docker/build-push-action@v4 uses: docker/login-action@v3
with:
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Build and push standard image
uses: docker/build-push-action@v6
with: with:
context: . context: .
file: ./build/Dockerfile
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}}
ghcr.io/gaseous-project/gaseousserver:latest
ghcr.io/gaseous-project/gaseousserver:${{ github.ref_name}}
- name: Build and push image with embedded mariadb
uses: docker/build-push-action@v6
with:
context: .
file: ./build/Dockerfile-EmbeddedDB
platforms: linux/amd64,linux/arm64
push: true
tags: |
gaseousgames/gaseousserver:${{ github.ref_name}}-embeddeddb
ghcr.io/gaseous-project/gaseousserver:${{ github.ref_name}}-embeddeddb

View File

@@ -8,21 +8,34 @@ RUN echo "Target: $TARGETARCH"
RUN echo "Build: $BUILDPLATFORM" 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" -a $TARGETARCH 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 true -c Release -o out -a $TARGETARCH 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 # disabled for 1.7.4 as the next version EmulatorJS is not yet available
RUN apt-get update && apt-get install -y p7zip-full # # update apt-get
# RUN apt-get update
# # download and unzip EmulatorJS from CDN
# RUN apt-get install -y p7zip-full
# RUN mkdir -p out/wwwroot/emulators/EmulatorJS
# RUN wget https://cdn.emulatorjs.org/releases/4.0.12.7z
# RUN 7z x -y -oout/wwwroot/emulators/EmulatorJS 4.0.12.7z
RUN wget --recursive --no-parent https://cdn.emulatorjs.org/latest/
RUN mkdir -p out/wwwroot/emulators/EmulatorJS RUN mkdir -p out/wwwroot/emulators/EmulatorJS
RUN wget https://cdn.emulatorjs.org/releases/4.0.12.7z RUN cp -fr cdn.emulatorjs.org/latest/* out/wwwroot/emulators/EmulatorJS
RUN 7z x -y -oout/wwwroot/emulators/EmulatorJS 4.0.12.7z RUN rm -Rf cdn.emulatorjs.org
# clean up apt-get
RUN apt-get clean && rm -rf /var/lib/apt/lists
# Build runtime image # Build runtime image
FROM mcr.microsoft.com/dotnet/aspnet:8.0 FROM mcr.microsoft.com/dotnet/aspnet:8.0
ENV INDOCKER=1 ENV INDOCKER=1
WORKDIR /App WORKDIR /App
COPY --from=build-env /App/out . COPY --from=build-env /App/out .
# start gaseous-server
ENTRYPOINT ["dotnet", "gaseous-server.dll"] ENTRYPOINT ["dotnet", "gaseous-server.dll"]

View File

@@ -0,0 +1,61 @@
FROM --platform=$BUILDPLATFORM mcr.microsoft.com/dotnet/sdk:8.0 AS build-env
ARG TARGETARCH
ARG BUILDPLATFORM
WORKDIR /App
EXPOSE 80
RUN echo "Target: $TARGETARCH"
RUN echo "Build: $BUILDPLATFORM"
# Copy everything
COPY .. ./
# Restore as distinct layers
RUN dotnet restore "gaseous-server/gaseous-server.csproj" -a $TARGETARCH
# Build and publish a release
RUN dotnet publish "gaseous-server/gaseous-server.csproj" --use-current-runtime --self-contained true -c Release -o out -a $TARGETARCH
# disabled for 1.7.4 as the next version EmulatorJS is not yet available
# # update apt-get
# RUN apt-get update
# # download and unzip EmulatorJS from CDN
# RUN apt-get install -y p7zip-full
# RUN mkdir -p out/wwwroot/emulators/EmulatorJS
# RUN wget https://cdn.emulatorjs.org/releases/4.0.12.7z
# RUN 7z x -y -oout/wwwroot/emulators/EmulatorJS 4.0.12.7z
RUN wget --recursive --no-parent https://cdn.emulatorjs.org/latest/
RUN mkdir -p out/wwwroot/emulators/EmulatorJS
RUN cp -fr cdn.emulatorjs.org/latest/* out/wwwroot/emulators/EmulatorJS
RUN rm -Rf cdn.emulatorjs.org
# Build runtime image
FROM mcr.microsoft.com/dotnet/aspnet:8.0
ENV INDOCKER=1
WORKDIR /App
COPY --from=build-env /App/out .
# variables
ENV dbhost=localhost
ENV dbuser=root
ENV dbpass=gaseous
ENV MARIADB_ROOT_PASSWORD=$dbpass
# install mariadb
RUN DEBIAN_FRONTEND=noninteractive && \
apt-get update && apt-get install -y mariadb-server
RUN mkdir -p /run/mysqld
COPY ../build/mariadb.sh /usr/sbin/start-mariadb.sh
RUN chmod +x /usr/sbin/start-mariadb.sh
# install supervisord
RUN apt-get install -y supervisor
COPY ../build/supervisord.conf /etc/supervisor/conf.d/supervisord.conf
# clean up apt-get
RUN apt-get clean && rm -rf /var/lib/apt/lists
# volumes
VOLUME /root/.gaseous-server /var/lib/mysql
# start services
CMD ["/usr/bin/supervisord", "-c", "/etc/supervisor/conf.d/supervisord.conf"]

9
build/mariadb.sh Normal file
View File

@@ -0,0 +1,9 @@
#!/bin/bash
# Wait for the service to start
while ! mysqladmin ping -h localhost --silent; do
sleep 1
done
# Set the root password
mariadb -e "ALTER USER 'root'@'localhost' IDENTIFIED BY '$MARIADB_ROOT_PASSWORD';"

31
build/supervisord.conf Normal file
View File

@@ -0,0 +1,31 @@
[supervisord]
user=root
nodaemon=true
logfile=/dev/null
logfile_maxbytes=0
pidfile=/var/run/supervisord.pid
loglevel = INFO
[program:mariadb]
command=/usr/sbin/mariadbd --user=root
autostart=true
autorestart=true
redirect_stderr=true
stdout_logfile=/dev/fd/1
stdout_logfile_maxbytes=0
[program:mariadb-setup]
command=bash -c "/usr/sbin/start-mariadb.sh"
autostart=true
autorestart=false
redirect_stderr=true
stdout_logfile=/dev/fd/1
stdout_logfile_maxbytes=0
[program:gaseous-server]
command=dotnet /App/gaseous-server.dll
autostart=true
autorestart=true
redirect_stderr=true
stdout_logfile=/dev/fd/1
stdout_logfile_maxbytes=0

View File

@@ -19,7 +19,8 @@ namespace gaseous_server.Classes
if (ObjectToCheck == null || ObjectToCheck == System.DBNull.Value) if (ObjectToCheck == null || ObjectToCheck == System.DBNull.Value)
{ {
return IfNullValue; return IfNullValue;
} else }
else
{ {
return ObjectToCheck; return ObjectToCheck;
} }
@@ -27,10 +28,10 @@ namespace gaseous_server.Classes
static public DateTime ConvertUnixToDateTime(double UnixTimeStamp) static public DateTime ConvertUnixToDateTime(double UnixTimeStamp)
{ {
DateTime dateTime = new DateTime(1970, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc); DateTime dateTime = new DateTime(1970, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc);
dateTime = dateTime.AddSeconds(UnixTimeStamp).ToLocalTime(); dateTime = dateTime.AddSeconds(UnixTimeStamp).ToLocalTime();
return dateTime; return dateTime;
} }
public class hashObject public class hashObject
{ {
@@ -41,21 +42,23 @@ namespace gaseous_server.Classes
public hashObject(string FileName) public hashObject(string FileName)
{ {
var xmlStream = File.OpenRead(FileName); var xmlStream = File.OpenRead(FileName);
var md5 = MD5.Create(); Logging.Log(Logging.LogType.Information, "Hash File", "Generating MD5 hash for file: " + FileName);
byte[] md5HashByte = md5.ComputeHash(xmlStream); var md5 = MD5.Create();
string md5Hash = BitConverter.ToString(md5HashByte).Replace("-", "").ToLowerInvariant(); byte[] md5HashByte = md5.ComputeHash(xmlStream);
string md5Hash = BitConverter.ToString(md5HashByte).Replace("-", "").ToLowerInvariant();
_md5hash = md5Hash; _md5hash = md5Hash;
var sha1 = SHA1.Create(); Logging.Log(Logging.LogType.Information, "Hash File", "Generating SHA1 hash for file: " + FileName);
var sha1 = SHA1.Create();
xmlStream.Position = 0; xmlStream.Position = 0;
byte[] sha1HashByte = sha1.ComputeHash(xmlStream); byte[] sha1HashByte = sha1.ComputeHash(xmlStream);
string sha1Hash = BitConverter.ToString(sha1HashByte).Replace("-", "").ToLowerInvariant(); string sha1Hash = BitConverter.ToString(sha1HashByte).Replace("-", "").ToLowerInvariant();
_sha1hash = sha1Hash; _sha1hash = sha1Hash;
xmlStream.Close(); xmlStream.Close();
} }
string _md5hash = ""; string _md5hash = "";
string _sha1hash = ""; string _sha1hash = "";
@@ -85,29 +88,29 @@ namespace gaseous_server.Classes
} }
} }
public static long DirSize(DirectoryInfo d) public static long DirSize(DirectoryInfo d)
{ {
long size = 0; long size = 0;
// Add file sizes. // Add file sizes.
FileInfo[] fis = d.GetFiles(); FileInfo[] fis = d.GetFiles();
foreach (FileInfo fi in fis) foreach (FileInfo fi in fis)
{ {
size += fi.Length; size += fi.Length;
} }
// Add subdirectory sizes. // Add subdirectory sizes.
DirectoryInfo[] dis = d.GetDirectories(); DirectoryInfo[] dis = d.GetDirectories();
foreach (DirectoryInfo di in dis) foreach (DirectoryInfo di in dis)
{ {
size += DirSize(di); size += DirSize(di);
} }
return size; return size;
} }
public static string[] SkippableFiles = { public static string[] SkippableFiles = {
".DS_STORE", ".DS_STORE",
"desktop.ini" "desktop.ini"
}; };
public static string NormalizePath(string path) public static string NormalizePath(string path)
{ {
return Path.GetFullPath(new Uri(path).LocalPath) return Path.GetFullPath(new Uri(path).LocalPath)
@@ -155,30 +158,30 @@ namespace gaseous_server.Classes
return defaultValue; return defaultValue;
} }
} }
} }
/// <summary> /// <summary>
/// Provides a way to set contextual data that flows with the call and /// Provides a way to set contextual data that flows with the call and
/// async context of a test or invocation. /// async context of a test or invocation.
/// </summary> /// </summary>
public static class CallContext public static class CallContext
{ {
static ConcurrentDictionary<string, AsyncLocal<object>> state = new ConcurrentDictionary<string, AsyncLocal<object>>(); static ConcurrentDictionary<string, AsyncLocal<object>> state = new ConcurrentDictionary<string, AsyncLocal<object>>();
/// <summary> /// <summary>
/// Stores a given object and associates it with the specified name. /// Stores a given object and associates it with the specified name.
/// </summary> /// </summary>
/// <param name="name">The name with which to associate the new item in the call context.</param> /// <param name="name">The name with which to associate the new item in the call context.</param>
/// <param name="data">The object to store in the call context.</param> /// <param name="data">The object to store in the call context.</param>
public static void SetData(string name, object data) => public static void SetData(string name, object data) =>
state.GetOrAdd(name, _ => new AsyncLocal<object>()).Value = data; state.GetOrAdd(name, _ => new AsyncLocal<object>()).Value = data;
/// <summary> /// <summary>
/// Retrieves an object with the specified name from the <see cref="CallContext"/>. /// Retrieves an object with the specified name from the <see cref="CallContext"/>.
/// </summary> /// </summary>
/// <param name="name">The name of the item in the call context.</param> /// <param name="name">The name of the item in the call context.</param>
/// <returns>The object in the call context associated with the specified name, or <see langword="null"/> if not found.</returns> /// <returns>The object in the call context associated with the specified name, or <see langword="null"/> if not found.</returns>
public static object GetData(string name) => public static object GetData(string name) =>
state.TryGetValue(name, out AsyncLocal<object> data) ? data.Value : null; state.TryGetValue(name, out AsyncLocal<object> data) ? data.Value : null;
} }
} }

View File

@@ -264,37 +264,47 @@ namespace gaseous_server.Classes
if (Config.MetadataConfiguration.SignatureSource == HasheousClient.Models.MetadataModel.SignatureSources.Hasheous) if (Config.MetadataConfiguration.SignatureSource == HasheousClient.Models.MetadataModel.SignatureSources.Hasheous)
{ {
HasheousClient.Hasheous hasheous = new HasheousClient.Hasheous(); HasheousClient.Hasheous hasheous = new HasheousClient.Hasheous();
SignatureLookupItem? HasheousResult = hasheous.RetrieveFromHasheousAsync(new HashLookupModel{ SignatureLookupItem? HasheousResult = null;
MD5 = hash.md5hash,
SHA1 = hash.sha1hash
});
if (HasheousResult != null) try
{ {
if (HasheousResult.Signature != null) HasheousResult = hasheous.RetrieveFromHasheousAsync(new HashLookupModel
{ {
gaseous_server.Models.Signatures_Games signature = new Models.Signatures_Games(); MD5 = hash.md5hash,
signature.Game = HasheousResult.Signature.Game; SHA1 = hash.sha1hash
signature.Rom = HasheousResult.Signature.Rom; });
if (HasheousResult.MetadataResults != null) if (HasheousResult != null)
{
if (HasheousResult.Signature != null)
{ {
if (HasheousResult.MetadataResults.Count > 0) gaseous_server.Models.Signatures_Games signature = new Models.Signatures_Games();
signature.Game = HasheousResult.Signature.Game;
signature.Rom = HasheousResult.Signature.Rom;
if (HasheousResult.MetadataResults != null)
{ {
foreach (SignatureLookupItem.MetadataResult metadataResult in HasheousResult.MetadataResults) if (HasheousResult.MetadataResults.Count > 0)
{ {
if (metadataResult.Source == MetadataModel.MetadataSources.IGDB) foreach (SignatureLookupItem.MetadataResult metadataResult in HasheousResult.MetadataResults)
{ {
signature.Flags.IGDBPlatformId = (long)metadataResult.PlatformId; if (metadataResult.Source == MetadataModel.MetadataSources.IGDB)
signature.Flags.IGDBGameId = (long)metadataResult.GameId; {
signature.Flags.IGDBPlatformId = (long)metadataResult.PlatformId;
signature.Flags.IGDBGameId = (long)metadataResult.GameId;
}
} }
} }
} }
}
return signature; return signature;
}
} }
} }
catch (Exception ex)
{
Logging.Log(Logging.LogType.Warning, "Get Signature", "Error retrieving signature from Hasheous", ex);
}
} }
return null; return null;

View File

@@ -5,7 +5,7 @@ using Microsoft.VisualStudio.Web.CodeGeneration;
namespace gaseous_server.Classes namespace gaseous_server.Classes
{ {
public class Maintenance : QueueItemStatus public class Maintenance : QueueItemStatus
{ {
const int MaxFileAge = 30; const int MaxFileAge = 30;
@@ -16,6 +16,7 @@ namespace gaseous_server.Classes
Dictionary<string, object> dbDict = new Dictionary<string, object>(); Dictionary<string, object> dbDict = new Dictionary<string, object>();
// remove any entries from the library that have an invalid id // remove any entries from the library that have an invalid id
Logging.Log(Logging.LogType.Information, "Maintenance", "Removing any entries from the library that have an invalid id");
string LibraryWhereClause = ""; string LibraryWhereClause = "";
foreach (GameLibrary.LibraryItem library in GameLibrary.GetLibraries) foreach (GameLibrary.LibraryItem library in GameLibrary.GetLibraries)
{ {
@@ -33,9 +34,27 @@ namespace gaseous_server.Classes
} }
// delete old logs // delete old logs
sql = "DELETE FROM ServerLogs WHERE EventTime < @EventRetentionDate;"; Logging.Log(Logging.LogType.Information, "Maintenance", "Removing logs older than " + Config.LoggingConfiguration.LogRetention + " days");
dbDict.Add("EventRetentionDate", DateTime.UtcNow.AddDays(Config.LoggingConfiguration.LogRetention * -1)); long deletedCount = 1;
db.ExecuteCMD(sql, dbDict); long deletedEventCount = 0;
long maxLoops = 1000;
while (deletedCount > 0)
{
sql = "DELETE FROM ServerLogs WHERE EventTime < @EventRetentionDate LIMIT 1000; SELECT ROW_COUNT() AS Count;";
dbDict.Add("EventRetentionDate", DateTime.UtcNow.AddDays(Config.LoggingConfiguration.LogRetention * -1));
DataTable deletedCountTable = db.ExecuteCMD(sql, dbDict);
deletedCount = (long)deletedCountTable.Rows[0][0];
deletedEventCount += deletedCount;
// check if we've hit the limit
maxLoops -= 1;
if (maxLoops <= 0)
{
Logging.Log(Logging.LogType.Warning, "Maintenance", "Hit the maximum number of loops for deleting logs. Stopping.");
break;
}
}
Logging.Log(Logging.LogType.Information, "Maintenance", "Deleted " + deletedEventCount + " log entries");
// delete files and directories older than 7 days in PathsToClean // delete files and directories older than 7 days in PathsToClean
List<string> PathsToClean = new List<string>(); List<string> PathsToClean = new List<string>();
@@ -87,7 +106,7 @@ namespace gaseous_server.Classes
SetStatus(StatusCounter, tables.Rows.Count, "Optimising table " + row[0].ToString()); SetStatus(StatusCounter, tables.Rows.Count, "Optimising table " + row[0].ToString());
sql = "OPTIMIZE TABLE " + row[0].ToString(); sql = "OPTIMIZE TABLE " + row[0].ToString();
DataTable response = db.ExecuteCMD(sql); DataTable response = db.ExecuteCMD(sql, new Dictionary<string, object>(), 240);
foreach (DataRow responseRow in response.Rows) foreach (DataRow responseRow in response.Rows)
{ {
string retVal = ""; string retVal = "";

View File

@@ -7,7 +7,7 @@ using Microsoft.CodeAnalysis.Elfie.Model.Strings;
namespace gaseous_server.Classes.Metadata namespace gaseous_server.Classes.Metadata
{ {
public class Covers public class Covers
{ {
const string fieldList = "fields alpha_channel,animated,checksum,game,game_localization,height,image_id,url,width;"; const string fieldList = "fields alpha_channel,animated,checksum,game,game_localization,height,image_id,url,width;";
@@ -70,7 +70,7 @@ namespace gaseous_server.Classes.Metadata
returnValue = await GetObjectFromServer(WhereClause, ImagePath); returnValue = await GetObjectFromServer(WhereClause, ImagePath);
Storage.NewCacheValue(returnValue); Storage.NewCacheValue(returnValue);
forceImageDownload = true; forceImageDownload = true;
break; break;
case Storage.CacheStatus.Expired: case Storage.CacheStatus.Expired:
try try
{ {
@@ -135,6 +135,6 @@ namespace gaseous_server.Classes.Metadata
return result; return result;
} }
} }
} }

View File

@@ -5,8 +5,8 @@ using IGDB.Models;
namespace gaseous_server.Classes.Metadata 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,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;"; 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()
@@ -15,9 +15,9 @@ namespace gaseous_server.Classes.Metadata
} }
public class InvalidGameId : Exception public class InvalidGameId : Exception
{ {
public InvalidGameId(long Id) : base("Unable to find Game by id " + Id) public InvalidGameId(long Id) : base("Unable to find Game by id " + Id)
{} { }
} }
public static Game? GetGame(long Id, bool getAllMetadata, bool followSubGames, bool forceRefresh) public static Game? GetGame(long Id, bool getAllMetadata, bool followSubGames, bool forceRefresh)
@@ -125,17 +125,17 @@ namespace gaseous_server.Classes.Metadata
private static void UpdateSubClasses(Game Game, bool getAllMetadata, bool followSubGames, bool forceRefresh) private static void UpdateSubClasses(Game Game, bool getAllMetadata, bool followSubGames, bool forceRefresh)
{ {
// required metadata // required metadata
if (Game.Cover != null) // if (Game.Cover != null)
{ // {
try // try
{ // {
Cover GameCover = Covers.GetCover(Game.Cover.Id, Config.LibraryConfiguration.LibraryMetadataDirectory_Game(Game), forceRefresh); // Cover GameCover = Covers.GetCover(Game.Cover.Id, Config.LibraryConfiguration.LibraryMetadataDirectory_Game(Game), forceRefresh);
} // }
catch (Exception ex) // catch (Exception ex)
{ // {
Logging.Log(Logging.LogType.Critical, "Game Metadata", "Unable to fetch cover artwork.", ex); // Logging.Log(Logging.LogType.Critical, "Game Metadata", "Unable to fetch cover artwork.", ex);
} // }
} // }
if (Game.Genres != null) if (Game.Genres != null)
{ {
@@ -285,7 +285,7 @@ namespace gaseous_server.Classes.Metadata
{ {
try try
{ {
Screenshot GameScreenshot = Screenshots.GetScreenshot(ScreenshotId, Config.LibraryConfiguration.LibraryMetadataDirectory_Game(Game), forceRefresh); Screenshot GameScreenshot = Screenshots.GetScreenshot(ScreenshotId, Config.LibraryConfiguration.LibraryMetadataDirectory_Game(Game), forceRefresh);
} }
catch (Exception ex) catch (Exception ex)
{ {
@@ -347,7 +347,7 @@ namespace gaseous_server.Classes.Metadata
// get missing metadata from parent if this is a port // get missing metadata from parent if this is a port
if (result.Category == Category.Port) if (result.Category == Category.Port)
{ {
if (result.Summary == null) if (result.Summary == null)
{ {
if (result.ParentGame != null) if (result.ParentGame != null)
@@ -364,7 +364,7 @@ namespace gaseous_server.Classes.Metadata
return result; return result;
} }
public static void AssignAllGamesToPlatformIdZero() public static void AssignAllGamesToPlatformIdZero()
{ {
Database db = new Database(Database.databaseType.MySql, Config.DatabaseConfiguration.ConnectionString); Database db = new Database(Database.databaseType.MySql, Config.DatabaseConfiguration.ConnectionString);
@@ -428,7 +428,7 @@ namespace gaseous_server.Classes.Metadata
} }
string sql = "SELECT Game.Id, Game.`Name`, Game.Slug, Relation_Game_Platforms.PlatformsId AS PlatformsId, Game.Summary FROM gaseous.Game JOIN Relation_Game_Platforms ON Game.Id = Relation_Game_Platforms.GameId WHERE " + whereClause + ";"; string sql = "SELECT Game.Id, Game.`Name`, Game.Slug, Relation_Game_Platforms.PlatformsId AS PlatformsId, Game.Summary FROM gaseous.Game JOIN Relation_Game_Platforms ON Game.Id = Relation_Game_Platforms.GameId WHERE " + whereClause + ";";
// get Game metadata // get Game metadata
Game[]? results = new Game[0]; Game[]? results = new Game[0];
@@ -439,7 +439,8 @@ namespace gaseous_server.Classes.Metadata
DataTable data = db.ExecuteCMD(sql, dbDict); DataTable data = db.ExecuteCMD(sql, dbDict);
foreach (DataRow row in data.Rows) foreach (DataRow row in data.Rows)
{ {
Game game = new Game{ Game game = new Game
{
Id = (long)row["Id"], Id = (long)row["Id"],
Name = (string)Common.ReturnValueIfNull(row["Name"], ""), Name = (string)Common.ReturnValueIfNull(row["Name"], ""),
Slug = (string)Common.ReturnValueIfNull(row["Slug"], ""), Slug = (string)Common.ReturnValueIfNull(row["Slug"], ""),
@@ -476,12 +477,12 @@ namespace gaseous_server.Classes.Metadata
searchBody = "where platforms = (" + PlatformId + ") & name ~ \"" + SearchString + "\";"; searchBody = "where platforms = (" + PlatformId + ") & name ~ \"" + SearchString + "\";";
break; break;
} }
// check search cache // check search cache
Game[]? games = Communications.GetSearchCache<Game[]?>(searchFields, searchBody); Game[]? games = Communications.GetSearchCache<Game[]?>(searchFields, searchBody);
if (games == null) if (games == null)
{ {
// cache miss // cache miss
// get Game metadata // get Game metadata
Communications comms = new Communications(); Communications comms = new Communications();
@@ -513,7 +514,7 @@ namespace gaseous_server.Classes.Metadata
foreach (DataRow row in data.Rows) foreach (DataRow row in data.Rows)
{ {
KeyValuePair<long, string> valuePair = new KeyValuePair<long, string>((long)row["PlatformId"], (string)row["Name"]); KeyValuePair<long, string> valuePair = new KeyValuePair<long, string>((long)row["PlatformId"], (string)row["Name"]);
platforms.Add(valuePair); platforms.Add(valuePair);
} }
return platforms; return platforms;
@@ -533,7 +534,7 @@ namespace gaseous_server.Classes.Metadata
{ {
} }
public MinimalGameItem(Game gameObject) public MinimalGameItem(Game gameObject)
{ {
this.Id = gameObject.Id; this.Id = gameObject.Id;

View File

@@ -5,15 +5,15 @@ using IGDB.Models;
namespace gaseous_server.Classes.Metadata namespace gaseous_server.Classes.Metadata
{ {
public class PlatformVersions public class PlatformVersions
{ {
const string fieldList = "fields checksum,companies,connectivity,cpu,graphics,main_manufacturer,media,memory,name,online,os,output,platform_logo,platform_version_release_dates,resolutions,slug,sound,storage,summary,url;"; const string fieldList = "fields checksum,companies,connectivity,cpu,graphics,main_manufacturer,media,memory,name,online,os,output,platform_logo,platform_version_release_dates,resolutions,slug,sound,storage,summary,url;";
public PlatformVersions() public PlatformVersions()
{ {
} }
public static PlatformVersion? GetPlatformVersion(long Id, Platform ParentPlatform) public static PlatformVersion? GetPlatformVersion(long Id, Platform ParentPlatform, bool GetImages = false)
{ {
if (Id == 0) if (Id == 0)
{ {
@@ -21,18 +21,18 @@ namespace gaseous_server.Classes.Metadata
} }
else else
{ {
Task<PlatformVersion> RetVal = _GetPlatformVersion(SearchUsing.id, Id, ParentPlatform); Task<PlatformVersion> RetVal = _GetPlatformVersion(SearchUsing.id, Id, ParentPlatform, GetImages);
return RetVal.Result; return RetVal.Result;
} }
} }
public static PlatformVersion GetPlatformVersion(string Slug, Platform ParentPlatform) public static PlatformVersion GetPlatformVersion(string Slug, Platform ParentPlatform, bool GetImages)
{ {
Task<PlatformVersion> RetVal = _GetPlatformVersion(SearchUsing.slug, Slug, ParentPlatform); Task<PlatformVersion> RetVal = _GetPlatformVersion(SearchUsing.slug, Slug, ParentPlatform, GetImages);
return RetVal.Result; return RetVal.Result;
} }
private static async Task<PlatformVersion> _GetPlatformVersion(SearchUsing searchUsing, object searchValue, Platform ParentPlatform) private static async Task<PlatformVersion> _GetPlatformVersion(SearchUsing searchUsing, object searchValue, Platform ParentPlatform, bool GetImages)
{ {
// check database first // check database first
Storage.CacheStatus? cacheStatus = new Storage.CacheStatus(); Storage.CacheStatus? cacheStatus = new Storage.CacheStatus();
@@ -67,7 +67,7 @@ namespace gaseous_server.Classes.Metadata
if (returnValue != null) if (returnValue != null)
{ {
Storage.NewCacheValue(returnValue); Storage.NewCacheValue(returnValue);
UpdateSubClasses(ParentPlatform, returnValue); UpdateSubClasses(ParentPlatform, returnValue, GetImages);
} }
return returnValue; return returnValue;
case Storage.CacheStatus.Expired: case Storage.CacheStatus.Expired:
@@ -75,7 +75,7 @@ namespace gaseous_server.Classes.Metadata
{ {
returnValue = await GetObjectFromServer(WhereClause); returnValue = await GetObjectFromServer(WhereClause);
Storage.NewCacheValue(returnValue, true); Storage.NewCacheValue(returnValue, true);
UpdateSubClasses(ParentPlatform, returnValue); UpdateSubClasses(ParentPlatform, returnValue, GetImages);
} }
catch (Exception ex) catch (Exception ex)
{ {
@@ -90,17 +90,20 @@ namespace gaseous_server.Classes.Metadata
} }
} }
private static void UpdateSubClasses(Platform ParentPlatform, PlatformVersion platformVersion) private static void UpdateSubClasses(Platform ParentPlatform, PlatformVersion platformVersion, bool GetImages)
{ {
if (platformVersion.PlatformLogo != null) if (GetImages == true)
{ {
try if (platformVersion.PlatformLogo != null)
{ {
PlatformLogo platformLogo = PlatformLogos.GetPlatformLogo(platformVersion.PlatformLogo.Id, Path.Combine(Config.LibraryConfiguration.LibraryMetadataDirectory_Platform(ParentPlatform), "Versions", platformVersion.Slug)); try
} {
catch (Exception ex) PlatformLogo platformLogo = PlatformLogos.GetPlatformLogo(platformVersion.PlatformLogo.Id, Path.Combine(Config.LibraryConfiguration.LibraryMetadataDirectory_Platform(ParentPlatform), "Versions", platformVersion.Slug));
{ }
Logging.Log(Logging.LogType.Warning, "Platform Update", "Unable to fetch platform logo", ex); catch (Exception ex)
{
Logging.Log(Logging.LogType.Warning, "Platform Update", "Unable to fetch platform logo", ex);
}
} }
} }
} }

View File

@@ -7,16 +7,16 @@ using IGDB.Models;
namespace gaseous_server.Classes.Metadata namespace gaseous_server.Classes.Metadata
{ {
public class Platforms public class Platforms
{ {
const string fieldList = "fields abbreviation,alternative_name,category,checksum,created_at,generation,name,platform_family,platform_logo,slug,summary,updated_at,url,versions,websites;"; const string fieldList = "fields abbreviation,alternative_name,category,checksum,created_at,generation,name,platform_family,platform_logo,slug,summary,updated_at,url,versions,websites;";
public Platforms() public Platforms()
{ {
} }
public static Platform? GetPlatform(long Id, bool forceRefresh = false) public static Platform? GetPlatform(long Id, bool forceRefresh = false, bool GetImages = false)
{ {
if (Id == 0) if (Id == 0)
{ {
Platform returnValue = new Platform(); Platform returnValue = new Platform();
@@ -41,10 +41,10 @@ namespace gaseous_server.Classes.Metadata
{ {
try try
{ {
Task<Platform> RetVal = _GetPlatform(SearchUsing.id, Id, forceRefresh); Task<Platform> RetVal = _GetPlatform(SearchUsing.id, Id, forceRefresh, GetImages);
return RetVal.Result; return RetVal.Result;
} }
catch(Exception ex) catch (Exception ex)
{ {
Logging.Log(Logging.LogType.Warning, "Metadata", "An error occurred fetching Platform Id " + Id, ex); Logging.Log(Logging.LogType.Warning, "Metadata", "An error occurred fetching Platform Id " + Id, ex);
return null; return null;
@@ -52,14 +52,14 @@ namespace gaseous_server.Classes.Metadata
} }
} }
public static Platform GetPlatform(string Slug, bool forceRefresh = false) public static Platform GetPlatform(string Slug, bool forceRefresh = false, bool GetImages = false)
{ {
Task<Platform> RetVal = _GetPlatform(SearchUsing.slug, Slug, forceRefresh); Task<Platform> RetVal = _GetPlatform(SearchUsing.slug, Slug, forceRefresh, GetImages);
return RetVal.Result; return RetVal.Result;
} }
private static async Task<Platform> _GetPlatform(SearchUsing searchUsing, object searchValue, bool forceRefresh) private static async Task<Platform> _GetPlatform(SearchUsing searchUsing, object searchValue, bool forceRefresh, bool GetImages)
{ {
// check database first // check database first
Storage.CacheStatus? cacheStatus = new Storage.CacheStatus(); Storage.CacheStatus? cacheStatus = new Storage.CacheStatus();
if (searchUsing == SearchUsing.id) if (searchUsing == SearchUsing.id)
@@ -96,7 +96,7 @@ namespace gaseous_server.Classes.Metadata
case Storage.CacheStatus.NotPresent: case Storage.CacheStatus.NotPresent:
returnValue = await GetObjectFromServer(WhereClause); returnValue = await GetObjectFromServer(WhereClause);
Storage.NewCacheValue(returnValue); Storage.NewCacheValue(returnValue);
UpdateSubClasses(returnValue); UpdateSubClasses(returnValue, GetImages);
AddPlatformMapping(returnValue); AddPlatformMapping(returnValue);
return returnValue; return returnValue;
case Storage.CacheStatus.Expired: case Storage.CacheStatus.Expired:
@@ -104,7 +104,7 @@ namespace gaseous_server.Classes.Metadata
{ {
returnValue = await GetObjectFromServer(WhereClause); returnValue = await GetObjectFromServer(WhereClause);
Storage.NewCacheValue(returnValue, true); Storage.NewCacheValue(returnValue, true);
UpdateSubClasses(returnValue); UpdateSubClasses(returnValue, GetImages);
AddPlatformMapping(returnValue); AddPlatformMapping(returnValue);
return returnValue; return returnValue;
} }
@@ -120,7 +120,7 @@ namespace gaseous_server.Classes.Metadata
} }
} }
private static void UpdateSubClasses(Platform platform) private static void UpdateSubClasses(Platform platform, bool GetImages)
{ {
if (platform.Versions != null) if (platform.Versions != null)
{ {
@@ -130,15 +130,18 @@ namespace gaseous_server.Classes.Metadata
} }
} }
if (platform.PlatformLogo != null) if (GetImages == true)
{ {
try if (platform.PlatformLogo != null)
{ {
PlatformLogo platformLogo = PlatformLogos.GetPlatformLogo(platform.PlatformLogo.Id, Config.LibraryConfiguration.LibraryMetadataDirectory_Platform(platform)); try
} {
catch (Exception ex) PlatformLogo platformLogo = PlatformLogos.GetPlatformLogo(platform.PlatformLogo.Id, Config.LibraryConfiguration.LibraryMetadataDirectory_Platform(platform));
{ }
Logging.Log(Logging.LogType.Warning, "Platform Update", "Unable to fetch platform logo", ex); catch (Exception ex)
{
Logging.Log(Logging.LogType.Warning, "Platform Update", "Unable to fetch platform logo", ex);
}
} }
} }
} }
@@ -158,11 +161,12 @@ namespace gaseous_server.Classes.Metadata
{ {
Logging.Log(Logging.LogType.Information, "Platform Map", "Importing " + platform.Name + " from predefined data."); Logging.Log(Logging.LogType.Information, "Platform Map", "Importing " + platform.Name + " from predefined data.");
// doesn't exist - add it // doesn't exist - add it
item = new Models.PlatformMapping.PlatformMapItem{ item = new Models.PlatformMapping.PlatformMapItem
{
IGDBId = (long)platform.Id, IGDBId = (long)platform.Id,
IGDBName = platform.Name, IGDBName = platform.Name,
IGDBSlug = platform.Slug, IGDBSlug = platform.Slug,
AlternateNames = new List<string>{ platform.AlternativeName } AlternateNames = new List<string> { platform.AlternativeName }
}; };
Models.PlatformMapping.WritePlatformMap(item, false, true); Models.PlatformMapping.WritePlatformMap(item, false, true);
} }

View File

@@ -7,19 +7,19 @@ using Microsoft.Extensions.Caching.Memory;
namespace gaseous_server.Classes.Metadata namespace gaseous_server.Classes.Metadata
{ {
public class Storage public class Storage
{ {
public enum CacheStatus public enum CacheStatus
{ {
NotPresent, NotPresent,
Current, Current,
Expired Expired
} }
public static CacheStatus GetCacheStatus(string Endpoint, string Slug) public static CacheStatus GetCacheStatus(string Endpoint, string Slug)
{ {
return _GetCacheStatus(Endpoint, "slug", Slug); return _GetCacheStatus(Endpoint, "slug", Slug);
} }
public static CacheStatus GetCacheStatus(string Endpoint, long Id) public static CacheStatus GetCacheStatus(string Endpoint, long Id)
{ {
@@ -47,124 +47,124 @@ namespace gaseous_server.Classes.Metadata
} }
private static CacheStatus _GetCacheStatus(string Endpoint, string SearchField, object SearchValue) private static CacheStatus _GetCacheStatus(string Endpoint, string SearchField, object SearchValue)
{ {
Database db = new Database(Database.databaseType.MySql, Config.DatabaseConfiguration.ConnectionString); Database db = new Database(Database.databaseType.MySql, Config.DatabaseConfiguration.ConnectionString);
string sql = "SELECT lastUpdated FROM " + Endpoint + " WHERE " + SearchField + " = @" + SearchField; string sql = "SELECT lastUpdated FROM " + Endpoint + " WHERE " + SearchField + " = @" + SearchField;
Dictionary<string, object> dbDict = new Dictionary<string, object>(); Dictionary<string, object> dbDict = new Dictionary<string, object>();
dbDict.Add("Endpoint", Endpoint); dbDict.Add("Endpoint", Endpoint);
dbDict.Add(SearchField, SearchValue); dbDict.Add(SearchField, SearchValue);
DataTable dt = db.ExecuteCMD(sql, dbDict); DataTable dt = db.ExecuteCMD(sql, dbDict);
if (dt.Rows.Count == 0) if (dt.Rows.Count == 0)
{ {
// no data stored for this item, or lastUpdated // no data stored for this item, or lastUpdated
return CacheStatus.NotPresent; return CacheStatus.NotPresent;
} }
else else
{ {
DateTime CacheExpiryTime = DateTime.UtcNow.AddHours(-168); DateTime CacheExpiryTime = DateTime.UtcNow.AddHours(-168);
if ((DateTime)dt.Rows[0]["lastUpdated"] < CacheExpiryTime) if ((DateTime)dt.Rows[0]["lastUpdated"] < CacheExpiryTime)
{ {
return CacheStatus.Expired; return CacheStatus.Expired;
} }
else else
{ {
return CacheStatus.Current; return CacheStatus.Current;
} }
} }
} }
public static void NewCacheValue(object ObjectToCache, bool UpdateRecord = false) public static void NewCacheValue(object ObjectToCache, bool UpdateRecord = false)
{ {
// get the object type name // get the object type name
string ObjectTypeName = ObjectToCache.GetType().Name; string ObjectTypeName = ObjectToCache.GetType().Name;
// build dictionary // build dictionary
string objectJson = Newtonsoft.Json.JsonConvert.SerializeObject(ObjectToCache); string objectJson = Newtonsoft.Json.JsonConvert.SerializeObject(ObjectToCache);
Dictionary<string, object?> objectDict = Newtonsoft.Json.JsonConvert.DeserializeObject<Dictionary<string, object?>>(objectJson); Dictionary<string, object?> objectDict = Newtonsoft.Json.JsonConvert.DeserializeObject<Dictionary<string, object?>>(objectJson);
objectDict.Add("dateAdded", DateTime.UtcNow); objectDict.Add("dateAdded", DateTime.UtcNow);
objectDict.Add("lastUpdated", DateTime.UtcNow); objectDict.Add("lastUpdated", DateTime.UtcNow);
// generate sql // generate sql
string fieldList = ""; string fieldList = "";
string valueList = ""; string valueList = "";
string updateFieldValueList = ""; string updateFieldValueList = "";
foreach (KeyValuePair<string, object?> key in objectDict) foreach (KeyValuePair<string, object?> key in objectDict)
{ {
if (fieldList.Length > 0) if (fieldList.Length > 0)
{ {
fieldList = fieldList + ", "; fieldList = fieldList + ", ";
valueList = valueList + ", "; valueList = valueList + ", ";
} }
fieldList = fieldList + key.Key; fieldList = fieldList + key.Key;
valueList = valueList + "@" + key.Key; valueList = valueList + "@" + key.Key;
if ((key.Key != "id") && (key.Key != "dateAdded")) if ((key.Key != "id") && (key.Key != "dateAdded"))
{ {
if (updateFieldValueList.Length > 0) if (updateFieldValueList.Length > 0)
{ {
updateFieldValueList = updateFieldValueList + ", "; updateFieldValueList = updateFieldValueList + ", ";
} }
updateFieldValueList += key.Key + " = @" + key.Key; updateFieldValueList += key.Key + " = @" + key.Key;
} }
// check property type // check property type
Type objectType = ObjectToCache.GetType(); Type objectType = ObjectToCache.GetType();
if (objectType != null) if (objectType != null)
{ {
PropertyInfo objectProperty = objectType.GetProperty(key.Key); PropertyInfo objectProperty = objectType.GetProperty(key.Key);
if (objectProperty != null) if (objectProperty != null)
{ {
string compareName = objectProperty.PropertyType.Name.ToLower().Split("`")[0]; string compareName = objectProperty.PropertyType.Name.ToLower().Split("`")[0];
var objectValue = objectProperty.GetValue(ObjectToCache); var objectValue = objectProperty.GetValue(ObjectToCache);
if (objectValue != null) if (objectValue != null)
{ {
string newObjectValue; string newObjectValue;
Dictionary<string, object> newDict; Dictionary<string, object> newDict;
switch (compareName) switch (compareName)
{ {
case "identityorvalue": case "identityorvalue":
newObjectValue = Newtonsoft.Json.JsonConvert.SerializeObject(objectValue); newObjectValue = Newtonsoft.Json.JsonConvert.SerializeObject(objectValue);
newDict = Newtonsoft.Json.JsonConvert.DeserializeObject<Dictionary<string, object>>(newObjectValue); newDict = Newtonsoft.Json.JsonConvert.DeserializeObject<Dictionary<string, object>>(newObjectValue);
objectDict[key.Key] = newDict["Id"]; objectDict[key.Key] = newDict["Id"];
break; break;
case "identitiesorvalues": case "identitiesorvalues":
newObjectValue = Newtonsoft.Json.JsonConvert.SerializeObject(objectValue); newObjectValue = Newtonsoft.Json.JsonConvert.SerializeObject(objectValue);
newDict = Newtonsoft.Json.JsonConvert.DeserializeObject<Dictionary<string, object>>(newObjectValue); newDict = Newtonsoft.Json.JsonConvert.DeserializeObject<Dictionary<string, object>>(newObjectValue);
newObjectValue = Newtonsoft.Json.JsonConvert.SerializeObject(newDict["Ids"]); newObjectValue = Newtonsoft.Json.JsonConvert.SerializeObject(newDict["Ids"]);
objectDict[key.Key] = newObjectValue; objectDict[key.Key] = newObjectValue;
StoreRelations(ObjectTypeName, key.Key, (long)objectDict["Id"], newObjectValue); StoreRelations(ObjectTypeName, key.Key, (long)objectDict["Id"], newObjectValue);
break; break;
case "int32[]": case "int32[]":
newObjectValue = Newtonsoft.Json.JsonConvert.SerializeObject(objectValue); newObjectValue = Newtonsoft.Json.JsonConvert.SerializeObject(objectValue);
objectDict[key.Key] = newObjectValue; objectDict[key.Key] = newObjectValue;
break; break;
} }
} }
} }
} }
} }
string sql = ""; string sql = "";
if (UpdateRecord == false) if (UpdateRecord == false)
{ {
sql = "INSERT INTO " + ObjectTypeName + " (" + fieldList + ") VALUES (" + valueList + ")"; sql = "INSERT INTO " + ObjectTypeName + " (" + fieldList + ") VALUES (" + valueList + ")";
}
else
{
sql = "UPDATE " + ObjectTypeName + " SET " + updateFieldValueList + " WHERE Id = @Id";
} }
else
{
sql = "UPDATE " + ObjectTypeName + " SET " + updateFieldValueList + " WHERE Id = @Id";
}
// execute sql // execute sql
Database db = new Database(Database.databaseType.MySql, Config.DatabaseConfiguration.ConnectionString); Database db = new Database(Database.databaseType.MySql, Config.DatabaseConfiguration.ConnectionString);
db.ExecuteCMD(sql, objectDict); db.ExecuteCMD(sql, objectDict);
} }
public static T GetCacheValue<T>(T EndpointType, string SearchField, object SearchValue) public static T GetCacheValue<T>(T EndpointType, string SearchField, object SearchValue)
{ {
string Endpoint = EndpointType.GetType().Name; string Endpoint = EndpointType.GetType().Name;
Database db = new Database(Database.databaseType.MySql, Config.DatabaseConfiguration.ConnectionString); Database db = new Database(Database.databaseType.MySql, Config.DatabaseConfiguration.ConnectionString);
@@ -178,20 +178,20 @@ namespace gaseous_server.Classes.Metadata
DataTable dt = db.ExecuteCMD(sql, dbDict); DataTable dt = db.ExecuteCMD(sql, dbDict);
if (dt.Rows.Count == 0) if (dt.Rows.Count == 0)
{ {
// no data stored for this item // no data stored for this item
throw new Exception("No record found that matches endpoint " + Endpoint + " with search value " + SearchValue); throw new Exception("No record found that matches endpoint " + Endpoint + " with search value " + SearchValue);
} }
else else
{ {
DataRow dataRow = dt.Rows[0]; DataRow dataRow = dt.Rows[0];
object returnObject = BuildCacheObject<T>(EndpointType, dataRow); object returnObject = BuildCacheObject<T>(EndpointType, dataRow);
return (T)returnObject; return (T)returnObject;
} }
} }
public static T BuildCacheObject<T>(T EndpointType, DataRow dataRow) public static T BuildCacheObject<T>(T EndpointType, DataRow dataRow)
{ {
foreach (PropertyInfo property in EndpointType.GetType().GetProperties()) foreach (PropertyInfo property in EndpointType.GetType().GetProperties())
{ {
if (dataRow.Table.Columns.Contains(property.Name)) if (dataRow.Table.Columns.Contains(property.Name))
@@ -428,11 +428,35 @@ namespace gaseous_server.Classes.Metadata
} }
} }
public static void CreateRelationsTables<T>()
{
string PrimaryTable = typeof(T).Name;
foreach (PropertyInfo property in typeof(T).GetProperties())
{
string SecondaryTable = property.Name;
if (property.PropertyType.Name == "IdentitiesOrValues`1")
{
string TableName = "Relation_" + PrimaryTable + "_" + SecondaryTable;
Database db = new Database(Database.databaseType.MySql, Config.DatabaseConfiguration.ConnectionString);
string sql = "SELECT * FROM information_schema.tables WHERE table_schema = '" + Config.DatabaseConfiguration.DatabaseName + "' AND table_name = '" + TableName + "';";
DataTable data = db.ExecuteCMD(sql);
if (data.Rows.Count == 0)
{
// table doesn't exist, create it
sql = "CREATE TABLE `" + Config.DatabaseConfiguration.DatabaseName + "`.`" + TableName + "` (`" + PrimaryTable + "Id` BIGINT NOT NULL, `" + SecondaryTable + "Id` BIGINT NOT NULL, PRIMARY KEY (`" + PrimaryTable + "Id`, `" + SecondaryTable + "Id`), INDEX `idx_PrimaryColumn` (`" + PrimaryTable + "Id` ASC) VISIBLE);";
db.ExecuteCMD(sql);
}
}
}
}
private class MemoryCacheObject private class MemoryCacheObject
{ {
public object Object { get; set; } public object Object { get; set; }
public DateTime CreationTime { get; } = DateTime.UtcNow; public DateTime CreationTime { get; } = DateTime.UtcNow;
public DateTime ExpiryTime public DateTime ExpiryTime
{ {
get get
{ {

View File

@@ -10,23 +10,29 @@ namespace gaseous_server.Classes
public class Roms public class Roms
{ {
public class InvalidRomId : Exception public class InvalidRomId : Exception
{ {
public InvalidRomId(long Id) : base("Unable to find ROM by id " + Id) public InvalidRomId(long Id) : base("Unable to find ROM by id " + Id)
{} { }
} }
public class InvalidRomHash : Exception
{
public InvalidRomHash(String Hash) : base("Unable to find ROM by hash " + Hash)
{ }
}
public static GameRomObject GetRoms(long GameId, long PlatformId = -1, string NameSearch = "", int pageNumber = 0, int pageSize = 0, string userid = "") public static GameRomObject GetRoms(long GameId, long PlatformId = -1, string NameSearch = "", int pageNumber = 0, int pageSize = 0, string userid = "")
{ {
GameRomObject GameRoms = new GameRomObject(); GameRomObject GameRoms = new GameRomObject();
Database db = new Database(Database.databaseType.MySql, Config.DatabaseConfiguration.ConnectionString); Database db = new Database(Database.databaseType.MySql, Config.DatabaseConfiguration.ConnectionString);
string sql = ""; string sql = "";
string sqlCount = ""; string sqlCount = "";
string sqlPlatform = ""; string sqlPlatform = "";
Dictionary<string, object> dbDict = new Dictionary<string, object>(); Dictionary<string, object> dbDict = new Dictionary<string, object>();
dbDict.Add("id", GameId); dbDict.Add("id", GameId);
dbDict.Add("userid", userid); dbDict.Add("userid", userid);
string NameSearchWhere = ""; string NameSearchWhere = "";
if (NameSearch.Length > 0) if (NameSearch.Length > 0)
{ {
@@ -37,13 +43,16 @@ namespace gaseous_server.Classes
// platform query // platform query
sqlPlatform = "SELECT DISTINCT Games_Roms.PlatformId, Platform.`Name` FROM Games_Roms LEFT JOIN Platform ON Games_Roms.PlatformId = Platform.Id WHERE GameId = @id ORDER BY Platform.`Name`;"; sqlPlatform = "SELECT DISTINCT Games_Roms.PlatformId, Platform.`Name` FROM Games_Roms LEFT JOIN Platform ON Games_Roms.PlatformId = Platform.Id WHERE GameId = @id ORDER BY Platform.`Name`;";
if (PlatformId == -1) { if (PlatformId == -1)
{
// data query // data query
sql = "SELECT DISTINCT Games_Roms.*, Platform.`Name` AS platformname, GameState.RomId AS SavedStateRomId FROM Games_Roms LEFT JOIN Platform ON Games_Roms.PlatformId = Platform.Id LEFT JOIN GameState ON (Games_Roms.Id = GameState.RomId AND GameState.UserId = @userid AND GameState.IsMediaGroup = 0) WHERE Games_Roms.GameId = @id" + NameSearchWhere + " ORDER BY Platform.`Name`, Games_Roms.`Name` LIMIT 1000;"; sql = "SELECT DISTINCT Games_Roms.*, Platform.`Name` AS platformname, GameState.RomId AS SavedStateRomId FROM Games_Roms LEFT JOIN Platform ON Games_Roms.PlatformId = Platform.Id LEFT JOIN GameState ON (Games_Roms.Id = GameState.RomId AND GameState.UserId = @userid AND GameState.IsMediaGroup = 0) WHERE Games_Roms.GameId = @id" + NameSearchWhere + " ORDER BY Platform.`Name`, Games_Roms.`Name` LIMIT 1000;";
// count query // count query
sqlCount = "SELECT COUNT(Games_Roms.Id) AS RomCount FROM Games_Roms WHERE Games_Roms.GameId = @id" + NameSearchWhere + ";"; sqlCount = "SELECT COUNT(Games_Roms.Id) AS RomCount FROM Games_Roms WHERE Games_Roms.GameId = @id" + NameSearchWhere + ";";
} else { }
else
{
// data query // data query
sql = "SELECT DISTINCT Games_Roms.*, Platform.`Name` AS platformname, GameState.RomId AS SavedStateRomId FROM Games_Roms LEFT JOIN Platform ON Games_Roms.PlatformId = Platform.Id LEFT JOIN GameState ON (Games_Roms.Id = GameState.RomId AND GameState.UserId = @userid AND GameState.IsMediaGroup = 0) WHERE Games_Roms.GameId = @id AND Games_Roms.PlatformId = @platformid" + NameSearchWhere + " ORDER BY Platform.`Name`, Games_Roms.`Name` LIMIT 1000;"; sql = "SELECT DISTINCT Games_Roms.*, Platform.`Name` AS platformname, GameState.RomId AS SavedStateRomId FROM Games_Roms LEFT JOIN Platform ON Games_Roms.PlatformId = Platform.Id LEFT JOIN GameState ON (Games_Roms.Id = GameState.RomId AND GameState.UserId = @userid AND GameState.IsMediaGroup = 0) WHERE Games_Roms.GameId = @id AND Games_Roms.PlatformId = @platformid" + NameSearchWhere + " ORDER BY Platform.`Name`, Games_Roms.`Name` LIMIT 1000;";
@@ -52,12 +61,12 @@ namespace gaseous_server.Classes
dbDict.Add("platformid", PlatformId); dbDict.Add("platformid", PlatformId);
} }
DataTable romDT = db.ExecuteCMD(sql, dbDict); DataTable romDT = db.ExecuteCMD(sql, dbDict);
Dictionary<string, object> rowCount = db.ExecuteCMDDict(sqlCount, dbDict)[0]; Dictionary<string, object> rowCount = db.ExecuteCMDDict(sqlCount, dbDict)[0];
DataTable platformDT = db.ExecuteCMD(sqlPlatform, dbDict); DataTable platformDT = db.ExecuteCMD(sqlPlatform, dbDict);
if (romDT.Rows.Count > 0) if (romDT.Rows.Count > 0)
{ {
// set count of roms // set count of roms
GameRoms.Count = int.Parse((string)rowCount["RomCount"]); GameRoms.Count = int.Parse((string)rowCount["RomCount"]);
@@ -73,12 +82,12 @@ namespace gaseous_server.Classes
} }
return GameRoms; return GameRoms;
} }
else else
{ {
throw new Games.InvalidGameId(GameId); throw new Games.InvalidGameId(GameId);
} }
} }
public static GameRomItem GetRom(long RomId) public static GameRomItem GetRom(long RomId)
{ {
@@ -100,6 +109,26 @@ namespace gaseous_server.Classes
} }
} }
public static GameRomItem GetRom(string MD5)
{
Database db = new Database(Database.databaseType.MySql, Config.DatabaseConfiguration.ConnectionString);
string sql = "SELECT Games_Roms.*, Platform.`Name` AS platformname FROM Games_Roms LEFT JOIN Platform ON Games_Roms.PlatformId = Platform.Id WHERE Games_Roms.MD5 = @id";
Dictionary<string, object> dbDict = new Dictionary<string, object>();
dbDict.Add("id", MD5);
DataTable romDT = db.ExecuteCMD(sql, dbDict);
if (romDT.Rows.Count > 0)
{
DataRow romDR = romDT.Rows[0];
GameRomItem romItem = BuildRom(romDR);
return romItem;
}
else
{
throw new InvalidRomHash(MD5);
}
}
public static GameRomItem UpdateRom(long RomId, long PlatformId, long GameId) public static GameRomItem UpdateRom(long RomId, long PlatformId, long GameId)
{ {
// ensure metadata for platformid is present // ensure metadata for platformid is present
@@ -108,10 +137,10 @@ namespace gaseous_server.Classes
// ensure metadata for gameid is present // ensure metadata for gameid is present
IGDB.Models.Game game = Classes.Metadata.Games.GetGame(GameId, false, false, false); IGDB.Models.Game game = Classes.Metadata.Games.GetGame(GameId, false, false, false);
Database db = new Database(Database.databaseType.MySql, Config.DatabaseConfiguration.ConnectionString); Database db = new Database(Database.databaseType.MySql, Config.DatabaseConfiguration.ConnectionString);
string sql = "UPDATE Games_Roms SET PlatformId=@platformid, GameId=@gameid WHERE Id = @id"; string sql = "UPDATE Games_Roms SET PlatformId=@platformid, GameId=@gameid WHERE Id = @id";
Dictionary<string, object> dbDict = new Dictionary<string, object>(); Dictionary<string, object> dbDict = new Dictionary<string, object>();
dbDict.Add("id", RomId); dbDict.Add("id", RomId);
dbDict.Add("platformid", PlatformId); dbDict.Add("platformid", PlatformId);
dbDict.Add("gameid", GameId); dbDict.Add("gameid", GameId);
db.ExecuteCMD(sql, dbDict); db.ExecuteCMD(sql, dbDict);
@@ -119,7 +148,7 @@ namespace gaseous_server.Classes
GameRomItem rom = GetRom(RomId); GameRomItem rom = GetRom(RomId);
return rom; return rom;
} }
public static void DeleteRom(long RomId) public static void DeleteRom(long RomId)
{ {
@@ -137,7 +166,7 @@ namespace gaseous_server.Classes
dbDict.Add("id", RomId); dbDict.Add("id", RomId);
db.ExecuteCMD(sql, dbDict); db.ExecuteCMD(sql, dbDict);
} }
} }
private static GameRomItem BuildRom(DataRow romDR) private static GameRomItem BuildRom(DataRow romDR)
{ {
@@ -151,27 +180,27 @@ namespace gaseous_server.Classes
} }
GameRomItem romItem = new GameRomItem GameRomItem romItem = new GameRomItem
{ {
Id = (long)romDR["id"], Id = (long)romDR["id"],
PlatformId = (long)romDR["platformid"], PlatformId = (long)romDR["platformid"],
Platform = (string)romDR["platformname"], Platform = (string)romDR["platformname"],
GameId = (long)romDR["gameid"], GameId = (long)romDR["gameid"],
Name = (string)romDR["name"], Name = (string)romDR["name"],
Size = (long)romDR["size"], Size = (long)romDR["size"],
Crc = ((string)romDR["crc"]).ToLower(), Crc = ((string)romDR["crc"]).ToLower(),
Md5 = ((string)romDR["md5"]).ToLower(), Md5 = ((string)romDR["md5"]).ToLower(),
Sha1 = ((string)romDR["sha1"]).ToLower(), Sha1 = ((string)romDR["sha1"]).ToLower(),
DevelopmentStatus = (string)romDR["developmentstatus"], DevelopmentStatus = (string)romDR["developmentstatus"],
Attributes = Newtonsoft.Json.JsonConvert.DeserializeObject<List<KeyValuePair<string, object>>>((string)Common.ReturnValueIfNull(romDR["attributes"], "[ ]")), Attributes = Newtonsoft.Json.JsonConvert.DeserializeObject<List<KeyValuePair<string, object>>>((string)Common.ReturnValueIfNull(romDR["attributes"], "[ ]")),
RomType = (HasheousClient.Models.LookupResponseModel.RomItem.RomTypes)(int)romDR["romtype"], RomType = (HasheousClient.Models.LookupResponseModel.RomItem.RomTypes)(int)romDR["romtype"],
RomTypeMedia = (string)romDR["romtypemedia"], RomTypeMedia = (string)romDR["romtypemedia"],
MediaLabel = (string)romDR["medialabel"], MediaLabel = (string)romDR["medialabel"],
Path = (string)romDR["path"], Path = (string)romDR["path"],
SignatureSource = (gaseous_server.Models.Signatures_Games.RomItem.SignatureSourceType)(Int32)romDR["metadatasource"], SignatureSource = (gaseous_server.Models.Signatures_Games.RomItem.SignatureSourceType)(Int32)romDR["metadatasource"],
SignatureSourceGameTitle = (string)Common.ReturnValueIfNull(romDR["MetadataGameName"], ""), SignatureSourceGameTitle = (string)Common.ReturnValueIfNull(romDR["MetadataGameName"], ""),
HasSaveStates = hasSaveStates, HasSaveStates = hasSaveStates,
Library = GameLibrary.GetLibrary((int)romDR["LibraryId"]) Library = GameLibrary.GetLibrary((int)romDR["LibraryId"])
}; };
// check for a web emulator and update the romItem // check for a web emulator and update the romItem
foreach (Models.PlatformMapping.PlatformMapItem platformMapping in Models.PlatformMapping.PlatformMap) foreach (Models.PlatformMapping.PlatformMapItem platformMapping in Models.PlatformMapping.PlatformMap)
@@ -185,8 +214,8 @@ namespace gaseous_server.Classes
} }
} }
return romItem; return romItem;
} }
public class GameRomObject public class GameRomObject
{ {
@@ -198,13 +227,13 @@ namespace gaseous_server.Classes
{ {
public long PlatformId { get; set; } public long PlatformId { get; set; }
public string Platform { get; set; } public string Platform { get; set; }
public Models.PlatformMapping.PlatformMapItem.WebEmulatorItem? Emulator { get; set; } public Models.PlatformMapping.PlatformMapItem.WebEmulatorItem? Emulator { get; set; }
public long GameId { get; set; } public long GameId { get; set; }
public string? Path { get; set; } public string? Path { get; set; }
public string? SignatureSourceGameTitle { get; set;} public string? SignatureSourceGameTitle { get; set; }
public bool HasSaveStates { get; set; } = false; public bool HasSaveStates { get; set; } = false;
public GameLibrary.LibraryItem Library { get; set; } public GameLibrary.LibraryItem Library { get; set; }
} }
} }
} }

View File

@@ -73,7 +73,8 @@ namespace gaseous_server.Controllers
private static async Task<List<GaseousGame>> _SearchForGame(long PlatformId, string SearchString) private static async Task<List<GaseousGame>> _SearchForGame(long PlatformId, string SearchString)
{ {
string searchBody = ""; string searchBody = "";
string searchFields = "fields cover,first_release_date,name,platforms,slug; "; // string searchFields = "fields cover,first_release_date,name,platforms,slug; ";
string searchFields = "fields *; ";
searchBody += "search \"" + SearchString + "\";"; searchBody += "search \"" + SearchString + "\";";
searchBody += "where platforms = (" + PlatformId + ");"; searchBody += "where platforms = (" + PlatformId + ");";
searchBody += "limit 100;"; searchBody += "limit 100;";
@@ -86,12 +87,12 @@ namespace gaseous_server.Controllers
// get Game metadata from data source // get Game metadata from data source
Communications comms = new Communications(); Communications comms = new Communications();
var results = await comms.APIComm<Game>(IGDBClient.Endpoints.Games, searchFields, searchBody); var results = await comms.APIComm<Game>(IGDBClient.Endpoints.Games, searchFields, searchBody);
List<GaseousGame> games = new List<GaseousGame>(); List<GaseousGame> games = new List<GaseousGame>();
foreach (Game game in results.ToList()) foreach (Game game in results.ToList())
{ {
Storage.CacheStatus cacheStatus = Storage.GetCacheStatus("Game", (long)game.Id); Storage.CacheStatus cacheStatus = Storage.GetCacheStatus("Game", (long)game.Id);
switch(cacheStatus) switch (cacheStatus)
{ {
case Storage.CacheStatus.NotPresent: case Storage.CacheStatus.NotPresent:
Storage.NewCacheValue(game, false); Storage.NewCacheValue(game, false);

View File

@@ -70,7 +70,8 @@ namespace gaseous_server.Controllers
[HttpGet] [HttpGet]
[Route("Version")] [Route("Version")]
[ProducesResponseType(StatusCodes.Status200OK)] [ProducesResponseType(StatusCodes.Status200OK)]
public Version GetSystemVersion() { public Version GetSystemVersion()
{
return Assembly.GetExecutingAssembly().GetName().Version; return Assembly.GetExecutingAssembly().GetName().Version;
} }
@@ -80,32 +81,36 @@ namespace gaseous_server.Controllers
[Route("VersionFile")] [Route("VersionFile")]
[AllowAnonymous] [AllowAnonymous]
[ProducesResponseType(StatusCodes.Status200OK)] [ProducesResponseType(StatusCodes.Status200OK)]
public FileContentResult GetSystemVersionAsFile() { public FileContentResult GetSystemVersionAsFile()
{
Database db = new Database(Database.databaseType.MySql, Config.DatabaseConfiguration.ConnectionString); Database db = new Database(Database.databaseType.MySql, Config.DatabaseConfiguration.ConnectionString);
// get age ratings dictionary // get age ratings dictionary
Dictionary<int, string> ClassificationBoardsStrings = new Dictionary<int, string>(); Dictionary<int, string> ClassificationBoardsStrings = new Dictionary<int, string>();
foreach(IGDB.Models.AgeRatingCategory ageRatingCategory in Enum.GetValues(typeof(IGDB.Models.AgeRatingCategory)) ) foreach (IGDB.Models.AgeRatingCategory ageRatingCategory in Enum.GetValues(typeof(IGDB.Models.AgeRatingCategory)))
{ {
ClassificationBoardsStrings.Add((int)ageRatingCategory, ageRatingCategory.ToString()); ClassificationBoardsStrings.Add((int)ageRatingCategory, ageRatingCategory.ToString());
} }
Dictionary<int, string> AgeRatingsStrings = new Dictionary<int, string>(); Dictionary<int, string> AgeRatingsStrings = new Dictionary<int, string>();
foreach(IGDB.Models.AgeRatingTitle ageRatingTitle in Enum.GetValues(typeof(IGDB.Models.AgeRatingTitle)) ) foreach (IGDB.Models.AgeRatingTitle ageRatingTitle in Enum.GetValues(typeof(IGDB.Models.AgeRatingTitle)))
{ {
AgeRatingsStrings.Add((int)ageRatingTitle, ageRatingTitle.ToString()); AgeRatingsStrings.Add((int)ageRatingTitle, ageRatingTitle.ToString());
} }
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<string>("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 +
"var AgeRatingStrings = " + JsonSerializer.Serialize(AgeRatingsStrings, new JsonSerializerOptions{ "var AgeRatingStrings = " + JsonSerializer.Serialize(AgeRatingsStrings, new JsonSerializerOptions
{
WriteIndented = true WriteIndented = true
}) + ";" + 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 + }) + ";" + Environment.NewLine +
"var emulatorDebugMode = " + Config.ReadSetting<string>("emulatorDebugMode", false.ToString()).ToLower() + ";"; "var emulatorDebugMode = " + Config.ReadSetting<string>("emulatorDebugMode", false.ToString()).ToLower() + ";";
@@ -159,7 +164,7 @@ namespace gaseous_server.Controllers
{ {
// update task enabled // update task enabled
Logging.Log(Logging.LogType.Information, "Update Background Task", "Updating task " + TaskConfiguration.Task + " with enabled value " + TaskConfiguration.Enabled.ToString()); Logging.Log(Logging.LogType.Information, "Update Background Task", "Updating task " + TaskConfiguration.Task + " with enabled value " + TaskConfiguration.Enabled.ToString());
Config.SetSetting<string>("Enabled_" + TaskConfiguration.Task, TaskConfiguration.Enabled.ToString()); Config.SetSetting<string>("Enabled_" + TaskConfiguration.Task, TaskConfiguration.Enabled.ToString());
// update existing process // update existing process
@@ -170,12 +175,12 @@ namespace gaseous_server.Controllers
item.Enabled(Boolean.Parse(TaskConfiguration.Enabled.ToString())); item.Enabled(Boolean.Parse(TaskConfiguration.Enabled.ToString()));
} }
} }
// update task interval // update task interval
if (TaskConfiguration.Interval >= taskItem.MinimumAllowedInterval) if (TaskConfiguration.Interval >= taskItem.MinimumAllowedInterval)
{ {
Logging.Log(Logging.LogType.Information, "Update Background Task", "Updating task " + TaskConfiguration.Task + " with new interval " + TaskConfiguration.Interval); 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()); Config.SetSetting<string>("Interval_" + TaskConfiguration.Task, TaskConfiguration.Interval.ToString());
// update existing process // update existing process
@@ -194,7 +199,7 @@ namespace gaseous_server.Controllers
// update task weekdays // update task weekdays
Logging.Log(Logging.LogType.Information, "Update Background Task", "Updating task " + TaskConfiguration.Task + " with new weekdays " + String.Join(", ", TaskConfiguration.AllowedDays)); 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)); Config.SetSetting<string>("AllowedDays_" + TaskConfiguration.Task, Newtonsoft.Json.JsonConvert.SerializeObject(TaskConfiguration.AllowedDays));
// update existing process // update existing process
@@ -208,7 +213,7 @@ namespace gaseous_server.Controllers
// update task hours // 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")); 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>("AllowedStartHours_" + TaskConfiguration.Task, TaskConfiguration.AllowedStartHours.ToString());
Config.SetSetting<string>("AllowedStartMinutes_" + TaskConfiguration.Task, TaskConfiguration.AllowedStartMinutes.ToString()); Config.SetSetting<string>("AllowedStartMinutes_" + TaskConfiguration.Task, TaskConfiguration.AllowedStartMinutes.ToString());
Config.SetSetting<string>("AllowedEndHours_" + TaskConfiguration.Task, TaskConfiguration.AllowedEndHours.ToString()); Config.SetSetting<string>("AllowedEndHours_" + TaskConfiguration.Task, TaskConfiguration.AllowedEndHours.ToString());
@@ -225,7 +230,7 @@ namespace gaseous_server.Controllers
item.AllowedEndMinutes = TaskConfiguration.AllowedEndMinutes; item.AllowedEndMinutes = TaskConfiguration.AllowedEndMinutes;
} }
} }
} }
else else
{ {
@@ -251,7 +256,8 @@ namespace gaseous_server.Controllers
[ProducesResponseType(StatusCodes.Status200OK)] [ProducesResponseType(StatusCodes.Status200OK)]
public ActionResult GetSystemSettings() public ActionResult GetSystemSettings()
{ {
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())) EmulatorDebugMode = Boolean.Parse(Config.ReadSetting<string>("emulatorDebugMode", false.ToString()))
@@ -281,7 +287,8 @@ namespace gaseous_server.Controllers
private SystemInfo.PathItem GetDisk(string Path) private SystemInfo.PathItem GetDisk(string Path)
{ {
SystemInfo.PathItem pathItem = new SystemInfo.PathItem { SystemInfo.PathItem pathItem = new SystemInfo.PathItem
{
LibraryPath = Path, LibraryPath = Path,
SpaceUsed = Common.DirSize(new DirectoryInfo(Path)), SpaceUsed = Common.DirSize(new DirectoryInfo(Path)),
SpaceAvailable = new DriveInfo(Path).AvailableFreeSpace, SpaceAvailable = new DriveInfo(Path).AvailableFreeSpace,
@@ -293,11 +300,12 @@ namespace gaseous_server.Controllers
public class SystemInfo public class SystemInfo
{ {
public Version ApplicationVersion { public Version ApplicationVersion
{
get get
{ {
return Assembly.GetExecutingAssembly().GetName().Version; return Assembly.GetExecutingAssembly().GetName().Version;
} }
} }
public List<PathItem>? Paths { get; set; } public List<PathItem>? Paths { get; set; }
public long DatabaseSize { get; set; } public long DatabaseSize { get; set; }
@@ -352,7 +360,7 @@ namespace gaseous_server.Controllers
this.DefaultAllowedEndHours = 23; this.DefaultAllowedEndHours = 23;
this.DefaultAllowedEndMinutes = 59; this.DefaultAllowedEndMinutes = 59;
break; break;
case ProcessQueue.QueueItemType.TitleIngestor: case ProcessQueue.QueueItemType.TitleIngestor:
this._UserManageable = true; this._UserManageable = true;
this.DefaultInterval = 1; this.DefaultInterval = 1;
@@ -589,7 +597,8 @@ namespace gaseous_server.Controllers
} }
private bool _UserManageable; private bool _UserManageable;
public bool UserManageable => _UserManageable; public bool UserManageable => _UserManageable;
public int Interval { public int Interval
{
get get
{ {
return int.Parse(Config.ReadSetting<string>("Interval_" + Task, DefaultInterval.ToString())); return int.Parse(Config.ReadSetting<string>("Interval_" + Task, DefaultInterval.ToString()));
@@ -642,7 +651,7 @@ namespace gaseous_server.Controllers
public List<ProcessQueue.QueueItemType> Blocks public List<ProcessQueue.QueueItemType> Blocks
{ {
get get
{ {
if (_Blocks.Contains(ProcessQueue.QueueItemType.All)) if (_Blocks.Contains(ProcessQueue.QueueItemType.All))
{ {
List<ProcessQueue.QueueItemType> blockList = new List<ProcessQueue.QueueItemType>(); List<ProcessQueue.QueueItemType> blockList = new List<ProcessQueue.QueueItemType>();

View File

@@ -6,6 +6,7 @@ using Authentication;
using Microsoft.AspNetCore.Identity; using Microsoft.AspNetCore.Identity;
using System.Data; using System.Data;
using Asp.Versioning; using Asp.Versioning;
using System.IO.Compression;
namespace gaseous_server.Controllers.v1_1 namespace gaseous_server.Controllers.v1_1
{ {
@@ -13,7 +14,7 @@ namespace gaseous_server.Controllers.v1_1
[ApiVersion("1.0")] [ApiVersion("1.0")]
[ApiVersion("1.1")] [ApiVersion("1.1")]
[ApiController] [ApiController]
public class StateManagerController: ControllerBase public class StateManagerController : ControllerBase
{ {
private readonly UserManager<ApplicationUser> _userManager; private readonly UserManager<ApplicationUser> _userManager;
private readonly SignInManager<ApplicationUser> _signInManager; private readonly SignInManager<ApplicationUser> _signInManager;
@@ -39,7 +40,7 @@ namespace gaseous_server.Controllers.v1_1
var user = await _userManager.GetUserAsync(User); var user = await _userManager.GetUserAsync(User);
byte[] CompressedState = Common.Compress(uploadState.StateByteArray); 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, Zipped) VALUES (@userid, @romid, @ismediagroup, @statedatetime, @name, @screenshot, @state, @zipped); 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>
@@ -86,7 +87,7 @@ namespace gaseous_server.Controllers.v1_1
{ "ismediagroup", IsMediaGroup } { "ismediagroup", IsMediaGroup }
}; };
DataTable data = db.ExecuteCMD(sql, dbDict); DataTable data = db.ExecuteCMD(sql, dbDict);
List<Models.GameStateItem> gameStates = new List<GameStateItem>(); List<Models.GameStateItem> gameStates = new List<GameStateItem>();
foreach (DataRow row in data.Rows) foreach (DataRow row in data.Rows)
{ {
@@ -116,7 +117,7 @@ namespace gaseous_server.Controllers.v1_1
{ "ismediagroup", IsMediaGroup } { "ismediagroup", IsMediaGroup }
}; };
DataTable data = db.ExecuteCMD(sql, dbDict); DataTable data = db.ExecuteCMD(sql, dbDict);
if (data.Rows.Count == 0) if (data.Rows.Count == 0)
{ {
// invalid match - return not found // invalid match - return not found
@@ -150,7 +151,7 @@ namespace gaseous_server.Controllers.v1_1
{ "ismediagroup", IsMediaGroup } { "ismediagroup", IsMediaGroup }
}; };
db.ExecuteNonQuery(sql, dbDict); db.ExecuteNonQuery(sql, dbDict);
return Ok(); return Ok();
} }
@@ -175,7 +176,7 @@ namespace gaseous_server.Controllers.v1_1
{ "name", model.Name } { "name", model.Name }
}; };
db.ExecuteNonQuery(sql, dbDict); db.ExecuteNonQuery(sql, dbDict);
return Ok(); return Ok();
} }
@@ -200,7 +201,7 @@ namespace gaseous_server.Controllers.v1_1
{ "ismediagroup", IsMediaGroup } { "ismediagroup", IsMediaGroup }
}; };
DataTable data = db.ExecuteCMD(sql, dbDict); DataTable data = db.ExecuteCMD(sql, dbDict);
if (data.Rows.Count == 0) if (data.Rows.Count == 0)
{ {
// invalid match - return not found // invalid match - return not found
@@ -233,11 +234,11 @@ namespace gaseous_server.Controllers.v1_1
[ProducesResponseType(StatusCodes.Status404NotFound)] [ProducesResponseType(StatusCodes.Status404NotFound)]
[Route("{RomId}/{StateId}/State/")] [Route("{RomId}/{StateId}/State/")]
[Route("{RomId}/{StateId}/State/savestate.state")] [Route("{RomId}/{StateId}/State/savestate.state")]
public async Task<ActionResult> GetStateDataAsync(long RomId, long StateId, bool IsMediaGroup = false) public async Task<ActionResult> GetStateDataAsync(long RomId, long StateId, bool IsMediaGroup = false, bool StateOnly = false)
{ {
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 Zipped, State FROM GameState WHERE Id = @id AND RomId = @romid AND IsMediaGroup = @ismediagroup AND UserId = @userid;"; string sql = "SELECT * 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 },
@@ -246,7 +247,7 @@ namespace gaseous_server.Controllers.v1_1
{ "ismediagroup", IsMediaGroup } { "ismediagroup", IsMediaGroup }
}; };
DataTable data = db.ExecuteCMD(sql, dbDict); DataTable data = db.ExecuteCMD(sql, dbDict);
if (data.Rows.Count == 0) if (data.Rows.Count == 0)
{ {
// invalid match - return not found // invalid match - return not found
@@ -254,7 +255,9 @@ namespace gaseous_server.Controllers.v1_1
} }
else else
{ {
string filename = "savestate.state"; // get rom data
Roms.GameRomItem romItem = Roms.GetRom(RomId);
byte[] bytes; byte[] bytes;
if ((bool)data.Rows[0]["Zipped"] == false) if ((bool)data.Rows[0]["Zipped"] == false)
{ {
@@ -264,7 +267,86 @@ namespace gaseous_server.Controllers.v1_1
{ {
bytes = Common.Decompress((byte[])data.Rows[0]["State"]); bytes = Common.Decompress((byte[])data.Rows[0]["State"]);
} }
string contentType = "application/octet-stream";
string contentType = "";
string filename = ((DateTime)data.Rows[0]["StateDateTime"]).ToString("yyyy-MM-ddTHH-mm-ss") + "-" + Path.GetFileNameWithoutExtension(romItem.Name);
if (StateOnly == true)
{
contentType = "application/octet-stream";
filename = filename + ".state";
}
else
{
contentType = "application/zip";
filename = filename + ".zip";
Dictionary<string, object> RomInfo = new Dictionary<string, object>
{
{ "Name", romItem.Name },
{ "StateDateTime", data.Rows[0]["StateDateTime"] },
{ "StateName", data.Rows[0]["Name"] }
};
if ((int)data.Rows[0]["IsMediaGroup"] == 0)
{
RomInfo.Add("MD5", romItem.Md5);
RomInfo.Add("SHA1", romItem.Sha1);
RomInfo.Add("Type", "ROM");
}
else
{
RomInfo.Add("Type", "Media Group");
RomInfo.Add("MediaGroupId", (long)data.Rows[0]["RomId"]);
}
string RomInfoString = Newtonsoft.Json.JsonConvert.SerializeObject(RomInfo, Newtonsoft.Json.Formatting.Indented, new Newtonsoft.Json.JsonSerializerSettings { NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore });
// compile zip file
using (var compressedFileStream = new MemoryStream())
{
List<Dictionary<string, object>> Attachments = new List<Dictionary<string, object>>();
Attachments.Add(new Dictionary<string, object>
{
{ "Name", "savestate.state" },
{ "Body", bytes }
});
// check if value is dbnull
if (data.Rows[0]["Screenshot"] != DBNull.Value)
{
Attachments.Add(new Dictionary<string, object>
{
{ "Name", "screenshot.jpg" },
{ "Body", (byte[])data.Rows[0]["Screenshot"] }
});
}
Attachments.Add(new Dictionary<string, object>
{
{ "Name", "rominfo.json" },
{ "Body", System.Text.Encoding.UTF8.GetBytes(RomInfoString) }
});
//Create an archive and store the stream in memory.
using (var zipArchive = new ZipArchive(compressedFileStream, ZipArchiveMode.Create, false))
{
foreach (var Attachment in Attachments)
{
//Create a zip entry for each attachment
var zipEntry = zipArchive.CreateEntry(Attachment["Name"].ToString());
//Get the stream of the attachment
using (var originalFileStream = new MemoryStream((byte[])Attachment["Body"]))
using (var zipEntryStream = zipEntry.Open())
{
//Copy the attachment stream to the zip entry stream
originalFileStream.CopyTo(zipEntryStream);
}
}
}
//return new FileContentResult(compressedFileStream.ToArray(), "application/zip") { FileDownloadName = filename };
bytes = compressedFileStream.ToArray();
}
}
var cd = new System.Net.Mime.ContentDisposition var cd = new System.Net.Mime.ContentDisposition
{ {
@@ -279,6 +361,156 @@ namespace gaseous_server.Controllers.v1_1
} }
} }
[MapToApiVersion("1.0")]
[MapToApiVersion("1.1")]
[HttpPost]
[Authorize]
[ProducesResponseType(StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status404NotFound)]
[RequestSizeLimit(long.MaxValue)]
[Consumes("multipart/form-data")]
[DisableRequestSizeLimit, RequestFormLimits(MultipartBodyLengthLimit = long.MaxValue, ValueLengthLimit = int.MaxValue)]
[Route("Upload")]
public async Task<ActionResult> UploadStateDataAsync(IFormFile file, long RomId = 0, bool IsMediaGroup = false)
{
// get user
var user = await _userManager.GetUserAsync(User);
if (file.Length > 0)
{
MemoryStream fileContent = new MemoryStream();
file.CopyTo(fileContent);
// test if file is a zip file
try
{
using (var zipArchive = new ZipArchive(fileContent, ZipArchiveMode.Read, false))
{
foreach (var entry in zipArchive.Entries)
{
if (entry.FullName == "rominfo.json")
{
using (var stream = entry.Open())
using (var reader = new StreamReader(stream))
{
string RomInfoString = reader.ReadToEnd();
Dictionary<string, object> RomInfo = Newtonsoft.Json.JsonConvert.DeserializeObject<Dictionary<string, object>>(RomInfoString);
// get rom data
Roms.GameRomItem romItem;
try
{
romItem = Roms.GetRom((string)RomInfo["MD5"]);
}
catch (Roms.InvalidRomHash)
{
return NotFound();
}
// get state data
byte[] StateData = null;
byte[] ScreenshotData = null;
string StateName = RomInfo["StateName"].ToString();
DateTime StateDateTime = DateTime.Parse(RomInfo["StateDateTime"].ToString());
IsMediaGroup = RomInfo["Type"].ToString() == "Media Group" ? true : false;
if (zipArchive.GetEntry("savestate.state") != null)
{
using (var stateStream = zipArchive.GetEntry("savestate.state").Open())
using (var stateReader = new MemoryStream())
{
stateStream.CopyTo(stateReader);
StateData = stateReader.ToArray();
}
}
if (zipArchive.GetEntry("screenshot.jpg") != null)
{
using (var screenshotStream = zipArchive.GetEntry("screenshot.jpg").Open())
using (var screenshotReader = new MemoryStream())
{
screenshotStream.CopyTo(screenshotReader);
ScreenshotData = screenshotReader.ToArray();
}
}
// save state
Database db = new Database(Database.databaseType.MySql, Config.DatabaseConfiguration.ConnectionString);
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>
{
{ "userid", user.Id },
{ "romid", romItem.Id },
{ "ismediagroup", IsMediaGroup },
{ "statedatetime", StateDateTime },
{ "name", StateName },
{ "screenshot", ScreenshotData },
{ "state", Common.Compress(StateData) },
{ "zipped", true }
};
DataTable data = db.ExecuteCMD(sql, dbDict);
RomInfo.Add("RomId", romItem.Id);
RomInfo.Add("Management", "Managed");
return Ok(RomInfo);
}
}
}
}
return BadRequest("File is not a valid Gaseous state file.");
}
catch
{
// not a zip file
if (RomId != 0)
{
// get rom data
Roms.GameRomItem romItem;
try
{
romItem = Roms.GetRom(RomId);
}
catch (Roms.InvalidRomHash)
{
return NotFound();
}
// save state
Database db = new Database(Database.databaseType.MySql, Config.DatabaseConfiguration.ConnectionString);
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>
{
{ "userid", user.Id },
{ "romid", RomId },
{ "ismediagroup", IsMediaGroup },
{ "statedatetime", DateTime.UtcNow },
{ "name", "" },
{ "screenshot", null },
{ "state", Common.Compress(fileContent.ToArray()) },
{ "zipped", true }
};
DataTable data = db.ExecuteCMD(sql, dbDict);
return Ok(new Dictionary<string, object>
{
{ "RomId", RomId },
{ "Management", "Unmanaged" }
});
}
else
{
return BadRequest("No rom id provided.");
}
}
}
else
{
return BadRequest("File is empty.");
}
}
private Models.GameStateItem BuildGameStateItem(DataRow dr) private Models.GameStateItem BuildGameStateItem(DataRow dr)
{ {
bool HasScreenshot = true; bool HasScreenshot = true;

View File

@@ -16,7 +16,7 @@ namespace gaseous_server.Models
{ {
var targetType = this.GetType(); var targetType = this.GetType();
var sourceType = game.GetType(); var sourceType = game.GetType();
foreach(var prop in targetType.GetProperties(BindingFlags.Instance | BindingFlags.Public| BindingFlags.SetProperty)) foreach (var prop in targetType.GetProperties(BindingFlags.Instance | BindingFlags.Public | BindingFlags.SetProperty))
{ {
// check whether source object has the the property // check whether source object has the the property
var sp = sourceType.GetProperty(prop.Name); var sp = sourceType.GetProperty(prop.Name);
@@ -39,7 +39,11 @@ namespace gaseous_server.Models
{ {
if (this.Cover.Id != null) if (this.Cover.Id != null)
{ {
IGDB.Models.Cover cover = Covers.GetCover(Cover.Id, Config.LibraryConfiguration.LibraryMetadataDirectory_Game(this), false); // IGDB.Models.Cover cover = Covers.GetCover(Cover.Id, Config.LibraryConfiguration.LibraryMetadataDirectory_Game(this), false);
IGDB.Models.Cover cover = new IGDB.Models.Cover()
{
Id = this.Cover.Id
};
return cover; return cover;
} }

View File

@@ -13,8 +13,8 @@ using Newtonsoft.Json;
namespace gaseous_server.Models namespace gaseous_server.Models
{ {
public class PlatformMapping public class PlatformMapping
{ {
private static Dictionary<string, PlatformMapItem> PlatformMapCache = new Dictionary<string, PlatformMapItem>(); private static Dictionary<string, PlatformMapItem> PlatformMapCache = new Dictionary<string, PlatformMapItem>();
/// <summary> /// <summary>
@@ -27,7 +27,8 @@ namespace gaseous_server.Models
{ {
string rawJson = reader.ReadToEnd(); string rawJson = reader.ReadToEnd();
List<PlatformMapItem> platforms = new List<PlatformMapItem>(); List<PlatformMapItem> platforms = new List<PlatformMapItem>();
Newtonsoft.Json.JsonSerializerSettings jsonSerializerSettings = new JsonSerializerSettings{ Newtonsoft.Json.JsonSerializerSettings jsonSerializerSettings = new JsonSerializerSettings
{
MaxDepth = 64 MaxDepth = 64
}; };
platforms = Newtonsoft.Json.JsonConvert.DeserializeObject<List<PlatformMapItem>>(rawJson, jsonSerializerSettings); platforms = Newtonsoft.Json.JsonConvert.DeserializeObject<List<PlatformMapItem>>(rawJson, jsonSerializerSettings);
@@ -74,7 +75,7 @@ namespace gaseous_server.Models
foreach (PlatformMapItem mapItem in platforms) foreach (PlatformMapItem mapItem in platforms)
{ {
// get the IGDB platform data // get the IGDB platform data
Platform platform = Platforms.GetPlatform(mapItem.IGDBId); Platform platform = Platforms.GetPlatform(mapItem.IGDBId, false);
try try
{ {
@@ -92,7 +93,7 @@ namespace gaseous_server.Models
} }
} }
} }
public static List<PlatformMapItem> PlatformMap public static List<PlatformMapItem> PlatformMap
{ {
get get
@@ -254,7 +255,7 @@ namespace gaseous_server.Models
} }
} }
public static void WriteAvailableEmulators (PlatformMapItem item) public static void WriteAvailableEmulators(PlatformMapItem item)
{ {
Database db = new Database(Database.databaseType.MySql, Config.DatabaseConfiguration.ConnectionString); Database db = new Database(Database.databaseType.MySql, Config.DatabaseConfiguration.ConnectionString);
string sql = ""; string sql = "";
@@ -286,7 +287,7 @@ namespace gaseous_server.Models
string sql = ""; string sql = "";
// get platform data // get platform data
IGDB.Models.Platform? platform = Platforms.GetPlatform(IGDBId); IGDB.Models.Platform? platform = Platforms.GetPlatform(IGDBId, false);
if (platform != null) if (platform != null)
{ {
@@ -369,18 +370,20 @@ namespace gaseous_server.Models
mapItem.IGDBName = platform.Name; mapItem.IGDBName = platform.Name;
mapItem.IGDBSlug = platform.Slug; mapItem.IGDBSlug = platform.Slug;
mapItem.AlternateNames = alternateNames; mapItem.AlternateNames = alternateNames;
mapItem.Extensions = new PlatformMapItem.FileExtensions{ mapItem.Extensions = new PlatformMapItem.FileExtensions
{
SupportedFileExtensions = knownExtensions, SupportedFileExtensions = knownExtensions,
UniqueFileExtensions = uniqueExtensions UniqueFileExtensions = uniqueExtensions
}; };
mapItem.RetroPieDirectoryName = (string)Common.ReturnValueIfNull(row["RetroPieDirectoryName"], ""); mapItem.RetroPieDirectoryName = (string)Common.ReturnValueIfNull(row["RetroPieDirectoryName"], "");
mapItem.WebEmulator = new PlatformMapItem.WebEmulatorItem{ mapItem.WebEmulator = new PlatformMapItem.WebEmulatorItem
{
Type = (string)Common.ReturnValueIfNull(row["WebEmulator_Type"], ""), Type = (string)Common.ReturnValueIfNull(row["WebEmulator_Type"], ""),
Core = (string)Common.ReturnValueIfNull(row["WebEmulator_Core"], ""), Core = (string)Common.ReturnValueIfNull(row["WebEmulator_Core"], ""),
AvailableWebEmulators = Newtonsoft.Json.JsonConvert.DeserializeObject<List<PlatformMapItem.WebEmulatorItem.AvailableWebEmulatorItem>>((string)Common.ReturnValueIfNull(row["AvailableWebEmulators"], "[]")) AvailableWebEmulators = Newtonsoft.Json.JsonConvert.DeserializeObject<List<PlatformMapItem.WebEmulatorItem.AvailableWebEmulatorItem>>((string)Common.ReturnValueIfNull(row["AvailableWebEmulators"], "[]"))
}; };
mapItem.Bios = bioss; mapItem.Bios = bioss;
if (PlatformMapCache.ContainsKey(IGDBId.ToString())) if (PlatformMapCache.ContainsKey(IGDBId.ToString()))
{ {
PlatformMapCache[IGDBId.ToString()] = mapItem; PlatformMapCache[IGDBId.ToString()] = mapItem;
@@ -461,7 +464,7 @@ namespace gaseous_server.Models
public string IGDBName { get; set; } public string IGDBName { get; set; }
public string IGDBSlug { get; set; } public string IGDBSlug { get; set; }
public List<string> AlternateNames { get; set; } = new List<string>(); public List<string> AlternateNames { get; set; } = new List<string>();
public FileExtensions Extensions { get; set; } public FileExtensions Extensions { get; set; }
public class FileExtensions public class FileExtensions
{ {
@@ -503,6 +506,6 @@ namespace gaseous_server.Models
public string filename { get; set; } public string filename { get; set; }
} }
} }
} }
} }

View File

@@ -36,12 +36,19 @@ db = new Database(Database.databaseType.MySql, Config.DatabaseConfiguration.Conn
// set up db // set up db
db.InitDB(); db.InitDB();
// create relation tables if they don't exist
Storage.CreateRelationsTables<IGDB.Models.Game>();
Storage.CreateRelationsTables<IGDB.Models.Platform>();
// populate db with static data for lookups // populate db with static data for lookups
AgeRatings.PopulateAgeMap(); AgeRatings.PopulateAgeMap();
// load app settings // load app settings
Config.InitSettings(); Config.InitSettings();
// disable hasheous
Config.MetadataConfiguration.SignatureSource = HasheousClient.Models.MetadataModel.SignatureSources.LocalOnly;
// write updated settings back to the config file // write updated settings back to the config file
Config.UpdateConfig(); Config.UpdateConfig();
@@ -273,7 +280,7 @@ using (var scope = app.Services.CreateScope())
{ {
var roleManager = scope.ServiceProvider.GetRequiredService<RoleStore>(); var roleManager = scope.ServiceProvider.GetRequiredService<RoleStore>();
var roles = new[] { "Admin", "Gamer", "Player" }; var roles = new[] { "Admin", "Gamer", "Player" };
foreach (var role in roles) foreach (var role in roles)
{ {
if (await roleManager.FindByNameAsync(role, CancellationToken.None) == null) if (await roleManager.FindByNameAsync(role, CancellationToken.None) == null)
@@ -303,11 +310,11 @@ app.Use(async (context, next) =>
string correlationId = Guid.NewGuid().ToString(); string correlationId = Guid.NewGuid().ToString();
CallContext.SetData("CorrelationId", correlationId); CallContext.SetData("CorrelationId", correlationId);
CallContext.SetData("CallingProcess", context.Request.Method + ": " + context.Request.Path); CallContext.SetData("CallingProcess", context.Request.Method + ": " + context.Request.Path);
string userIdentity; string userIdentity;
try try
{ {
userIdentity = context.User.Claims.Where(x=>x.Type==System.Security.Claims.ClaimTypes.NameIdentifier).FirstOrDefault().Value; userIdentity = context.User.Claims.Where(x => x.Type == System.Security.Claims.ClaimTypes.NameIdentifier).FirstOrDefault().Value;
} }
catch catch
{ {
@@ -329,7 +336,7 @@ app.Use(async (context, next) =>
// - the server will not start while the RecoverAccount.txt file exists // - the server will not start while the RecoverAccount.txt file exists
string PasswordRecoveryFile = Path.Combine(Config.LibraryConfiguration.LibraryRootDirectory, "RecoverAccount.txt"); string PasswordRecoveryFile = Path.Combine(Config.LibraryConfiguration.LibraryRootDirectory, "RecoverAccount.txt");
if (!String.IsNullOrEmpty(Environment.GetEnvironmentVariable("recoveraccount"))) if (!String.IsNullOrEmpty(Environment.GetEnvironmentVariable("recoveraccount")))
{ {
if (File.Exists(PasswordRecoveryFile)) if (File.Exists(PasswordRecoveryFile))
{ {
// password has already been set - do nothing and just exit // password has already been set - do nothing and just exit
@@ -345,7 +352,7 @@ if (!String.IsNullOrEmpty(Environment.GetEnvironmentVariable("recoveraccount")))
string password = new string(Enumerable.Repeat(chars, length).Select(s => s[random.Next(s.Length)]).ToArray()); string password = new string(Enumerable.Repeat(chars, length).Select(s => s[random.Next(s.Length)]).ToArray());
File.WriteAllText(PasswordRecoveryFile, password); File.WriteAllText(PasswordRecoveryFile, password);
// reset the password // reset the password
using (var scope = app.Services.CreateScope()) using (var scope = app.Services.CreateScope())
{ {

View File

@@ -20,7 +20,7 @@
<PackageReference Include="Asp.Versioning.Mvc.ApiExplorer" Version="8.0.0" /> <PackageReference Include="Asp.Versioning.Mvc.ApiExplorer" Version="8.0.0" />
<PackageReference Include="gaseous-signature-parser" Version="2.1.0" /> <PackageReference Include="gaseous-signature-parser" Version="2.1.0" />
<PackageReference Include="gaseous.IGDB" Version="1.0.2" /> <PackageReference Include="gaseous.IGDB" Version="1.0.2" />
<PackageReference Include="hasheous-client" Version="0.2.0" /> <PackageReference Include="hasheous-client" Version="0.1.0" />
<PackageReference Include="Magick.NET-Q8-AnyCPU" Version="13.5.0" /> <PackageReference Include="Magick.NET-Q8-AnyCPU" Version="13.5.0" />
<PackageReference Include="sharpcompress" Version="0.36.0" /> <PackageReference Include="sharpcompress" Version="0.36.0" />
<PackageReference Include="Squid-Box.SevenZipSharp" Version="1.6.1.23" /> <PackageReference Include="Squid-Box.SevenZipSharp" Version="1.6.1.23" />

View File

@@ -1,5 +1,6 @@
<!DOCTYPE html> <!DOCTYPE html>
<html> <html>
<head> <head>
<meta charset="utf-8" /> <meta charset="utf-8" />
<script src="/api/v1.1/System/VersionFile"></script> <script src="/api/v1.1/System/VersionFile"></script>
@@ -44,48 +45,56 @@
var userProfile; var userProfile;
</script> </script>
</head> </head>
<body> <body>
<!-- Notifications --> <!-- Notifications -->
<div id="notifications_target"></div> <div id="notifications_target"></div>
<div id="banner_icon" onclick="window.location.href = '/index.html';"> <div id="banner_icon" onclick="window.location.href = '/index.html';">
<img src="/images/logo.png" alt="Gaseous" id="banner_icon_image" /> <img src="/images/logo.png" alt="Gaseous" id="banner_icon_image" />
</div> </div>
<div id="banner_header"> <div id="banner_header">
<div id="bannerButtons"> <div id="bannerButtons">
<div id="banner_user" onclick="showMenu();" class="banner_button dropdown dropbtn"> <div id="banner_user" onclick="showMenu();" class="banner_button dropdown dropbtn">
<img src="/images/user.svg" alt="Account" title="Account" id="banner_user_image" class="banner_button_image" style="position: relative; top: 10px; right: 0px; pointer-events: none;" onclick="showMenu();" /> <img src="/images/user.svg" alt="Account" title="Account" id="banner_user_image"
class="banner_button_image" style="position: relative; top: 10px; right: 0px; pointer-events: none;"
onclick="showMenu();" />
<div id="myDropdown" class="dropdown-content"> <div id="myDropdown" class="dropdown-content">
<div id="banner_user_roles"></div> <div id="banner_user_roles"></div>
<a href="#" onclick="showDialog('userprofile');">Profile</a> <a href="#" onclick="showDialog('userprofile');">Profile</a>
<a href="#" onclick="userLogoff();">Sign Out</a> <a href="#" onclick="userLogoff();">Sign Out</a>
</div> </div>
</div> </div>
<div id="banner_cog" onclick="window.location.href = '/index.html?page=settings';" class="banner_button"> <div id="banner_cog" onclick="window.location.href = '/index.html?page=settings';" class="banner_button">
<img src="/images/settings.svg" alt="Settings" title="Settings" id="banner_system_image" class="banner_button_image" /> <img src="/images/settings.svg" alt="Settings" title="Settings" id="banner_system_image"
class="banner_button_image" />
<span id="banner_system_label">Settings</span> <span id="banner_system_label">Settings</span>
</div> </div>
<div id="banner_upload" onclick="showDialog('upload');" class="banner_button"> <div id="banner_upload" onclick="showDialog('upload');" class="banner_button">
<img src="/images/upload.svg" alt="Upload" title="Upload" id="banner_upload_image" class="banner_button_image" /> <img src="/images/upload.svg" alt="Upload" title="Upload" id="banner_upload_image"
class="banner_button_image" />
<span id="banner_upload_label">Upload</span> <span id="banner_upload_label">Upload</span>
</div> </div>
<div id="banner_collections" onclick="window.location.href = '/index.html?page=collections';" class="banner_button"> <div id="banner_collections" onclick="window.location.href = '/index.html?page=collections';"
<img src="/images/collections.svg" alt="Collections" title="Collections" id="banner_collections_image" class="banner_button_image" /> class="banner_button">
<img src="/images/collections.svg" alt="Collections" title="Collections" id="banner_collections_image"
class="banner_button_image" />
<span id="banner_collections_label">Collections</span> <span id="banner_collections_label">Collections</span>
</div> </div>
<div id="banner_library" onclick="window.location.href = '/index.html';" class="banner_button"> <div id="banner_library" onclick="window.location.href = '/index.html';" class="banner_button">
<img src="/images/library.svg" alt="Library" title="Library" id="banner_library_image" class="banner_button_image" /> <img src="/images/library.svg" alt="Library" title="Library" id="banner_library_image"
class="banner_button_image" />
<span id="banner_library_label">Library</span> <span id="banner_library_label">Library</span>
</div> </div>
</div> </div>
<div id="banner_header_label" onclick="window.location.href = '/index.html';">Gaseous Games</div> <div id="banner_header_label" onclick="window.location.href = '/index.html';">Gaseous Games</div>
</div> </div>
<div id="content"> <div id="content">
@@ -97,7 +106,9 @@
<!-- Modal content --> <!-- Modal content -->
<div class="modal-content"> <div class="modal-content">
<span class="close">&times;</span> <span class="close">&times;</span>
<div><h1 id="modal-heading">Modal heading</h1></div> <div>
<h1 id="modal-heading">Modal heading</h1>
</div>
<div id="modal-content">Some text in the Modal..</div> <div id="modal-content">Some text in the Modal..</div>
</div> </div>
@@ -118,7 +129,7 @@
var modalVariables = null; var modalVariables = null;
// redirect if first run status = 0 // redirect if first run status = 0
if (FirstRunStatus == 0) { if (FirstRunStatus == 0 || FirstRunStatus == "0") {
window.location.replace("/pages/first.html"); window.location.replace("/pages/first.html");
} }
@@ -126,7 +137,7 @@
ajaxCall( ajaxCall(
'/api/v1.1/Account/Profile/Basic', '/api/v1.1/Account/Profile/Basic',
'GET', 'GET',
function(result) { function (result) {
console.log("User is logged in"); console.log("User is logged in");
userProfile = result; userProfile = result;
@@ -137,7 +148,7 @@
if (!userProfile.roles.includes("Admin") && !userProfile.roles.includes("Gamer")) { if (!userProfile.roles.includes("Admin") && !userProfile.roles.includes("Gamer")) {
uploadButton.style.display = 'none'; uploadButton.style.display = 'none';
} }
// populate page // populate page
var myParam = getQueryString('page', 'string'); var myParam = getQueryString('page', 'string');
@@ -147,7 +158,7 @@
$('#content').load('/pages/' + myParam + '.html?v=' + AppVersion); $('#content').load('/pages/' + myParam + '.html?v=' + AppVersion);
}, },
function(error) { function (error) {
window.location.replace("/pages/login.html"); window.location.replace("/pages/login.html");
} }
); );
@@ -159,15 +170,15 @@
} }
// Close the dropdown menu if the user clicks outside of it // Close the dropdown menu if the user clicks outside of it
window.onclick = function(event) { window.onclick = function (event) {
if (!event.target.matches('.dropbtn')) { if (!event.target.matches('.dropbtn')) {
var dropdowns = document.getElementsByClassName("dropdown-content"); var dropdowns = document.getElementsByClassName("dropdown-content");
var i; var i;
for (i = 0; i < dropdowns.length; i++) { for (i = 0; i < dropdowns.length; i++) {
var openDropdown = dropdowns[i]; var openDropdown = dropdowns[i];
if (openDropdown.classList.contains('show')) { if (openDropdown.classList.contains('show')) {
openDropdown.classList.remove('show'); openDropdown.classList.remove('show');
} }
} }
} }
} }
@@ -186,4 +197,5 @@
} }
</script> </script>
</body> </body>
</html>
</html>

View File

@@ -1,6 +1,9 @@
<div id="saved_states"> <div id="saved_states">
</div> </div>
<div>
<span>Upload state file: </span><input type="file" id="stateFile" />
</div>
<script text="text/javascript"> <script text="text/javascript">
document.getElementById('modal-heading').innerHTML = "Load saved state"; document.getElementById('modal-heading').innerHTML = "Load saved state";
@@ -13,9 +16,10 @@
ajaxCall( ajaxCall(
statesUrl, statesUrl,
'GET', 'GET',
function(result) { function (result) {
var statesBox = document.getElementById('saved_states'); var statesBox = document.getElementById('saved_states');
statesBox.innerHTML = ''; statesBox.innerHTML = '';
document.getElementById('stateFile').value = '';
for (var i = 0; i < result.length; i++) { for (var i = 0; i < result.length; i++) {
var stateBox = document.createElement('div'); var stateBox = document.createElement('div');
@@ -62,7 +66,7 @@
stateControls.id = 'stateControls_' + result[i].id; stateControls.id = 'stateControls_' + result[i].id;
stateControls.className = 'saved_state_controls'; stateControls.className = 'saved_state_controls';
var stateControlsLaunch= document.createElement('span'); var stateControlsLaunch = document.createElement('span');
stateControlsLaunch.id = 'stateControlsLaunch_' + result[i].id; stateControlsLaunch.id = 'stateControlsLaunch_' + result[i].id;
stateControlsLaunch.className = 'romstart'; stateControlsLaunch.className = 'romstart';
var emulatorTarget = '/index.html?page=emulator&engine=@engine&core=@core&platformid=@platformid&gameid=@gameid&romid=@romid&mediagroup=@mediagroup&rompath=@rompath&stateid=' + result[i].id; var emulatorTarget = '/index.html?page=emulator&engine=@engine&core=@core&platformid=@platformid&gameid=@gameid&romid=@romid&mediagroup=@mediagroup&rompath=@rompath&stateid=' + result[i].id;
@@ -128,7 +132,7 @@
ajaxCall( ajaxCall(
'/api/v1.1/StateManager/' + modalVariables.romId + '/' + StateId + '?IsMediaGroup=' + IsMediaGroup, '/api/v1.1/StateManager/' + modalVariables.romId + '/' + StateId + '?IsMediaGroup=' + IsMediaGroup,
'DELETE', 'DELETE',
function(success) { function (success) {
LoadStates(); LoadStates();
}, },
function (error) { function (error) {
@@ -147,7 +151,7 @@
ajaxCall( ajaxCall(
'/api/v1.1/StateManager/' + modalVariables.romId + '/' + StateId + '?IsMediaGroup=' + IsMediaGroup, '/api/v1.1/StateManager/' + modalVariables.romId + '/' + StateId + '?IsMediaGroup=' + IsMediaGroup,
'PUT', 'PUT',
function(success) { function (success) {
LoadStates(); LoadStates();
}, },
function (error) { function (error) {
@@ -156,4 +160,36 @@
JSON.stringify(model) JSON.stringify(model)
); );
} }
document.getElementById('stateFile').addEventListener('change', function () {
let file = document.getElementById('stateFile').files[0];
let formData = new FormData();
formData.append('file', file);
console.log("Uploading state file");
fetch('/api/v1.1/StateManager/Upload?RomId=' + modalVariables.romId + '&IsMediaGroup=' + modalVariables.IsMediaGroup, {
method: 'POST',
body: formData
})
.then(response => response.json())
.then(data => {
console.log('Success:', data);
UploadAlert(data);
LoadStates();
})
.catch(error => {
console.error("Error:", error);
UploadAlert(error);
LoadStates();
});
});
function UploadAlert(data) {
if (data.Management == "Managed") {
alert("State uploaded successfully.");
} else {
alert("State uploaded successfully, but it might not function correctly for this platform and ROM.");
}
}
</script> </script>

View File

@@ -13,7 +13,7 @@
if (IsMediaGroupInt == 1) { IsMediaGroup = true; } if (IsMediaGroupInt == 1) { IsMediaGroup = true; }
var StateUrl = undefined; var StateUrl = undefined;
if (getQueryString('stateid', 'int')) { if (getQueryString('stateid', 'int')) {
StateUrl = '/api/v1.1/StateManager/' + romId + '/' + getQueryString('stateid', 'int') + '/State/savestate.state?IsMediaGroup=' + IsMediaGroup; StateUrl = '/api/v1.1/StateManager/' + romId + '/' + getQueryString('stateid', 'int') + '/State/savestate.state?StateOnly=true&IsMediaGroup=' + IsMediaGroup;
} }
var gameData; var gameData;
var artworks = null; var artworks = null;
@@ -60,7 +60,7 @@
case 'EmulatorJS': case 'EmulatorJS':
console.log("Emulator: " + getQueryString('engine', 'string')); console.log("Emulator: " + getQueryString('engine', 'string'));
console.log("Core: " + getQueryString('core', 'string')); console.log("Core: " + getQueryString('core', 'string'));
$('#emulator').load('/emulators/EmulatorJS.html?v=' + AppVersion); $('#emulator').load('/emulators/EmulatorJS.html?v=' + AppVersion);
break; break;
} }
@@ -95,11 +95,11 @@
'/api/v1.1/Statistics/Games/' + gameId + '/' + SessionId, '/api/v1.1/Statistics/Games/' + gameId + '/' + SessionId,
'PUT', 'PUT',
function (success) { function (success) {
} }
); );
} }
} }
setInterval(SaveStatistics, 60000); setInterval(SaveStatistics, 60000);
</script> </script>

View File

@@ -1,5 +1,6 @@
<!DOCTYPE html> <!DOCTYPE html>
<html> <html>
<head> <head>
<meta charset="utf-8" /> <meta charset="utf-8" />
<script src="/api/v1.1/System/VersionFile"></script> <script src="/api/v1.1/System/VersionFile"></script>
@@ -40,8 +41,10 @@
} }
</script> </script>
</head> </head>
<body> <body>
<div id="bgImage" style="background-image: url('/images/LoginWallpaper.jpg'); background-position: center; background-repeat: no-repeat; background-size: cover; filter: blur(10px); -webkit-filter: blur(10px);"> <div id="bgImage"
style="background-image: url('/images/LoginWallpaper.jpg'); background-position: center; background-repeat: no-repeat; background-size: cover; filter: blur(10px); -webkit-filter: blur(10px);">
<div id="bgImage_Opacity"></div> <div id="bgImage_Opacity"></div>
</div> </div>
@@ -52,7 +55,9 @@
<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>
<button type="button" value="Get Started" onclick="document.getElementById('first_welcome').style.display = 'none'; document.getElementById('first_newadmin').style.display = '';" class="bigbutton">Get Started</button> <button type="button" value="Get Started"
onclick="document.getElementById('first_welcome').style.display = 'none'; document.getElementById('first_newadmin').style.display = '';"
class="bigbutton">Get Started</button>
</div> </div>
</div> </div>
<div class="loginwindow" id="first_newadmin" style="display: none;"> <div class="loginwindow" id="first_newadmin" style="display: none;">
@@ -67,15 +72,18 @@
</tr> </tr>
<tr> <tr>
<th>Email</th> <th>Email</th>
<td><input type="email" id="login_email" style="width: 95%;" onkeyup="checkPasswordsMatch();" /></td> <td><input type="email" id="login_email" style="width: 95%;" onkeyup="checkPasswordsMatch();" />
</td>
</tr> </tr>
<tr> <tr>
<th>New Password</th> <th>New Password</th>
<td><input type="password" id="login_password" style="width: 95%;" onkeyup="checkPasswordsMatch();" /></td> <td><input type="password" id="login_password" style="width: 95%;"
onkeyup="checkPasswordsMatch();" /></td>
</tr> </tr>
<tr> <tr>
<th>Confirm Password</th> <th>Confirm Password</th>
<td><input type="password" id="login_confirmpassword" style="width: 95%;" onkeyup="checkPasswordsMatch();" /></td> <td><input type="password" id="login_confirmpassword" style="width: 95%;"
onkeyup="checkPasswordsMatch();" /></td>
</tr> </tr>
<tr> <tr>
<td colspan="2" id="login_passwordnotice">&nbsp;</td> <td colspan="2" id="login_passwordnotice">&nbsp;</td>
@@ -85,7 +93,9 @@
</tr> </tr>
<tr> <tr>
<td colspan="2" style="padding-top: 20px;"> <td colspan="2" style="padding-top: 20px;">
<button id="login_createaccount" type="button" value="Create Account" onclick="registerAccount();" disabled="disabled" class="bigbutton">Create Account</button> <button id="login_createaccount" type="button" value="Create Account"
onclick="registerAccount();" disabled="disabled" class="bigbutton">Create
Account</button>
</td> </td>
</tr> </tr>
</table> </table>
@@ -94,12 +104,14 @@
</div> </div>
<div id="settings_photocredit"> <div id="settings_photocredit">
Wallpaper by <a href="https://unsplash.com/@spideyjoey" class="romlink">Joey Kwok</a> / <a href="https://unsplash.com/photos/a-room-filled-with-arcade-machines-and-neon-lights-jbIsTd7rdd8" class="romlink">Unsplash</a> Wallpaper by <a href="https://unsplash.com/@spideyjoey" class="romlink">Joey Kwok</a> / <a
href="https://unsplash.com/photos/a-room-filled-with-arcade-machines-and-neon-lights-jbIsTd7rdd8"
class="romlink">Unsplash</a>
</div> </div>
<script type="text/javascript"> <script type="text/javascript">
// redirect if first run status != 0 as 0 indicates that first run needs to be run // redirect if first run status != 0 as 0 indicates that first run needs to be run
if (FirstRunStatus != 0) { if (FirstRunStatus != 0 && FirstRunStatus != "0") {
window.location.replace("/"); window.location.replace("/");
} }
@@ -146,10 +158,10 @@
ajaxCall( ajaxCall(
'/api/v1.1/FirstSetup/0', '/api/v1.1/FirstSetup/0',
'POST', 'POST',
function(result){ function (result) {
loginCallback(result); loginCallback(result);
}, },
function(error){ function (error) {
loginCallback(error); loginCallback(error);
}, },
JSON.stringify(model) JSON.stringify(model)

View File

@@ -39,7 +39,7 @@ function ajaxCall(endpoint, method, successFunction, errorFunction, body) {
function getQueryString(stringName, type) { function getQueryString(stringName, type) {
const urlParams = new URLSearchParams(window.location.search); const urlParams = new URLSearchParams(window.location.search);
var myParam = urlParams.get(stringName); var myParam = urlParams.get(stringName);
switch (type) { switch (type) {
case "int": case "int":
@@ -63,9 +63,9 @@ function getQueryString(stringName, type) {
function setCookie(cname, cvalue, exdays) { function setCookie(cname, cvalue, exdays) {
const d = new Date(); const d = new Date();
d.setTime(d.getTime() + (exdays*24*60*60*1000)); d.setTime(d.getTime() + (exdays * 24 * 60 * 60 * 1000));
if (exdays) { if (exdays) {
let expires = "expires="+ d.toUTCString(); let expires = "expires=" + d.toUTCString();
document.cookie = cname + "=" + cvalue + ";" + expires + ";path=/"; document.cookie = cname + "=" + cvalue + ";" + expires + ";path=/";
} else { } else {
document.cookie = cname + "=" + cvalue + ";path=/"; document.cookie = cname + "=" + cvalue + ";path=/";
@@ -76,14 +76,14 @@ function getCookie(cname) {
let name = cname + "="; let name = cname + "=";
let decodedCookie = decodeURIComponent(document.cookie); let decodedCookie = decodeURIComponent(document.cookie);
let ca = decodedCookie.split(';'); let ca = decodedCookie.split(';');
for(let i = 0; i <ca.length; i++) { for (let i = 0; i < ca.length; i++) {
let c = ca[i]; let c = ca[i];
while (c.charAt(0) == ' ') { while (c.charAt(0) == ' ') {
c = c.substring(1); c = c.substring(1);
} }
if (c.indexOf(name) == 0) { if (c.indexOf(name) == 0) {
return c.substring(name.length, c.length); return c.substring(name.length, c.length);
} }
} }
return ""; return "";
} }
@@ -207,7 +207,7 @@ function createTableRow(isHeader, row, rowClass, cellClass) {
} }
var newCell = document.createElement(cellType); var newCell = document.createElement(cellType);
if (typeof(row[i]) != "object") { if (typeof (row[i]) != "object") {
newCell.innerHTML = row[i]; newCell.innerHTML = row[i];
newCell.className = cellClass; newCell.className = cellClass;
} else { } else {
@@ -258,7 +258,7 @@ function DropDownRenderGameOption(state) {
if (state.cover) { if (state.cover) {
response = $( response = $(
'<table class="dropdown-div"><tr><td class="dropdown-cover"><img src="/api/v1.1/Games/' + state.id + '/cover/image/cover_small/' + state.cover.imageId + '.jpg" /></td><td class="dropdown-label"><span class="dropdown-title">' + state.text + '</span><span class="dropdown-releasedate">' + releaseDate + '</span></td></tr></table>' '<table class="dropdown-div"><tr><td class="dropdown-cover"><img src="/api/v1.1/Games/' + state.id + '/cover/image/cover_small/' + state.id + '.jpg" class="game_tile_small_search" /></td><td class="dropdown-label"><span class="dropdown-title">' + state.text + '</span><span class="dropdown-releasedate">' + releaseDate + '</span></td></tr></table>'
); );
} else { } else {
response = $( response = $(
@@ -317,8 +317,8 @@ function CreateEditableTable(TableName, Headers) {
var addButton = document.createElement('button'); var addButton = document.createElement('button');
addButton.value = 'Add Row'; addButton.value = 'Add Row';
addButton.innerHTML = 'Add Row'; addButton.innerHTML = 'Add Row';
$(addButton).click(function() { $(addButton).click(function () {
eTable.appendChild(AddEditableTableRow(Headers)); eTable.appendChild(AddEditableTableRow(Headers));
}); });
@@ -463,10 +463,10 @@ function SetPreference(Setting, Value) {
ajaxCall( ajaxCall(
'/api/v1.1/Account/Preferences', '/api/v1.1/Account/Preferences',
'POST', 'POST',
function(result) { function (result) {
SetPreference_Local(Setting, Value); SetPreference_Local(Setting, Value);
}, },
function(error) { function (error) {
SetPreference_Local(Setting, Value); SetPreference_Local(Setting, Value);
}, },
JSON.stringify(model) JSON.stringify(model)
@@ -478,12 +478,12 @@ function SetPreference_Batch(model) {
ajaxCall( ajaxCall(
'/api/v1.1/Account/Preferences', '/api/v1.1/Account/Preferences',
'POST', 'POST',
function(result) { function (result) {
for (var i = 0; i < model.length; i++) { for (var i = 0; i < model.length; i++) {
SetPreference_Local(model[i].setting, model[i].value.toString()); SetPreference_Local(model[i].setting, model[i].value.toString());
} }
}, },
function(error) { function (error) {
for (var i = 0; i < model.length; i++) { for (var i = 0; i < model.length; i++) {
SetPreference_Local(model[i].setting, model[i].value.toString()); SetPreference_Local(model[i].setting, model[i].value.toString());
} }
@@ -509,11 +509,11 @@ function SetPreference_Local(Setting, Value) {
} }
} }
function Uint8ToString(u8a){ function Uint8ToString(u8a) {
var CHUNK_SZ = 0x8000; var CHUNK_SZ = 0x8000;
var c = []; var c = [];
for (var i=0; i < u8a.length; i+=CHUNK_SZ) { for (var i = 0; i < u8a.length; i += CHUNK_SZ) {
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("");
} }

View File

@@ -23,21 +23,29 @@ h3 {
border-bottom-width: 1px; border-bottom-width: 1px;
/*border-image: linear-gradient(to right, blue 25%, yellow 25%, yellow 50%,red 50%, red 75%, teal 75%) 5;*/ /*border-image: linear-gradient(to right, blue 25%, yellow 25%, yellow 50%,red 50%, red 75%, teal 75%) 5;*/
border-image: linear-gradient(to right, rgba(255,0,0,1) 0%, rgba(251,255,0,1) 16%, rgba(0,255,250,1) 30%, rgba(0,16,255,1) 46%, rgba(250,0,255,1) 62%, rgba(255,0,0,1) 78%, rgba(255,237,0,1) 90%, rgba(20,255,0,1) 100%) 5; border-image: linear-gradient(to right, rgba(255, 0, 0, 1) 0%, rgba(251, 255, 0, 1) 16%, rgba(0, 255, 250, 1) 30%, rgba(0, 16, 255, 1) 46%, rgba(250, 0, 255, 1) 62%, rgba(255, 0, 0, 1) 78%, rgba(255, 237, 0, 1) 90%, rgba(20, 255, 0, 1) 100%) 5;
} }
/* The Modal (background) */ /* The Modal (background) */
.modal { .modal {
display: none; /* Hidden by default */ display: none;
position: fixed; /* Stay in place */ /* Hidden by default */
z-index: 100; /* Sit on top */ position: fixed;
/* Stay in place */
z-index: 100;
/* Sit on top */
left: 0; left: 0;
top: 0; top: 0;
width: 100%; /* Full width */ width: 100%;
height: 100%; /* Full height */ /* Full width */
overflow: none; /* Enable scroll if needed */ height: 100%;
background-color: rgb(0,0,0); /* Fallback color */ /* Full height */
background-color: rgba(0,0,0,0.4); /* Black w/ opacity */ overflow: none;
/* Enable scroll if needed */
background-color: rgb(0, 0, 0);
/* Fallback color */
background-color: rgba(0, 0, 0, 0.4);
/* Black w/ opacity */
backdrop-filter: blur(8px); backdrop-filter: blur(8px);
-webkit-backdrop-filter: blur(8px); -webkit-backdrop-filter: blur(8px);
filter: drop-shadow(5px 5px 10px #000); filter: drop-shadow(5px 5px 10px #000);
@@ -47,22 +55,28 @@ h3 {
/* Modal Content/Box */ /* Modal Content/Box */
.modal-content { .modal-content {
background-color: #383838; background-color: #383838;
margin: 10% auto; /* 15% from the top and centered */ margin: 10% auto;
/* 15% from the top and centered */
padding: 10px; padding: 10px;
border: 1px solid #888; border: 1px solid #888;
border-radius: 10px; border-radius: 10px;
width: 700px; /* Could be more or less, depending on screen size */ width: 700px;
/* Could be more or less, depending on screen size */
min-height: 358px; min-height: 358px;
} }
.modal-content-sub { .modal-content-sub {
background-color: #383838; background-color: #383838;
margin: 20% auto; /* 20% from the top and centered */ margin: 20% auto;
/* 20% from the top and centered */
padding: 10px; padding: 10px;
border: 1px solid #888; border: 1px solid #888;
border-radius: 10px; border-radius: 10px;
width: 300px; /* Could be more or less, depending on screen size */ width: 300px;
/* Could be more or less, depending on screen size */
min-height: 110px; min-height: 110px;
} }
#modal-heading { #modal-heading {
margin-block: 5px; margin-block: 5px;
border-bottom-style: solid; border-bottom-style: solid;
@@ -70,8 +84,9 @@ h3 {
border-bottom-width: 3px; border-bottom-width: 3px;
/*border-image: linear-gradient(to right, blue 25%, yellow 25%, yellow 50%,red 50%, red 75%, teal 75%) 5;*/ /*border-image: linear-gradient(to right, blue 25%, yellow 25%, yellow 50%,red 50%, red 75%, teal 75%) 5;*/
border-image: linear-gradient(to right, rgba(255,0,0,1) 0%, rgba(251,255,0,1) 16%, rgba(0,255,250,1) 30%, rgba(0,16,255,1) 46%, rgba(250,0,255,1) 62%, rgba(255,0,0,1) 78%, rgba(255,237,0,1) 90%, rgba(20,255,0,1) 100%) 5; border-image: linear-gradient(to right, rgba(255, 0, 0, 1) 0%, rgba(251, 255, 0, 1) 16%, rgba(0, 255, 250, 1) 30%, rgba(0, 16, 255, 1) 46%, rgba(250, 0, 255, 1) 62%, rgba(255, 0, 0, 1) 78%, rgba(255, 237, 0, 1) 90%, rgba(20, 255, 0, 1) 100%) 5;
} }
#modal-content { #modal-content {
height: 100%; height: 100%;
} }
@@ -268,7 +283,7 @@ h3 {
} }
#games_filter { #games_filter {
width: 200px; width: 200px;
/* border-style: solid; /* border-style: solid;
border-width: 1px; border-width: 1px;
@@ -296,7 +311,11 @@ h3 {
z-index: 1; z-index: 1;
} }
input[type='text'], input[type='number'], input[type="email"], input[type="password"], input[type="datetime-local"] { input[type='text'],
input[type='number'],
input[type="email"],
input[type="password"],
input[type="datetime-local"] {
background-color: #2b2b2b; background-color: #2b2b2b;
color: white; color: white;
padding: 4px; padding: 4px;
@@ -313,7 +332,11 @@ input[type='text'], input[type='number'], input[type="email"], input[type="passw
height: 21px; height: 21px;
} }
input[type='text']:hover, input[type='number']:hover, input[type="email"]:hover, input[type="password"]:hover, input[type="datetime-local"]:hover { input[type='text']:hover,
input[type='number']:hover,
input[type="email"]:hover,
input[type="password"]:hover,
input[type="datetime-local"]:hover {
border-color: #939393; border-color: #939393;
} }
@@ -351,9 +374,7 @@ input[name='filter_panel_range_max'] {
background: #555; background: #555;
} }
.text_link { .text_link {}
}
.text_link:hover { .text_link:hover {
cursor: pointer; cursor: pointer;
@@ -390,9 +411,9 @@ input[name='filter_panel_range_max'] {
background-color: rgba(0, 22, 56, 0.8); background-color: rgba(0, 22, 56, 0.8);
backdrop-filter: blur(8px); backdrop-filter: blur(8px);
-webkit-backdrop-filter: blur(8px); -webkit-backdrop-filter: blur(8px);
box-shadow: 5px 5px 19px 0px rgba(0,0,0,0.44); 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); -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); -moz-box-shadow: 5px 5px 19px 0px rgba(0, 0, 0, 0.44);
} }
.games_pager_number { .games_pager_number {
@@ -525,13 +546,13 @@ input[name='filter_panel_range_max'] {
overflow-y: auto; overflow-y: auto;
/* display: flex; */ /* display: flex; */
justify-content: center; justify-content: center;
align-items: center; align-items: center;
} }
#games_library_alpha_pager { #games_library_alpha_pager {
width: 50px; width: 50px;
justify-content: center; justify-content: center;
align-items: center; align-items: center;
} }
.games_library_alpha_pager_letter { .games_library_alpha_pager_letter {
@@ -604,6 +625,12 @@ input[name='filter_panel_range_max'] {
border: 1px solid #2b2b2b; border: 1px solid #2b2b2b;
} }
.game_tile_small_search {
min-height: 50px;
min-width: 50px;
width: 80px;
}
.game_tile_row { .game_tile_row {
padding: 5px; padding: 5px;
display: block; display: block;
@@ -706,9 +733,9 @@ input[name='filter_panel_range_max'] {
} }
.game_tile_image_shadow { .game_tile_image_shadow {
box-shadow: 5px 5px 19px 0px rgba(0,0,0,0.44); 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); -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); -moz-box-shadow: 5px 5px 19px 0px rgba(0, 0, 0, 0.44);
} }
.game_tile_image_row { .game_tile_image_row {
@@ -720,11 +747,13 @@ input[name='filter_panel_range_max'] {
background-color: transparent; background-color: transparent;
} }
.game_tile_image, .unknown { .game_tile_image,
.unknown {
background-color: transparent; background-color: transparent;
} }
.game_tile_image_row, .unknown { .game_tile_image_row,
.unknown {
background-color: transparent; background-color: transparent;
} }
@@ -790,9 +819,9 @@ input[name='filter_panel_range_max'] {
max-width: 250px; max-width: 250px;
max-height: 350px; max-height: 350px;
width: 100%; width: 100%;
box-shadow: 5px 5px 19px 0px rgba(0,0,0,0.44); 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); -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); -moz-box-shadow: 5px 5px 19px 0px rgba(0, 0, 0, 0.44);
} }
.gamegenrelabel { .gamegenrelabel {
@@ -842,9 +871,9 @@ input[name='filter_panel_range_max'] {
padding: 10px; padding: 10px;
/*height: 350px;*/ /*height: 350px;*/
margin-bottom: 20px; margin-bottom: 20px;
box-shadow: 5px 5px 19px 0px rgba(0,0,0,0.44); 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); -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); -moz-box-shadow: 5px 5px 19px 0px rgba(0, 0, 0, 0.44);
} }
#gamescreenshots_main { #gamescreenshots_main {
@@ -903,11 +932,16 @@ iframe {
background-color: #383838; background-color: #383838;
color: black; color: black;
text-align: center; text-align: center;
user-select: none; /* standard syntax */ user-select: none;
-webkit-user-select: none; /* webkit (safari, chrome) browsers */ /* standard syntax */
-moz-user-select: none; /* mozilla browsers */ -webkit-user-select: none;
-khtml-user-select: none; /* webkit (konqueror) browsers */ /* webkit (safari, chrome) browsers */
-ms-user-select: none; /* IE10+ */ -moz-user-select: none;
/* mozilla browsers */
-khtml-user-select: none;
/* webkit (konqueror) browsers */
-ms-user-select: none;
/* IE10+ */
} }
.gamescreenshots_arrows:hover { .gamescreenshots_arrows:hover {
@@ -981,9 +1015,7 @@ iframe {
background-color: rgba(56, 56, 56, 0.9); background-color: rgba(56, 56, 56, 0.9);
} }
#gamesummarytext_label { #gamesummarytext_label {}
}
.line-clamp-4 { .line-clamp-4 {
overflow: hidden; overflow: hidden;
@@ -1057,9 +1089,9 @@ th {
-webkit-border-radius: 5px 5px 5px 5px; -webkit-border-radius: 5px 5px 5px 5px;
-moz-border-radius: 5px 5px 5px 5px; -moz-border-radius: 5px 5px 5px 5px;
border: 1px solid #19d348; border: 1px solid #19d348;
box-shadow: 5px 5px 19px 0px rgba(0,0,0,0.44); 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); -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); -moz-box-shadow: 5px 5px 19px 0px rgba(0, 0, 0, 0.44);
} }
.romstart:hover { .romstart:hover {
@@ -1084,9 +1116,9 @@ th {
border-color: white; border-color: white;
background-color: blue; background-color: blue;
outline-color: blue; outline-color: blue;
box-shadow: 5px 5px 19px 0px rgba(0,0,0,0.44); 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); -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); -moz-box-shadow: 5px 5px 19px 0px rgba(0, 0, 0, 0.44);
} }
.properties_button:hover { .properties_button:hover {
@@ -1115,14 +1147,18 @@ th {
height: 100%; height: 100%;
} }
div[name="properties_toc_item"],div[name="properties_user_toc_item"],div[name="properties_profile_toc_item"] { div[name="properties_toc_item"],
div[name="properties_user_toc_item"],
div[name="properties_profile_toc_item"] {
padding: 10px; padding: 10px;
border-bottom-width: 1px; border-bottom-width: 1px;
border-bottom-style: solid; border-bottom-style: solid;
border-bottom-color: #2b2b2b; border-bottom-color: #2b2b2b;
} }
div[name="properties_toc_item"]:hover,div[name="properties_user_toc_item"]:hover,div[name="properties_profile_toc_item"]:hover { div[name="properties_toc_item"]:hover,
div[name="properties_user_toc_item"]:hover,
div[name="properties_profile_toc_item"]:hover {
background-color: #2b2b2b; background-color: #2b2b2b;
cursor: pointer; cursor: pointer;
} }
@@ -1150,7 +1186,8 @@ div[name="properties_toc_item"]:hover,div[name="properties_user_toc_item"]:hover
border-radius: 5px; border-radius: 5px;
} }
.select2-container--default:hover, .select2-selection--multiple:hover { .select2-container--default:hover,
.select2-selection--multiple:hover {
border-color: #939393; border-color: #939393;
} }
@@ -1197,7 +1234,8 @@ div[name="properties_toc_item"]:hover,div[name="properties_user_toc_item"]:hover
border-radius: 5px; border-radius: 5px;
} }
.select2-selection--single:hover, .select2-selection__rendered:hover { .select2-selection--single:hover,
.select2-selection__rendered:hover {
border-color: #939393; border-color: #939393;
} }
@@ -1302,9 +1340,7 @@ button:not(.select2-selection__choice__remove):not(.ejs_menu_button):disabled {
background-color: #555; background-color: #555;
} }
#emulator { #emulator {}
}
.emulator_partscreen { .emulator_partscreen {
margin: 0 auto; margin: 0 auto;
@@ -1377,15 +1413,14 @@ button:not(.select2-selection__choice__remove):not(.ejs_menu_button):disabled {
margin-left: 15px; margin-left: 15px;
} }
.rom_checkbox_box { .rom_checkbox_box {}
}
.rom_checkbox_box_hidden { .rom_checkbox_box_hidden {
display: none; display: none;
} }
#rom_edit, #rom_edit_delete { #rom_edit,
#rom_edit_delete {
float: right; float: right;
} }
@@ -1398,9 +1433,9 @@ button:not(.select2-selection__choice__remove):not(.ejs_menu_button):disabled {
} }
#game { #game {
box-shadow: 5px 5px 19px 0px rgba(0,0,0,0.44); 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); -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); -moz-box-shadow: 5px 5px 19px 0px rgba(0, 0, 0, 0.44);
} }
#gametitle_criticrating { #gametitle_criticrating {
@@ -1440,7 +1475,7 @@ button:not(.select2-selection__choice__remove):not(.ejs_menu_button):disabled {
width: 1000px; width: 1000px;
height: 90%; height: 90%;
margin: 25px auto; margin: 25px auto;
/* overflow-x: scroll;*/ /* overflow-x: scroll;*/
position: relative; position: relative;
} }
@@ -1468,7 +1503,8 @@ button:not(.select2-selection__choice__remove):not(.ejs_menu_button):disabled {
} }
.bgalt1 { .bgalt1 {
background-color: transparent;; background-color: transparent;
;
} }
.logs_table_cell_150px { .logs_table_cell_150px {
@@ -1520,13 +1556,33 @@ button:not(.select2-selection__choice__remove):not(.ejs_menu_button):disabled {
background-color: #383838; background-color: #383838;
} }
.string { color: lightblue; } .string {
.number { color: lightblue; } color: lightblue;
.boolean { color: lightblue; } }
.null { color: magenta; }
.key { color: greenyellow; } .number {
.brace { color: #888; } color: lightblue;
.square { color: #fff000; } }
.boolean {
color: lightblue;
}
.null {
color: magenta;
}
.key {
color: greenyellow;
}
.brace {
color: #888;
}
.square {
color: #fff000;
}
.tagBox { .tagBox {
position: absolute; position: absolute;
@@ -1557,12 +1613,16 @@ button:not(.select2-selection__choice__remove):not(.ejs_menu_button):disabled {
} }
.loginwindow { .loginwindow {
position: fixed; /* Stay in place */ position: fixed;
/* Stay in place */
left: 0; left: 0;
top: 0; top: 0;
width: 100%; /* Full width */ width: 100%;
height: 100%; /* Full height */ /* Full width */
overflow: auto; /* Enable scroll if needed */ height: 100%;
/* Full height */
overflow: auto;
/* Enable scroll if needed */
/*background-color: rgb(0,0,0); /* Fallback color */ /*background-color: rgb(0,0,0); /* Fallback color */
/*background-color: rgba(0,0,0,0.4); /* Black w/ opacity */ /*background-color: rgba(0,0,0,0.4); /* Black w/ opacity */
/*backdrop-filter: blur(8px); /*backdrop-filter: blur(8px);
@@ -1575,11 +1635,13 @@ button:not(.select2-selection__choice__remove):not(.ejs_menu_button):disabled {
.loginwindow-content { .loginwindow-content {
position: relative; position: relative;
background-color: #383838; background-color: #383838;
margin: 15% auto; /* 15% from the top and centered */ margin: 15% auto;
/* 15% from the top and centered */
padding: 10px; padding: 10px;
border: 1px solid #888; border: 1px solid #888;
border-radius: 10px; border-radius: 10px;
width: 350px; /* Could be more or less, depending on screen size */ width: 350px;
/* Could be more or less, depending on screen size */
min-height: 250px; min-height: 250px;
} }
@@ -1615,7 +1677,8 @@ button:not(.select2-selection__choice__remove):not(.ejs_menu_button):disabled {
} }
/* Links inside the dropdown */ /* Links inside the dropdown */
.dropdown-content a, .dropdown-content span { .dropdown-content a,
.dropdown-content span {
color: black; color: black;
padding: 12px 16px; padding: 12px 16px;
text-decoration: none; text-decoration: none;
@@ -1625,12 +1688,16 @@ button:not(.select2-selection__choice__remove):not(.ejs_menu_button):disabled {
.dropdown-content span { .dropdown-content span {
cursor: auto; cursor: auto;
} }
/* Change color of dropdown links on hover */ /* Change color of dropdown links on hover */
.dropdown-content a:hover {background-color: #ddd;} .dropdown-content a:hover {
background-color: #ddd;
}
/* Show the dropdown menu (use JS to add this class to the .dropdown-content container when the user clicks on the dropdown button) */ /* Show the dropdown menu (use JS to add this class to the .dropdown-content container when the user clicks on the dropdown button) */
.show {display:block;} .show {
display: block;
}
.dropdownroleitem { .dropdownroleitem {
text-transform: capitalize; text-transform: capitalize;