Compare commits

..

65 Commits

Author SHA1 Message Date
Michael Green
9922e61b44 Deprecate API v1 - API v1.1 is now default (#469) 2024-12-19 14:37:21 +11:00
Michael Green
98a09c32f8 Add a healthcheck endpoint (#468)
The purpose of this endpoint is to respond only with http code 200, to
be used by services such as Docker to check if the server is still
running.
2024-12-19 14:18:36 +11:00
Michael Green
81b58e9b9f Add Redump and No-Intro DAT support (#467) 2024-12-19 13:46:04 +11:00
Michael Green
928a1538ea Allow the user to control how metadata is searched (#448) 2024-10-26 00:25:39 +11:00
Michael Green
f4a8892cbb Remove unneeded codeql action (#447) 2024-10-25 00:36:05 +11:00
Michael Green
c4435628e0 Updated to the latest version of EJS (see: https://github.com/EmulatorJS/EmulatorJS/releases/tag/v4.1.1) (#446) 2024-10-25 00:18:13 +11:00
Michael Green
933c624885 Enabled load and save buttons in addition to standard state load and save (#445) 2024-10-25 00:09:51 +11:00
Michael Green
06e344c74a Updated PlatformMap.json to support Super Famicom and others (#444) 2024-10-24 23:53:32 +11:00
Michael Green
9150ce7377 Dependency version bump (#443) 2024-10-24 23:30:02 +11:00
Michael Green
08a40e3dd9 Remove deprecated field from IGDB metadata (#403) 2024-08-07 23:57:38 +10:00
Michael Green
d7fca42057 Fixed import of MAME style DATs (#400) 2024-07-22 15:51:57 +10:00
Michael Green
1672520a29 Expand the platform table name field to accomodate larger platform names (#397)
IGDB has recently added a new platform who's name exceeds 45 characters
(the maximum Name length in the Platform table). This change extends the
character length to 255 chars.
2024-07-17 23:13:06 +10:00
Michael Green
3366d926f4 Unraid compatible single container docker image (#390)
See https://github.com/gaseous-project/unraid-template for an Unraid XML
template.
2024-07-12 15:43:02 +10:00
Michael Green
6d7f6f63c6 Resolved an error in the new logging cleanup (#389) 2024-07-10 11:53:37 +10:00
Michael Green
590a7829b1 Another attempt at fixing the Embedded DB Docker image (#388) 2024-07-08 21:22:26 +10:00
Michael Green
ae75fc1490 Fix for Embedded DB Docker Image (#387) 2024-07-08 08:29:32 +10:00
Michael Green
0c645d04aa v1.7.0 git sync 2024-07-06 19:54:56 +10:00
Michael Green
13b3764fcf Add Famicom to PlatformMap (#384) 2024-07-06 15:52:57 +10:00
Michael Green
57d4fe7cf9 Merge branch 'main' into branch-v1.7.0 2024-06-30 00:31:06 +10:00
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
dependabot[bot]
fd7b354571 chore(deps): bump Swashbuckle.AspNetCore from 6.5.0 to 6.6.1 (#360)
Bumps
[Swashbuckle.AspNetCore](https://github.com/domaindrivendev/Swashbuckle.AspNetCore)
from 6.5.0 to 6.6.1.
<details>
<summary>Release notes</summary>
<p><em>Sourced from <a
href="https://github.com/domaindrivendev/Swashbuckle.AspNetCore/releases">Swashbuckle.AspNetCore's
releases</a>.</em></p>
<blockquote>
<h2>v6.6.1</h2>
<h2>What's Changed</h2>
<ul>
<li>Modernise build and migrate to GitHub Actions for CI by <a
href="https://github.com/martincostello"><code>@​martincostello</code></a>
in <a
href="https://redirect.github.com/domaindrivendev/Swashbuckle.AspNetCore/pull/2775">domaindrivendev/Swashbuckle.AspNetCore#2775</a></li>
<li>Update Redoc spelling in docs by <a
href="https://github.com/Quppa"><code>@​Quppa</code></a> in <a
href="https://redirect.github.com/domaindrivendev/Swashbuckle.AspNetCore/pull/2568">domaindrivendev/Swashbuckle.AspNetCore#2568</a></li>
<li>C# 9 Record - Read example/summary from positional record by <a
href="https://github.com/pixellos"><code>@​pixellos</code></a> in <a
href="https://redirect.github.com/domaindrivendev/Swashbuckle.AspNetCore/pull/2546">domaindrivendev/Swashbuckle.AspNetCore#2546</a></li>
<li>Grammatical correction of some comments by <a
href="https://github.com/mokarchi"><code>@​mokarchi</code></a> in <a
href="https://redirect.github.com/domaindrivendev/Swashbuckle.AspNetCore/pull/2768">domaindrivendev/Swashbuckle.AspNetCore#2768</a></li>
<li>Update README.md for nested types custom schemaId support by <a
href="https://github.com/antmeehan"><code>@​antmeehan</code></a> in <a
href="https://redirect.github.com/domaindrivendev/Swashbuckle.AspNetCore/pull/2746">domaindrivendev/Swashbuckle.AspNetCore#2746</a></li>
<li>Add support for <code>WithSummary</code> and
<code>WithDescription</code> metadata by <a
href="https://github.com/hwoodiwiss"><code>@​hwoodiwiss</code></a> in <a
href="https://redirect.github.com/domaindrivendev/Swashbuckle.AspNetCore/pull/2414">domaindrivendev/Swashbuckle.AspNetCore#2414</a></li>
<li>Update README.md - Fix <code>Add Security Definitions and
Requirements for Bearer auth</code> URL by <a
href="https://github.com/Saibamen"><code>@​Saibamen</code></a> in <a
href="https://redirect.github.com/domaindrivendev/Swashbuckle.AspNetCore/pull/2705">domaindrivendev/Swashbuckle.AspNetCore#2705</a></li>
<li>Replace <!-- raw HTML omitted -->text<!-- raw HTML omitted --> with
Markdown link format by <a
href="https://github.com/mburumaxwell"><code>@​mburumaxwell</code></a>
in <a
href="https://redirect.github.com/domaindrivendev/Swashbuckle.AspNetCore/pull/2392">domaindrivendev/Swashbuckle.AspNetCore#2392</a></li>
<li>Add support for required keyword by <a
href="https://github.com/keahpeters"><code>@​keahpeters</code></a> in <a
href="https://redirect.github.com/domaindrivendev/Swashbuckle.AspNetCore/pull/2810">domaindrivendev/Swashbuckle.AspNetCore#2810</a></li>
<li>Resolves <a
href="https://redirect.github.com/domaindrivendev/Swashbuckle.AspNetCore/issues/2717">#2717</a>
by <a href="https://github.com/MerickOWA"><code>@​MerickOWA</code></a>
in <a
href="https://redirect.github.com/domaindrivendev/Swashbuckle.AspNetCore/pull/2718">domaindrivendev/Swashbuckle.AspNetCore#2718</a></li>
<li>Observe the route template constraints in the Swagger middleware by
<a href="https://github.com/0xced"><code>@​0xced</code></a> in <a
href="https://redirect.github.com/domaindrivendev/Swashbuckle.AspNetCore/pull/2418">domaindrivendev/Swashbuckle.AspNetCore#2418</a></li>
<li>Added Swashbuckle.AspNetCore.Annotations.SwaggerIgnoreAttribute by
<a href="https://github.com/jcracknell"><code>@​jcracknell</code></a> in
<a
href="https://redirect.github.com/domaindrivendev/Swashbuckle.AspNetCore/pull/2610">domaindrivendev/Swashbuckle.AspNetCore#2610</a></li>
<li>Fix schema generation with allOf inheritance by <a
href="https://github.com/bkoelman"><code>@​bkoelman</code></a> in <a
href="https://redirect.github.com/domaindrivendev/Swashbuckle.AspNetCore/pull/2815">domaindrivendev/Swashbuckle.AspNetCore#2815</a></li>
<li>avoid triple enumeration of formParameters by <a
href="https://github.com/SimonCropp"><code>@​SimonCropp</code></a> in <a
href="https://redirect.github.com/domaindrivendev/Swashbuckle.AspNetCore/pull/2823">domaindrivendev/Swashbuckle.AspNetCore#2823</a></li>
<li>reduce some linq allocation by <a
href="https://github.com/SimonCropp"><code>@​SimonCropp</code></a> in <a
href="https://redirect.github.com/domaindrivendev/Swashbuckle.AspNetCore/pull/2819">domaindrivendev/Swashbuckle.AspNetCore#2819</a></li>
<li>remove some duplicate dictionary lookups by <a
href="https://github.com/SimonCropp"><code>@​SimonCropp</code></a> in <a
href="https://redirect.github.com/domaindrivendev/Swashbuckle.AspNetCore/pull/2822">domaindrivendev/Swashbuckle.AspNetCore#2822</a></li>
<li>remove redundant any check in InferRequestContentTypes by <a
href="https://github.com/SimonCropp"><code>@​SimonCropp</code></a> in <a
href="https://redirect.github.com/domaindrivendev/Swashbuckle.AspNetCore/pull/2824">domaindrivendev/Swashbuckle.AspNetCore#2824</a></li>
<li>Correctly respect interfaces in <code>GetInheritanceChain</code> by
<a href="https://github.com/angelaki"><code>@​angelaki</code></a> in <a
href="https://redirect.github.com/domaindrivendev/Swashbuckle.AspNetCore/pull/2826">domaindrivendev/Swashbuckle.AspNetCore#2826</a></li>
<li>Generate Enum-Dictionary-Keys (analogous to Newtonsoft) by <a
href="https://github.com/angelaki"><code>@​angelaki</code></a> in <a
href="https://redirect.github.com/domaindrivendev/Swashbuckle.AspNetCore/pull/2825">domaindrivendev/Swashbuckle.AspNetCore#2825</a></li>
<li>Fix build badge by <a
href="https://github.com/martincostello"><code>@​martincostello</code></a>
in <a
href="https://redirect.github.com/domaindrivendev/Swashbuckle.AspNetCore/pull/2782">domaindrivendev/Swashbuckle.AspNetCore#2782</a></li>
<li>Fix preview package versions by <a
href="https://github.com/martincostello"><code>@​martincostello</code></a>
in <a
href="https://redirect.github.com/domaindrivendev/Swashbuckle.AspNetCore/pull/2783">domaindrivendev/Swashbuckle.AspNetCore#2783</a></li>
<li>Handle Stream and PipeReader content types correctly by <a
href="https://github.com/martincostello"><code>@​martincostello</code></a>
in <a
href="https://redirect.github.com/domaindrivendev/Swashbuckle.AspNetCore/pull/2784">domaindrivendev/Swashbuckle.AspNetCore#2784</a></li>
<li>Add NuGet package READMEs by <a
href="https://github.com/martincostello"><code>@​martincostello</code></a>
in <a
href="https://redirect.github.com/domaindrivendev/Swashbuckle.AspNetCore/pull/2808">domaindrivendev/Swashbuckle.AspNetCore#2808</a></li>
<li>Bump redoc to 2.1.3 by <a
href="https://github.com/martincostello"><code>@​martincostello</code></a>
in <a
href="https://redirect.github.com/domaindrivendev/Swashbuckle.AspNetCore/pull/2807">domaindrivendev/Swashbuckle.AspNetCore#2807</a></li>
<li>Sort system usings first by <a
href="https://github.com/martincostello"><code>@​martincostello</code></a>
in <a
href="https://redirect.github.com/domaindrivendev/Swashbuckle.AspNetCore/pull/2790">domaindrivendev/Swashbuckle.AspNetCore#2790</a></li>
<li>Throw if unsupported HTTP method used by <a
href="https://github.com/martincostello"><code>@​martincostello</code></a>
in <a
href="https://redirect.github.com/domaindrivendev/Swashbuckle.AspNetCore/pull/2797">domaindrivendev/Swashbuckle.AspNetCore#2797</a></li>
<li>Fix configuration properties not being copied by <a
href="https://github.com/martincostello"><code>@​martincostello</code></a>
in <a
href="https://redirect.github.com/domaindrivendev/Swashbuckle.AspNetCore/pull/2796">domaindrivendev/Swashbuckle.AspNetCore#2796</a></li>
<li>Add security policy by <a
href="https://github.com/martincostello"><code>@​martincostello</code></a>
in <a
href="https://redirect.github.com/domaindrivendev/Swashbuckle.AspNetCore/pull/2785">domaindrivendev/Swashbuckle.AspNetCore#2785</a></li>
<li>Bump Microsoft.OpenApi by <a
href="https://github.com/martincostello"><code>@​martincostello</code></a>
in <a
href="https://redirect.github.com/domaindrivendev/Swashbuckle.AspNetCore/pull/2795">domaindrivendev/Swashbuckle.AspNetCore#2795</a></li>
<li>Update to Swagger UI v5 by <a
href="https://github.com/martincostello"><code>@​martincostello</code></a>
in <a
href="https://redirect.github.com/domaindrivendev/Swashbuckle.AspNetCore/pull/2806">domaindrivendev/Swashbuckle.AspNetCore#2806</a></li>
<li>Add customized document serialization support by <a
href="https://github.com/remcolam"><code>@​remcolam</code></a> in <a
href="https://redirect.github.com/domaindrivendev/Swashbuckle.AspNetCore/pull/2677">domaindrivendev/Swashbuckle.AspNetCore#2677</a></li>
<li>Added documentation for ISwaggerDocumentSerializer by <a
href="https://github.com/remcolam"><code>@​remcolam</code></a> in <a
href="https://redirect.github.com/domaindrivendev/Swashbuckle.AspNetCore/pull/2837">domaindrivendev/Swashbuckle.AspNetCore#2837</a></li>
<li>Fix flaky tests by locking on the statup type by <a
href="https://github.com/remcolam"><code>@​remcolam</code></a> in <a
href="https://redirect.github.com/domaindrivendev/Swashbuckle.AspNetCore/pull/2838">domaindrivendev/Swashbuckle.AspNetCore#2838</a></li>
<li><a
href="https://redirect.github.com/domaindrivendev/Swashbuckle.AspNetCore/issues/2765">#2765</a>
Allow Filter instance reuse by <a
href="https://github.com/remcolam"><code>@​remcolam</code></a> in <a
href="https://redirect.github.com/domaindrivendev/Swashbuckle.AspNetCore/pull/2839">domaindrivendev/Swashbuckle.AspNetCore#2839</a></li>
<li>Throw an error when a user uses FromForm attribute with IFormFile in
… by <a
href="https://github.com/nikunjbhargava"><code>@​nikunjbhargava</code></a>
in <a
href="https://redirect.github.com/domaindrivendev/Swashbuckle.AspNetCore/pull/2840">domaindrivendev/Swashbuckle.AspNetCore#2840</a></li>
<li>Filter illegal header fields by <a
href="https://github.com/keahpeters"><code>@​keahpeters</code></a> in <a
href="https://redirect.github.com/domaindrivendev/Swashbuckle.AspNetCore/pull/2842">domaindrivendev/Swashbuckle.AspNetCore#2842</a></li>
<li>Fix handling of FileResult's with content types by <a
href="https://github.com/IGx89"><code>@​IGx89</code></a> in <a
href="https://redirect.github.com/domaindrivendev/Swashbuckle.AspNetCore/pull/2841">domaindrivendev/Swashbuckle.AspNetCore#2841</a></li>
<li>Adding additional responses when 5XX errors are thrown. by <a
href="https://github.com/say25"><code>@​say25</code></a> in <a
href="https://redirect.github.com/domaindrivendev/Swashbuckle.AspNetCore/pull/2852">domaindrivendev/Swashbuckle.AspNetCore#2852</a></li>
<li>Fix that XML comment examples do not show up if the type is string
and the example contains quotation marks by <a
href="https://github.com/dldl-cmd"><code>@​dldl-cmd</code></a> in <a
href="https://redirect.github.com/domaindrivendev/Swashbuckle.AspNetCore/pull/2727">domaindrivendev/Swashbuckle.AspNetCore#2727</a></li>
<li>Exclude unused Swagger-UI files by <a
href="https://github.com/martincostello"><code>@​martincostello</code></a>
in <a
href="https://redirect.github.com/domaindrivendev/Swashbuckle.AspNetCore/pull/2851">domaindrivendev/Swashbuckle.AspNetCore#2851</a></li>
<li>Fix RequestBodyFilters not being deep copied by <a
href="https://github.com/martincostello"><code>@​martincostello</code></a>
in <a
href="https://redirect.github.com/domaindrivendev/Swashbuckle.AspNetCore/pull/2850">domaindrivendev/Swashbuckle.AspNetCore#2850</a></li>
<li>Avoid GitHub step summary file write conflicts by <a
href="https://github.com/martincostello"><code>@​martincostello</code></a>
in <a
href="https://redirect.github.com/domaindrivendev/Swashbuckle.AspNetCore/pull/2848">domaindrivendev/Swashbuckle.AspNetCore#2848</a></li>
<li>Extend built-in supported types by <a
href="https://github.com/martincostello"><code>@​martincostello</code></a>
in <a
href="https://redirect.github.com/domaindrivendev/Swashbuckle.AspNetCore/pull/2804">domaindrivendev/Swashbuckle.AspNetCore#2804</a></li>
<li>Update compatibility table by <a
href="https://github.com/martincostello"><code>@​martincostello</code></a>
in <a
href="https://redirect.github.com/domaindrivendev/Swashbuckle.AspNetCore/pull/2856">domaindrivendev/Swashbuckle.AspNetCore#2856</a></li>
<li>Add GitHub issue and PR templates by <a
href="https://github.com/martincostello"><code>@​martincostello</code></a>
in <a
href="https://redirect.github.com/domaindrivendev/Swashbuckle.AspNetCore/pull/2788">domaindrivendev/Swashbuckle.AspNetCore#2788</a></li>
<li>Fix stale permissions by <a
href="https://github.com/martincostello"><code>@​martincostello</code></a>
in <a
href="https://redirect.github.com/domaindrivendev/Swashbuckle.AspNetCore/pull/2855">domaindrivendev/Swashbuckle.AspNetCore#2855</a></li>
<li>Add .NET 8 support by <a
href="https://github.com/martincostello"><code>@​martincostello</code></a>
in <a
href="https://redirect.github.com/domaindrivendev/Swashbuckle.AspNetCore/pull/2799">domaindrivendev/Swashbuckle.AspNetCore#2799</a></li>
</ul>
<!-- raw HTML omitted -->
</blockquote>
<p>... (truncated)</p>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a
href="f1c38cdec2"><code>f1c38cd</code></a>
Bump version and fix stable versioning (<a
href="https://redirect.github.com/domaindrivendev/Swashbuckle.AspNetCore/issues/2875">#2875</a>)</li>
<li><a
href="e189b42647"><code>e189b42</code></a>
Bump swagger-ui-dist in /src/Swashbuckle.AspNetCore.SwaggerUI (<a
href="https://redirect.github.com/domaindrivendev/Swashbuckle.AspNetCore/issues/2874">#2874</a>)</li>
<li><a
href="86963e1d53"><code>86963e1</code></a>
Bump github/codeql-action from 3.25.4 to 3.25.5 (<a
href="https://redirect.github.com/domaindrivendev/Swashbuckle.AspNetCore/issues/2873">#2873</a>)</li>
<li><a
href="eb79926655"><code>eb79926</code></a>
Only attest packages</li>
<li><a
href="cad47fb41a"><code>cad47fb</code></a>
Add descriptions for more HTTP status codes (<a
href="https://redirect.github.com/domaindrivendev/Swashbuckle.AspNetCore/issues/2872">#2872</a>)</li>
<li><a
href="ae3078f2d9"><code>ae3078f</code></a>
Update .gitignore</li>
<li><a
href="6205d33ce0"><code>6205d33</code></a>
Artifact attestation</li>
<li><a
href="e4c6a7aad4"><code>e4c6a7a</code></a>
Fix stale permissions</li>
<li><a
href="12ad627a10"><code>12ad627</code></a>
Use simple using statement</li>
<li><a
href="7bb8e16ba5"><code>7bb8e16</code></a>
Bump Mvc.Testing and TestHost</li>
<li>Additional commits viewable in <a
href="https://github.com/domaindrivendev/Swashbuckle.AspNetCore/compare/v6.5.0...v6.6.1">compare
view</a></li>
</ul>
</details>
<br />


[![Dependabot compatibility
score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=Swashbuckle.AspNetCore&package-manager=nuget&previous-version=6.5.0&new-version=6.6.1)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores)

Dependabot will resolve any conflicts with this PR as long as you don't
alter it yourself. You can also trigger a rebase manually by commenting
`@dependabot rebase`.

[//]: # (dependabot-automerge-start)
[//]: # (dependabot-automerge-end)

---

<details>
<summary>Dependabot commands and options</summary>
<br />

You can trigger Dependabot actions by commenting on this PR:
- `@dependabot rebase` will rebase this PR
- `@dependabot recreate` will recreate this PR, overwriting any edits
that have been made to it
- `@dependabot merge` will merge this PR after your CI passes on it
- `@dependabot squash and merge` will squash and merge this PR after
your CI passes on it
- `@dependabot cancel merge` will cancel a previously requested merge
and block automerging
- `@dependabot reopen` will reopen this PR if it is closed
- `@dependabot close` will close this PR and stop Dependabot recreating
it. You can achieve the same result by closing it manually
- `@dependabot show <dependency name> ignore conditions` will show all
of the ignore conditions of the specified dependency
- `@dependabot ignore this major version` will close this PR and stop
Dependabot creating any more for this major version (unless you reopen
the PR or upgrade to it yourself)
- `@dependabot ignore this minor version` will close this PR and stop
Dependabot creating any more for this minor version (unless you reopen
the PR or upgrade to it yourself)
- `@dependabot ignore this dependency` will close this PR and stop
Dependabot creating any more for this dependency (unless you reopen the
PR or upgrade to it yourself)


</details>

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-05-20 23:32:38 +10:00
Michael Green
be1d53b3b1 Replaced unknown game image (#359)
Replaced unknown game image with an entirely new image
2024-05-18 22:26:30 +10:00
dependabot[bot]
e0cff36819 chore(deps): bump Microsoft.AspNetCore.Identity.UI from 8.0.4 to 8.0.5 (#358)
Bumps
[Microsoft.AspNetCore.Identity.UI](https://github.com/dotnet/aspnetcore)
from 8.0.4 to 8.0.5.
<details>
<summary>Release notes</summary>
<p><em>Sourced from <a
href="https://github.com/dotnet/aspnetcore/releases">Microsoft.AspNetCore.Identity.UI's
releases</a>.</em></p>
<blockquote>
<h2>.NET 8.0.5</h2>
<p><a
href="https://github.com/dotnet/core/releases/tag/v8.0.5">Release</a></p>
</blockquote>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a
href="c9e3996173"><code>c9e3996</code></a>
Merged PR 39207: [internal/release/8.0] Updated Version.Details.xml -
replace...</li>
<li><a
href="1681d9acf7"><code>1681d9a</code></a>
Merged PR 39048: [internal/release/8.0] Update dependencies from
dnceng/inter...</li>
<li><a
href="b5c1ba3391"><code>b5c1ba3</code></a>
Merged PR 39041: [internal/release/8.0] Update dependencies from
dnceng/inter...</li>
<li><a
href="582b2e31f2"><code>582b2e3</code></a>
Merged PR 38666: [internal/release/8.0] Update dependencies from
dnceng/inter...</li>
<li><a
href="4fb54205d2"><code>4fb5420</code></a>
Merge in 'release/8.0' changes</li>
<li><a
href="d65b2f0638"><code>d65b2f0</code></a>
[release/8.0] Update Wix version (<a
href="https://redirect.github.com/dotnet/aspnetcore/issues/55101">#55101</a>)</li>
<li><a
href="eac2e5a3d5"><code>eac2e5a</code></a>
Update dependencies from <a
href="https://github.com/dotnet/arcade">https://github.com/dotnet/arcade</a>
build 20240404.3 (#...</li>
<li><a
href="90f892827e"><code>90f8928</code></a>
Merged PR 38780: Fix Abort lock</li>
<li><a
href="1002bb7db9"><code>1002bb7</code></a>
Merge in 'release/8.0' changes</li>
<li><a
href="fa6339e421"><code>fa6339e</code></a>
Merge pull request <a
href="https://redirect.github.com/dotnet/aspnetcore/issues/55034">#55034</a>
from vseanreesermsft/internal-merge-8.0-2024-04-09-...</li>
<li>Additional commits viewable in <a
href="https://github.com/dotnet/aspnetcore/compare/v8.0.4...v8.0.5">compare
view</a></li>
</ul>
</details>
<br />


[![Dependabot compatibility
score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=Microsoft.AspNetCore.Identity.UI&package-manager=nuget&previous-version=8.0.4&new-version=8.0.5)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores)

Dependabot will resolve any conflicts with this PR as long as you don't
alter it yourself. You can also trigger a rebase manually by commenting
`@dependabot rebase`.

[//]: # (dependabot-automerge-start)
[//]: # (dependabot-automerge-end)

---

<details>
<summary>Dependabot commands and options</summary>
<br />

You can trigger Dependabot actions by commenting on this PR:
- `@dependabot rebase` will rebase this PR
- `@dependabot recreate` will recreate this PR, overwriting any edits
that have been made to it
- `@dependabot merge` will merge this PR after your CI passes on it
- `@dependabot squash and merge` will squash and merge this PR after
your CI passes on it
- `@dependabot cancel merge` will cancel a previously requested merge
and block automerging
- `@dependabot reopen` will reopen this PR if it is closed
- `@dependabot close` will close this PR and stop Dependabot recreating
it. You can achieve the same result by closing it manually
- `@dependabot show <dependency name> ignore conditions` will show all
of the ignore conditions of the specified dependency
- `@dependabot ignore this major version` will close this PR and stop
Dependabot creating any more for this major version (unless you reopen
the PR or upgrade to it yourself)
- `@dependabot ignore this minor version` will close this PR and stop
Dependabot creating any more for this minor version (unless you reopen
the PR or upgrade to it yourself)
- `@dependabot ignore this dependency` will close this PR and stop
Dependabot creating any more for this dependency (unless you reopen the
PR or upgrade to it yourself)


</details>

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-05-18 19:44:13 +10:00
dependabot[bot]
cea654692e chore(deps): bump Microsoft.AspNetCore.OpenApi from 8.0.4 to 8.0.5 (#357)
Bumps
[Microsoft.AspNetCore.OpenApi](https://github.com/dotnet/aspnetcore)
from 8.0.4 to 8.0.5.
<details>
<summary>Release notes</summary>
<p><em>Sourced from <a
href="https://github.com/dotnet/aspnetcore/releases">Microsoft.AspNetCore.OpenApi's
releases</a>.</em></p>
<blockquote>
<h2>.NET 8.0.5</h2>
<p><a
href="https://github.com/dotnet/core/releases/tag/v8.0.5">Release</a></p>
</blockquote>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a
href="c9e3996173"><code>c9e3996</code></a>
Merged PR 39207: [internal/release/8.0] Updated Version.Details.xml -
replace...</li>
<li><a
href="1681d9acf7"><code>1681d9a</code></a>
Merged PR 39048: [internal/release/8.0] Update dependencies from
dnceng/inter...</li>
<li><a
href="b5c1ba3391"><code>b5c1ba3</code></a>
Merged PR 39041: [internal/release/8.0] Update dependencies from
dnceng/inter...</li>
<li><a
href="582b2e31f2"><code>582b2e3</code></a>
Merged PR 38666: [internal/release/8.0] Update dependencies from
dnceng/inter...</li>
<li><a
href="4fb54205d2"><code>4fb5420</code></a>
Merge in 'release/8.0' changes</li>
<li><a
href="d65b2f0638"><code>d65b2f0</code></a>
[release/8.0] Update Wix version (<a
href="https://redirect.github.com/dotnet/aspnetcore/issues/55101">#55101</a>)</li>
<li><a
href="eac2e5a3d5"><code>eac2e5a</code></a>
Update dependencies from <a
href="https://github.com/dotnet/arcade">https://github.com/dotnet/arcade</a>
build 20240404.3 (#...</li>
<li><a
href="90f892827e"><code>90f8928</code></a>
Merged PR 38780: Fix Abort lock</li>
<li><a
href="1002bb7db9"><code>1002bb7</code></a>
Merge in 'release/8.0' changes</li>
<li><a
href="fa6339e421"><code>fa6339e</code></a>
Merge pull request <a
href="https://redirect.github.com/dotnet/aspnetcore/issues/55034">#55034</a>
from vseanreesermsft/internal-merge-8.0-2024-04-09-...</li>
<li>Additional commits viewable in <a
href="https://github.com/dotnet/aspnetcore/compare/v8.0.4...v8.0.5">compare
view</a></li>
</ul>
</details>
<br />


[![Dependabot compatibility
score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=Microsoft.AspNetCore.OpenApi&package-manager=nuget&previous-version=8.0.4&new-version=8.0.5)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores)

Dependabot will resolve any conflicts with this PR as long as you don't
alter it yourself. You can also trigger a rebase manually by commenting
`@dependabot rebase`.

[//]: # (dependabot-automerge-start)
[//]: # (dependabot-automerge-end)

---

<details>
<summary>Dependabot commands and options</summary>
<br />

You can trigger Dependabot actions by commenting on this PR:
- `@dependabot rebase` will rebase this PR
- `@dependabot recreate` will recreate this PR, overwriting any edits
that have been made to it
- `@dependabot merge` will merge this PR after your CI passes on it
- `@dependabot squash and merge` will squash and merge this PR after
your CI passes on it
- `@dependabot cancel merge` will cancel a previously requested merge
and block automerging
- `@dependabot reopen` will reopen this PR if it is closed
- `@dependabot close` will close this PR and stop Dependabot recreating
it. You can achieve the same result by closing it manually
- `@dependabot show <dependency name> ignore conditions` will show all
of the ignore conditions of the specified dependency
- `@dependabot ignore this major version` will close this PR and stop
Dependabot creating any more for this major version (unless you reopen
the PR or upgrade to it yourself)
- `@dependabot ignore this minor version` will close this PR and stop
Dependabot creating any more for this minor version (unless you reopen
the PR or upgrade to it yourself)
- `@dependabot ignore this dependency` will close this PR and stop
Dependabot creating any more for this dependency (unless you reopen the
PR or upgrade to it yourself)


</details>

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Michael Green <84688932+michael-j-green@users.noreply.github.com>
2024-05-18 19:42:03 +10:00
dependabot[bot]
c82ffb6c97 chore(deps): bump Magick.NET-Q8-AnyCPU from 13.7.0 to 13.8.0 (#356)
Bumps [Magick.NET-Q8-AnyCPU](https://github.com/dlemstra/Magick.NET)
from 13.7.0 to 13.8.0.
<details>
<summary>Release notes</summary>
<p><em>Sourced from <a
href="https://github.com/dlemstra/Magick.NET/releases">Magick.NET-Q8-AnyCPU's
releases</a>.</em></p>
<blockquote>
<h2>Magick.NET 13.8.0</h2>
<h3>Changes in Magick.NET:</h3>
<ul>
<li>fix: add guards for <code>MagickImage.MeanShift</code> by <a
href="https://github.com/Gounlaf"><code>@​Gounlaf</code></a> in <a
href="https://redirect.github.com/dlemstra/Magick.NET/pull/1612">dlemstra/Magick.NET#1612</a></li>
<li>Added <code>ChromaUpsampling</code> to the
<code>HeicReadDefines</code>.</li>
<li>typo: Update <code>IMorphologySettings.cs</code> by <a
href="https://github.com/Gounlaf"><code>@​Gounlaf</code></a> in <a
href="https://redirect.github.com/dlemstra/Magick.NET/pull/1617">dlemstra/Magick.NET#1617</a></li>
<li>fix: add guard for <code>MagickImage.Morphology</code> by <a
href="https://github.com/Gounlaf"><code>@​Gounlaf</code></a> in <a
href="https://redirect.github.com/dlemstra/Magick.NET/pull/1618">dlemstra/Magick.NET#1618</a></li>
<li>Added <code>NoIdentifier</code> to the
<code>PdfWriteDefines</code>.</li>
<li>Made <code>NearLossless</code> of the <code>WebPWriteDefines</code>
obsolete because this was removed from ImageMagick.</li>
<li>perf: use index access to <code>Dictionary</code> by <a
href="https://github.com/Gounlaf"><code>@​Gounlaf</code></a> in <a
href="https://redirect.github.com/dlemstra/Magick.NET/pull/1621">dlemstra/Magick.NET#1621</a></li>
<li>doc: missing Exception for <code>MagickImage.OilPaint</code> by <a
href="https://github.com/Gounlaf"><code>@​Gounlaf</code></a> in <a
href="https://redirect.github.com/dlemstra/Magick.NET/pull/1623">dlemstra/Magick.NET#1623</a></li>
<li>Remove typo in *<code>PerceptualHash</code> summaries. by <a
href="https://github.com/Gounlaf"><code>@​Gounlaf</code></a> in <a
href="https://redirect.github.com/dlemstra/Magick.NET/pull/1624">dlemstra/Magick.NET#1624</a></li>
<li>Revert breaking changes in enum order (<a
href="https://redirect.github.com/dlemstra/Magick.NET/issues/1627">#1627</a>).</li>
</ul>
<h3>Related changes in ImageMagick since the last release of
Magick.NET:</h3>
<ul>
<li>protect backslash write writing properties to MIFF (<a
href="https://redirect.github.com/ImageMagick/ImageMagick/issues/7270">ImageMagick/ImageMagick#7270</a>)</li>
<li>Use the new OpenEXRCore api that allows meta channel support when
reading exr files (only when OpenEXR is version 3.1.0 or higher)</li>
<li>Fix GIF ICC profile reading. (<a
href="https://redirect.github.com/ImageMagick/ImageMagick/issues/7281">ImageMagick/ImageMagick#7281</a>)</li>
</ul>
<h3>Library updates:</h3>
<ul>
<li>ImageMagick 7.1.1-32 (2024-05-05)</li>
<li>aom 3.9.0 (2024-04-23)</li>
<li>deflate 1.20.0 (2024-03-23)</li>
<li>openexr 3.2.4 (2024-03-26)</li>
<li>fribidi 1.0.14 (2024-04-25)</li>
<li>harfbuzz 8.4.0 (2024-03-29)</li>
<li>lzma 5.4.6 (2024-01-26)</li>
<li>webp 1.4.0 (2023-04-13)</li>
<li>xml 2.12.6 (2024-03-15)</li>
</ul>
<p><strong>Full Changelog</strong>: <a
href="https://github.com/dlemstra/Magick.NET/compare/13.7.0...13.8.0">https://github.com/dlemstra/Magick.NET/compare/13.7.0...13.8.0</a></p>
</blockquote>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a
href="5ac3d812d4"><code>5ac3d81</code></a>
Published Magick.NET 13.8.0</li>
<li><a
href="54aab7e935"><code>54aab7e</code></a>
Renamed Building.md to CONTRIBUTING.md</li>
<li><a
href="68af1ba3ec"><code>68af1ba</code></a>
Revert breaking changes in enum order (<a
href="https://redirect.github.com/dlemstra/Magick.NET/issues/1627">#1627</a>).</li>
<li><a
href="3ffd1b0fae"><code>3ffd1b0</code></a>
Updated docs.</li>
<li><a
href="ea8a81806d"><code>ea8a818</code></a>
Updated Magick.Native.</li>
<li><a
href="a5fc2ba84e"><code>a5fc2ba</code></a>
Update doc typo in *PerceptualHash (<a
href="https://redirect.github.com/dlemstra/Magick.NET/issues/1624">#1624</a>)</li>
<li><a
href="cdc21d1101"><code>cdc21d1</code></a>
Use NSubstitute instead of a test class.</li>
<li><a
href="e8072a4cfb"><code>e8072a4</code></a>
Code style change.</li>
<li><a
href="d958a6943c"><code>d958a69</code></a>
Document missing Exception for MagickImage.OilPaint (<a
href="https://redirect.github.com/dlemstra/Magick.NET/issues/1623">#1623</a>)</li>
<li><a
href="417f8b4597"><code>417f8b4</code></a>
Correct return type of channel methods in Moments and PerceptualHash (<a
href="https://redirect.github.com/dlemstra/Magick.NET/issues/1621">#1621</a>)</li>
<li>Additional commits viewable in <a
href="https://github.com/dlemstra/Magick.NET/compare/13.7.0...13.8.0">compare
view</a></li>
</ul>
</details>
<br />


[![Dependabot compatibility
score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=Magick.NET-Q8-AnyCPU&package-manager=nuget&previous-version=13.7.0&new-version=13.8.0)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores)

Dependabot will resolve any conflicts with this PR as long as you don't
alter it yourself. You can also trigger a rebase manually by commenting
`@dependabot rebase`.

[//]: # (dependabot-automerge-start)
[//]: # (dependabot-automerge-end)

---

<details>
<summary>Dependabot commands and options</summary>
<br />

You can trigger Dependabot actions by commenting on this PR:
- `@dependabot rebase` will rebase this PR
- `@dependabot recreate` will recreate this PR, overwriting any edits
that have been made to it
- `@dependabot merge` will merge this PR after your CI passes on it
- `@dependabot squash and merge` will squash and merge this PR after
your CI passes on it
- `@dependabot cancel merge` will cancel a previously requested merge
and block automerging
- `@dependabot reopen` will reopen this PR if it is closed
- `@dependabot close` will close this PR and stop Dependabot recreating
it. You can achieve the same result by closing it manually
- `@dependabot show <dependency name> ignore conditions` will show all
of the ignore conditions of the specified dependency
- `@dependabot ignore this major version` will close this PR and stop
Dependabot creating any more for this major version (unless you reopen
the PR or upgrade to it yourself)
- `@dependabot ignore this minor version` will close this PR and stop
Dependabot creating any more for this minor version (unless you reopen
the PR or upgrade to it yourself)
- `@dependabot ignore this dependency` will close this PR and stop
Dependabot creating any more for this dependency (unless you reopen the
PR or upgrade to it yourself)


</details>

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-05-18 19:41:36 +10:00
Michael Green
7400a8c040 Change license from GPL to AGPL (#355)
Closes #328
2024-05-18 19:34:08 +10:00
Michael Green
eda171efa1 Integrate new Hasheous client (#354)
The new Hasheous client has been integrated into Gaseous to take
advantage of the new updates to Hasheous that bring improved matching
and community contributions.
2024-05-18 15:05:25 +10:00
Michael Green
f881459c0b Merge 1.7.0 branch into main (#350) 2024-05-01 15:28:31 +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
Michael Green
59173d8ae5 Merge branch 'main' into branch-v1.7.0 2024-04-16 11:51:21 +10:00
Michael Green
178f70cb98 Performance improvements to infinite scrolling (#347) 2024-04-16 11:39:58 +10:00
Michael Green
080a823cda Merge 1.7.3 into main (#331) 2024-04-15 14:38:17 +10:00
Michael Green
60dbaf85a4 Merge branch 'main' into branch-v1.7.0 2024-04-15 14:35:32 +10:00
Michael Green
8a80274030 Integrate EJS 4.0.12 - Adds a new Master System core, and a new DS core (#344)
Closes #340
2024-04-15 14:23:11 +10:00
Michael Green
7e8679151b Integrate EJS 4.0.12 - Adds a new Master System core, and a new DS core (#343)
Closes #340
2024-04-15 13:05:18 +10:00
Michael Green
123239cf58 Merge branch 'main' into branch-v1.7.0 2024-03-12 00:48:56 +11:00
Michael Green
04419383aa When fixing matches, search doesn't return the correct values (#330)
Issue was due to the search result limit being too low. Increased the search result size to 100 returned objects.
2024-03-12 00:42:59 +11:00
Michael Green
0bef298abf SQL error when loading the library with the MySQL database server (#327)
Fixes #305
2024-03-10 13:58:42 +11:00
Michael Green
a4d581b369 Infinite Scrolling breaks clicking the Gaseous Games logo to return to library (#326)
Closes #316

Overhauled how infinite scrolling works. Should be more reliable now.
2024-03-10 01:47:51 +11:00
Michael Green
16cb0c89dc Hide unneeded buttons from the emulator (#324)
Closes #317

Removes the save and load file buttons - leaving the save and load state buttons
2024-03-06 22:38:32 +11:00
Michael Green
95b52c47dd New library dialog content overruns the edges of the dialog (#323)
Closes #303

This fix is a clean up fix - will review functionality for 1.8.0
2024-03-06 20:48:42 +11:00
Michael Green
b1056299b8 Ensure ROMs are decompressed before loading them into a media group (#321)
All compressed archives (zip, rar, and 7z) are decompressed when a media group is created.

Only files with an extension defined in the platform mapping are added to the M3U file.

Closes #307
2024-03-01 13:39:27 +11:00
Michael Green
14c5761959 Error on startup when reading value LastRun_BackgroundDatabaseUpgrade (#319)
Fixes #300
2024-02-26 16:17:26 +11:00
Michael Green
d7b3711be6 Update for PlatformMap.json to add missing core support (#318)
Adds cores to PlatformMap.json for platforms (closes #315):
* Family Computer = fceumm
* Super Famicom = snes9x
* TurboGrafx-16/PC Engine + Turbografx-16/PC Engine CD = mednafen_pce
* Neo Geo Pocket + Neo Geo Pocket Color = mednafen_ngp
* Wonderswan + Wonderswan Color = mednafen_wswan
* Nec PC-FX = mednafen_pcfx
2024-02-25 13:22:41 +11:00
Michael Green
111c501911 Fix for broken first run on fresh installs
* Fix for broken first run on fresh installs
2024-02-11 00:47:41 +11:00
76 changed files with 7369 additions and 3215 deletions

View File

@@ -1,6 +1,10 @@
FROM mcr.microsoft.com/devcontainers/dotnet:1-8.0-bookworm FROM mcr.microsoft.com/devcontainers/dotnet:1-8.0-bookworm
RUN apt-get update && apt-get install -y p7zip-full # update apt-get
RUN mkdir -p /workspace/gaseous-server/wwwroot/emulators/EmulatorJS RUN apt-get update
RUN wget https://cdn.emulatorjs.org/releases/4.0.11.7z
RUN 7z x -y -o/workspace/gaseous-server/wwwroot/emulators/EmulatorJS 4.0.11.7z # 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.1.1.7z
RUN 7z x -y -oout/wwwroot/emulators/EmulatorJS 4.1.1.7z

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,41 @@ 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:latest-embeddeddb
gaseousgames/gaseousserver:${{ github.ref_name}}-embeddeddb
ghcr.io/gaseous-project/gaseousserver:latest-embeddeddb
ghcr.io/gaseous-project/gaseousserver:${{ github.ref_name}}-embeddeddb

View File

@@ -27,10 +27,19 @@ jobs:
with: with:
username: ${{ secrets.DOCKERHUB_USERNAME }} username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_TOKEN }} password: ${{ secrets.DOCKERHUB_TOKEN }}
- name: Build and push - name: Build and push standard image
uses: docker/build-push-action@v5 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/test:latest tags: gaseousgames/test:latest
- 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/test:latest-embeddeddb

View File

@@ -1,85 +0,0 @@
# For most projects, this workflow file will not need changing; you simply need
# to commit it to your repository.
#
# You may wish to alter this file to override the set of languages analyzed,
# or to provide custom queries or build logic.
#
# ******** NOTE ********
# We have attempted to detect the languages in your repository. Please check
# the `language` matrix defined below to confirm you have the correct set of
# supported CodeQL languages.
#
name: "CodeQL"
on:
push:
branches: [ "main", "branch-v*.*.*" ]
pull_request:
# The branches below must be a subset of the branches above
branches: [ "main" ]
schedule:
- cron: '21 11 * * 2'
jobs:
analyze:
name: Analyze
# Runner size impacts CodeQL analysis time. To learn more, please see:
# - https://gh.io/recommended-hardware-resources-for-running-codeql
# - https://gh.io/supported-runners-and-hardware-resources
# - https://gh.io/using-larger-runners
# Consider using larger runners for possible analysis time improvements.
runs-on: ${{ (matrix.language == 'swift' && 'macos-latest') || 'ubuntu-latest' }}
timeout-minutes: ${{ (matrix.language == 'swift' && 120) || 360 }}
permissions:
actions: read
contents: read
security-events: write
strategy:
fail-fast: false
matrix:
language: [ 'csharp', 'javascript' ]
# CodeQL supports [ 'cpp', 'csharp', 'go', 'java', 'javascript', 'python', 'ruby', 'swift' ]
# Use only 'java' to analyze code written in Java, Kotlin or both
# Use only 'javascript' to analyze code written in JavaScript, TypeScript or both
# Learn more about CodeQL language support at https://aka.ms/codeql-docs/language-support
steps:
- name: Checkout repository
uses: actions/checkout@v3
- 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"
# Initializes the CodeQL tools for scanning.
- name: Initialize CodeQL
uses: github/codeql-action/init@v2
with:
languages: ${{ matrix.language }}
# If you wish to specify custom queries, you can do so here or in a config file.
# By default, queries listed here will override any specified in a config file.
# Prefix the list here with "+" to use these queries and those in the config file.
# For more details on CodeQL's query packs, refer to: https://docs.github.com/en/code-security/code-scanning/automatically-scanning-your-code-for-vulnerabilities-and-errors/configuring-code-scanning#using-queries-in-ql-packs
# queries: security-extended,security-and-quality
# Autobuild attempts to build any compiled languages (C/C++, C#, Go, Java, or Swift).
# If this step fails, then you should remove it and run the build manually (see below)
- name: Autobuild
uses: github/codeql-action/autobuild@v2
# Command-line programs to run using the OS shell.
# 📚 See https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#jobsjob_idstepsrun
# If the Autobuild fails above, remove it and uncomment the following three lines.
# modify them (or add more) to build your code if your project, please refer to the EXAMPLE below for guidance.
# - run: |
# echo "Run, Build Application using script"
# ./location_of_script_within_repo/buildscript.sh
- name: Perform CodeQL Analysis
uses: github/codeql-action/analyze@v2
with:
category: "/language:${{matrix.language}}"

3
.vscode/launch.json vendored
View File

@@ -24,7 +24,8 @@
}, },
"sourceFileMap": { "sourceFileMap": {
"/Views": "${workspaceFolder}/Views" "/Views": "${workspaceFolder}/Views"
} },
"enableStepFiltering": false
}, },
{ {
"name": ".NET Core Attach", "name": ".NET Core Attach",

View File

@@ -1,4 +1,4 @@

Microsoft Visual Studio Solution File, Format Version 12.00 Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio Version 16 # Visual Studio Version 16
VisualStudioVersion = 25.0.1704.4 VisualStudioVersion = 25.0.1704.4
@@ -27,30 +27,18 @@ Global
Release|Any CPU = Release|Any CPU Release|Any CPU = Release|Any CPU
EndGlobalSection EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution GlobalSection(ProjectConfigurationPlatforms) = postSolution
{08699C93-15CD-4E39-9053-D3C8056CE938}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{08699C93-15CD-4E39-9053-D3C8056CE938}.Debug|Any CPU.Build.0 = Debug|Any CPU
{08699C93-15CD-4E39-9053-D3C8056CE938}.Release|Any CPU.ActiveCfg = Release|Any CPU
{08699C93-15CD-4E39-9053-D3C8056CE938}.Release|Any CPU.Build.0 = Release|Any CPU
{FFCEC386-033F-4772-A45B-D33579F2E5EE}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{FFCEC386-033F-4772-A45B-D33579F2E5EE}.Debug|Any CPU.Build.0 = Debug|Any CPU
{FFCEC386-033F-4772-A45B-D33579F2E5EE}.Release|Any CPU.ActiveCfg = Release|Any CPU
{FFCEC386-033F-4772-A45B-D33579F2E5EE}.Release|Any CPU.Build.0 = Release|Any CPU
{A01D2EFF-C82E-473B-84D7-7C25E736F5D2}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {A01D2EFF-C82E-473B-84D7-7C25E736F5D2}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{A01D2EFF-C82E-473B-84D7-7C25E736F5D2}.Debug|Any CPU.Build.0 = Debug|Any CPU {A01D2EFF-C82E-473B-84D7-7C25E736F5D2}.Debug|Any CPU.Build.0 = Debug|Any CPU
{A01D2EFF-C82E-473B-84D7-7C25E736F5D2}.Release|Any CPU.ActiveCfg = Release|Any CPU {A01D2EFF-C82E-473B-84D7-7C25E736F5D2}.Release|Any CPU.ActiveCfg = Release|Any CPU
{A01D2EFF-C82E-473B-84D7-7C25E736F5D2}.Release|Any CPU.Build.0 = Release|Any CPU {A01D2EFF-C82E-473B-84D7-7C25E736F5D2}.Release|Any CPU.Build.0 = Release|Any CPU
{B07A4655-A003-416B-A790-ADAA5B548E1A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{B07A4655-A003-416B-A790-ADAA5B548E1A}.Debug|Any CPU.Build.0 = Debug|Any CPU
{B07A4655-A003-416B-A790-ADAA5B548E1A}.Release|Any CPU.ActiveCfg = Release|Any CPU
{B07A4655-A003-416B-A790-ADAA5B548E1A}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection EndGlobalSection
GlobalSection(SolutionProperties) = preSolution GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE HideSolutionNode = FALSE
EndGlobalSection EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {979BF092-AFBC-4F19-B55F-32E73959BD1A}
EndGlobalSection
GlobalSection(NestedProjects) = preSolution GlobalSection(NestedProjects) = preSolution
{F1A847C7-57BC-4DA9-8F83-CD060A7F5122} = {17FA6F12-8532-420C-9489-CB8FDE42137C} {F1A847C7-57BC-4DA9-8F83-CD060A7F5122} = {17FA6F12-8532-420C-9489-CB8FDE42137C}
EndGlobalSection EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {979BF092-AFBC-4F19-B55F-32E73959BD1A}
EndGlobalSection
EndGlobal EndGlobal

149
LICENSE
View File

@@ -1,5 +1,5 @@
GNU GENERAL PUBLIC LICENSE GNU AFFERO GENERAL PUBLIC LICENSE
Version 3, 29 June 2007 Version 3, 19 November 2007
Copyright (C) 2007 Free Software Foundation, Inc. <https://fsf.org/> Copyright (C) 2007 Free Software Foundation, Inc. <https://fsf.org/>
Everyone is permitted to copy and distribute verbatim copies Everyone is permitted to copy and distribute verbatim copies
@@ -7,17 +7,15 @@
Preamble Preamble
The GNU General Public License is a free, copyleft license for The GNU Affero General Public License is a free, copyleft license for
software and other kinds of works. software and other kinds of works, specifically designed to ensure
cooperation with the community in the case of network server software.
The licenses for most software and other practical works are designed The licenses for most software and other practical works are designed
to take away your freedom to share and change the works. By contrast, to take away your freedom to share and change the works. By contrast,
the GNU General Public License is intended to guarantee your freedom to our General Public Licenses are intended to guarantee your freedom to
share and change all versions of a program--to make sure it remains free share and change all versions of a program--to make sure it remains free
software for all its users. We, the Free Software Foundation, use the software for all its users.
GNU General Public License for most of our software; it applies also to
any other work released this way by its authors. You can apply it to
your programs, too.
When we speak of free software, we are referring to freedom, not When we speak of free software, we are referring to freedom, not
price. Our General Public Licenses are designed to make sure that you price. Our General Public Licenses are designed to make sure that you
@@ -26,44 +24,34 @@ them if you wish), that you receive source code or can get it if you
want it, that you can change the software or use pieces of it in new want it, that you can change the software or use pieces of it in new
free programs, and that you know you can do these things. free programs, and that you know you can do these things.
To protect your rights, we need to prevent others from denying you Developers that use our General Public Licenses protect your rights
these rights or asking you to surrender the rights. Therefore, you have with two steps: (1) assert copyright on the software, and (2) offer
certain responsibilities if you distribute copies of the software, or if you this License which gives you legal permission to copy, distribute
you modify it: responsibilities to respect the freedom of others. and/or modify the software.
For example, if you distribute copies of such a program, whether A secondary benefit of defending all users' freedom is that
gratis or for a fee, you must pass on to the recipients the same improvements made in alternate versions of the program, if they
freedoms that you received. You must make sure that they, too, receive receive widespread use, become available for other developers to
or can get the source code. And you must show them these terms so they incorporate. Many developers of free software are heartened and
know their rights. encouraged by the resulting cooperation. However, in the case of
software used on network servers, this result may fail to come about.
The GNU General Public License permits making a modified version and
letting the public access it on a server without ever releasing its
source code to the public.
Developers that use the GNU GPL protect your rights with two steps: The GNU Affero General Public License is designed specifically to
(1) assert copyright on the software, and (2) offer you this License ensure that, in such cases, the modified source code becomes available
giving you legal permission to copy, distribute and/or modify it. to the community. It requires the operator of a network server to
provide the source code of the modified version running there to the
users of that server. Therefore, public use of a modified version, on
a publicly accessible server, gives the public access to the source
code of the modified version.
For the developers' and authors' protection, the GPL clearly explains An older license, called the Affero General Public License and
that there is no warranty for this free software. For both users' and published by Affero, was designed to accomplish similar goals. This is
authors' sake, the GPL requires that modified versions be marked as a different license, not a version of the Affero GPL, but Affero has
changed, so that their problems will not be attributed erroneously to released a new version of the Affero GPL which permits relicensing under
authors of previous versions. this license.
Some devices are designed to deny users access to install or run
modified versions of the software inside them, although the manufacturer
can do so. This is fundamentally incompatible with the aim of
protecting users' freedom to change the software. The systematic
pattern of such abuse occurs in the area of products for individuals to
use, which is precisely where it is most unacceptable. Therefore, we
have designed this version of the GPL to prohibit the practice for those
products. If such problems arise substantially in other domains, we
stand ready to extend this provision to those domains in future versions
of the GPL, as needed to protect the freedom of users.
Finally, every program is threatened constantly by software patents.
States should not allow patents to restrict development and use of
software on general-purpose computers, but in those that do, we wish to
avoid the special danger that patents applied to a free program could
make it effectively proprietary. To prevent this, the GPL assures that
patents cannot be used to render the program non-free.
The precise terms and conditions for copying, distribution and The precise terms and conditions for copying, distribution and
modification follow. modification follow.
@@ -72,7 +60,7 @@ modification follow.
0. Definitions. 0. Definitions.
"This License" refers to version 3 of the GNU General Public License. "This License" refers to version 3 of the GNU Affero General Public License.
"Copyright" also means copyright-like laws that apply to other kinds of "Copyright" also means copyright-like laws that apply to other kinds of
works, such as semiconductor masks. works, such as semiconductor masks.
@@ -549,35 +537,45 @@ to collect a royalty for further conveying from those to whom you convey
the Program, the only way you could satisfy both those terms and this the Program, the only way you could satisfy both those terms and this
License would be to refrain entirely from conveying the Program. License would be to refrain entirely from conveying the Program.
13. Use with the GNU Affero General Public License. 13. Remote Network Interaction; Use with the GNU General Public License.
Notwithstanding any other provision of this License, if you modify the
Program, your modified version must prominently offer all users
interacting with it remotely through a computer network (if your version
supports such interaction) an opportunity to receive the Corresponding
Source of your version by providing access to the Corresponding Source
from a network server at no charge, through some standard or customary
means of facilitating copying of software. This Corresponding Source
shall include the Corresponding Source for any work covered by version 3
of the GNU General Public License that is incorporated pursuant to the
following paragraph.
Notwithstanding any other provision of this License, you have Notwithstanding any other provision of this License, you have
permission to link or combine any covered work with a work licensed permission to link or combine any covered work with a work licensed
under version 3 of the GNU Affero General Public License into a single under version 3 of the GNU General Public License into a single
combined work, and to convey the resulting work. The terms of this combined work, and to convey the resulting work. The terms of this
License will continue to apply to the part which is the covered work, License will continue to apply to the part which is the covered work,
but the special requirements of the GNU Affero General Public License, but the work with which it is combined will remain governed by version
section 13, concerning interaction through a network will apply to the 3 of the GNU General Public License.
combination as such.
14. Revised Versions of this License. 14. Revised Versions of this License.
The Free Software Foundation may publish revised and/or new versions of The Free Software Foundation may publish revised and/or new versions of
the GNU General Public License from time to time. Such new versions will the GNU Affero General Public License from time to time. Such new versions
be similar in spirit to the present version, but may differ in detail to will be similar in spirit to the present version, but may differ in detail to
address new problems or concerns. address new problems or concerns.
Each version is given a distinguishing version number. If the Each version is given a distinguishing version number. If the
Program specifies that a certain numbered version of the GNU General Program specifies that a certain numbered version of the GNU Affero General
Public License "or any later version" applies to it, you have the Public License "or any later version" applies to it, you have the
option of following the terms and conditions either of that numbered option of following the terms and conditions either of that numbered
version or of any later version published by the Free Software version or of any later version published by the Free Software
Foundation. If the Program does not specify a version number of the Foundation. If the Program does not specify a version number of the
GNU General Public License, you may choose any version ever published GNU Affero General Public License, you may choose any version ever published
by the Free Software Foundation. by the Free Software Foundation.
If the Program specifies that a proxy can decide which future If the Program specifies that a proxy can decide which future
versions of the GNU General Public License can be used, that proxy's versions of the GNU Affero General Public License can be used, that proxy's
public statement of acceptance of a version permanently authorizes you public statement of acceptance of a version permanently authorizes you
to choose that version for the Program. to choose that version for the Program.
@@ -631,44 +629,33 @@ to attach them to the start of each source file to most effectively
state the exclusion of warranty; and each file should have at least state the exclusion of warranty; and each file should have at least
the "copyright" line and a pointer to where the full notice is found. the "copyright" line and a pointer to where the full notice is found.
Gaseous <one line to give the program's name and a brief idea of what it does.>
Copyright (C) 2023 Gaseous Copyright (C) <year> <name of author>
This program is free software: you can redistribute it and/or modify This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by it under the terms of the GNU Affero General Public License as published
the Free Software Foundation, either version 3 of the License, or by the Free Software Foundation, either version 3 of the License, or
(at your option) any later version. (at your option) any later version.
This program is distributed in the hope that it will be useful, This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details. GNU Affero General Public License for more details.
You should have received a copy of the GNU General Public License You should have received a copy of the GNU Affero General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>. along with this program. If not, see <https://www.gnu.org/licenses/>.
Also add information on how to contact you by electronic and paper mail. Also add information on how to contact you by electronic and paper mail.
If the program does terminal interaction, make it output a short If your software can interact with users remotely through a computer
notice like this when it starts in an interactive mode: network, you should also make sure that it provides a way for users to
get its source. For example, if your program is a web application, its
<program> Copyright (C) 2023 Gaseous interface could display a "Source" link that leads users to an archive
This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. of the code. There are many ways you could offer source, and different
This is free software, and you are welcome to redistribute it solutions will be better for different programs; see section 13 for the
under certain conditions; type `show c' for details. specific requirements.
The hypothetical commands `show w' and `show c' should show the appropriate
parts of the General Public License. Of course, your program's commands
might be different; for a GUI interface, you would use an "about box".
You should also get your employer (if you work as a programmer) or school, You should also get your employer (if you work as a programmer) or school,
if any, to sign a "copyright disclaimer" for the program, if necessary. if any, to sign a "copyright disclaimer" for the program, if necessary.
For more information on this, and how to apply and follow the GNU GPL, see For more information on this, and how to apply and follow the GNU AGPL, see
<https://www.gnu.org/licenses/>. <https://www.gnu.org/licenses/>.
The GNU General Public License does not permit incorporating your program
into proprietary programs. If your program is a subroutine library, you
may consider it more useful to permit linking proprietary applications with
the library. If this is what you want to do, use the GNU Lesser General
Public License instead of this License. But first, please read
<https://www.gnu.org/licenses/why-not-lgpl.html>.

View File

@@ -8,21 +8,32 @@ 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
# update apt-get
RUN apt-get update
# download and unzip EmulatorJS from CDN # download and unzip EmulatorJS from CDN
RUN apt-get update && apt-get install -y p7zip-full RUN apt-get install -y p7zip-full
RUN mkdir -p out/wwwroot/emulators/EmulatorJS RUN mkdir -p out/wwwroot/emulators/EmulatorJS
RUN wget https://cdn.emulatorjs.org/releases/4.0.11.7z RUN wget https://cdn.emulatorjs.org/releases/4.1.1.7z
RUN 7z x -y -oout/wwwroot/emulators/EmulatorJS 4.0.11.7z RUN 7z x -y -oout/wwwroot/emulators/EmulatorJS 4.1.1.7z
# 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 .
# Configure healthcheck
HEALTHCHECK --interval=30s --timeout=5s --start-period=60s --retries=3 CMD curl --fail http://localhost:80/healthCheck || exit 1
# start gaseous-server
ENTRYPOINT ["dotnet", "gaseous-server.dll"] ENTRYPOINT ["dotnet", "gaseous-server.dll"]

View File

@@ -0,0 +1,76 @@
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
# 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.1.1.7z
RUN 7z x -y -oout/wwwroot/emulators/EmulatorJS 4.1.1.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
ARG PUID=1000
ARG PGID=1000
ARG dbhost=localhost
ARG dbuser=root
ARG dbpass=gaseous
ARG MARIADB_ROOT_PASSWORD=$dbpass
ENV PUID=${PUID}
ENV PGID=${PGID}
ENV dbhost=${dbhost}
ENV dbuser=${dbuser}
ENV dbpass=${dbpass}
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
RUN mkdir -p /var/run/supervisord
RUN mkdir -p /var/log/supervisord
# clean up apt-get
RUN apt-get clean && rm -rf /var/lib/apt/lists
# copy entrypoint
COPY ../build/entrypoint.sh /usr/sbin/entrypoint.sh
RUN chmod +x /usr/sbin/entrypoint.sh
# volumes
VOLUME /home/gaseous/.gaseous-server /var/lib/mysql
# start services
ENTRYPOINT [ "/usr/sbin/entrypoint.sh" ]

13
build/entrypoint.sh Normal file
View File

@@ -0,0 +1,13 @@
#!/bin/sh
# create the user
echo "Creating user gaseous with UID ${PUID} and GID ${PGID}"
groupadd -g ${PGID} gaseous
useradd -u ${PUID} -g ${PGID} -m gaseous -d /home/gaseous -G sudo
usermod -p "*" gaseous
mkdir -p /home/gaseous/.gaseous-server /var/lib/mysql /var/log/mariadb /run/mysqld
chown -R ${PUID} /App /home/gaseous/.gaseous-server /var/lib/mysql /var/log/mariadb /run/mysqld
chgrp -R ${PGID} /App /home/gaseous/.gaseous-server /var/lib/mysql /var/log/mariadb /run/mysqld
# Start supervisord and services
/usr/bin/supervisord -c /etc/supervisor/conf.d/supervisord.conf

20
build/mariadb.sh Normal file
View File

@@ -0,0 +1,20 @@
#!/bin/sh
# install the database
/usr/bin/mariadb-install-db --datadir=/var/lib/mysql --user=gaseous
# start the database server without network or grant tables
/usr/sbin/mariadbd --datadir=/var/lib/mysql --skip-grant-tables --skip-networking &
# wait for the server to start
sleep 5
# change the root password
mariadb -u root -e "FLUSH PRIVILEGES; ALTER USER 'root'@'localhost' IDENTIFIED BY '$MARIADB_ROOT_PASSWORD'; ALTER USER 'gaseous'@'localhost' IDENTIFIED BY '$MARIADB_ROOT_PASSWORD'; FLUSH PRIVILEGES; SHUTDOWN;"
# stop the server
sleep 5
killall mariadbd
# start the server normally
/usr/sbin/mariadbd --datadir=/var/lib/mysql

37
build/supervisord.conf Normal file
View File

@@ -0,0 +1,37 @@
[supervisord]
user=root
nodaemon=true
logfile=/var/log/supervisord/supervisord.log
logfile_maxbytes=50
logfile_backups=5
pidfile=/var/run/supervisord/supervisord.pid
loglevel = info
[unix_http_server]
file=/var/run/supervisord/supervisor.sock
chmod=0700
[rpcinterface:supervisor]
supervisor.rpcinterface_factory = supervisor.rpcinterface:make_main_rpcinterface
[supervisorctl]
serverurl=unix:///var/run/supervisord/supervisor.sock
[program:mariadb]
user=gaseous
command=bash -c "/usr/sbin/start-mariadb.sh"
autostart=true
autorestart=true
redirect_stderr=true
stdout_logfile=/dev/fd/1
stdout_logfile_maxbytes=0
[program:gaseous-server]
user=gaseous
command=dotnet /App/gaseous-server.dll
environment=HOME="/home/gaseous",USER="gaseous"
autostart=true
autorestart=true
redirect_stderr=true
stdout_logfile=/dev/fd/1
stdout_logfile_maxbytes=0

View File

@@ -1,5 +1,6 @@
using System.Collections.Concurrent; using System.Collections.Concurrent;
using System.ComponentModel; using System.ComponentModel;
using System.Data;
using System.IO.Compression; using System.IO.Compression;
using System.Reflection; using System.Reflection;
using System.Security.Cryptography; using System.Security.Cryptography;
@@ -19,7 +20,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 +29,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 +43,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 +89,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 +159,74 @@ namespace gaseous_server.Classes
return defaultValue; return defaultValue;
} }
} }
}
public static int GetLookupByCode(LookupTypes LookupType, string Code)
{
Database db = new Database(Database.databaseType.MySql, Config.DatabaseConfiguration.ConnectionString);
string sql = "SELECT Id FROM " + LookupType.ToString() + " WHERE Code = @code";
Dictionary<string, object> dbDict = new Dictionary<string, object>{
{ "code", Code }
};
DataTable data = db.ExecuteCMD(sql, dbDict);
if (data.Rows.Count == 0)
{
return -1;
}
else
{
return (int)data.Rows[0]["Id"];
}
}
public static int GetLookupByValue(LookupTypes LookupType, string Value)
{
Database db = new Database(Database.databaseType.MySql, Config.DatabaseConfiguration.ConnectionString);
string sql = "SELECT Id FROM " + LookupType.ToString() + " WHERE Value = @value";
Dictionary<string, object> dbDict = new Dictionary<string, object>{
{ "value", Value }
};
DataTable data = db.ExecuteCMD(sql, dbDict);
if (data.Rows.Count == 0)
{
return -1;
}
else
{
return (int)data.Rows[0]["Id"];
}
}
public enum LookupTypes
{
Country,
Language
}
}
/// <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

@@ -80,7 +80,8 @@ namespace gaseous_server.Classes
get get
{ {
string logPath = Path.Combine(ConfigurationPath, "Logs"); string logPath = Path.Combine(ConfigurationPath, "Logs");
if (!Directory.Exists(logPath)) { if (!Directory.Exists(logPath))
{
Directory.CreateDirectory(logPath); Directory.CreateDirectory(logPath);
} }
return logPath; return logPath;
@@ -92,7 +93,7 @@ namespace gaseous_server.Classes
get get
{ {
string logFileExtension = "txt"; string logFileExtension = "txt";
string logPathName = Path.Combine(LogPath, "Server Log " + DateTime.Now.ToUniversalTime().ToString("yyyyMMdd") + "." + logFileExtension); string logPathName = Path.Combine(LogPath, "Server Log " + DateTime.Now.ToUniversalTime().ToString("yyyyMMdd") + "." + logFileExtension);
return logPathName; return logPathName;
} }
@@ -131,7 +132,7 @@ namespace gaseous_server.Classes
_config.DatabaseConfiguration.DatabaseName = (string)Common.GetEnvVar("dbname", _config.DatabaseConfiguration.DatabaseName); _config.DatabaseConfiguration.DatabaseName = (string)Common.GetEnvVar("dbname", _config.DatabaseConfiguration.DatabaseName);
_config.DatabaseConfiguration.Port = int.Parse((string)Common.GetEnvVar("dbport", _config.DatabaseConfiguration.Port.ToString())); _config.DatabaseConfiguration.Port = int.Parse((string)Common.GetEnvVar("dbport", _config.DatabaseConfiguration.Port.ToString()));
_config.MetadataConfiguration.MetadataSource = (HasheousClient.Models.MetadataModel.MetadataSources)Enum.Parse(typeof(HasheousClient.Models.MetadataModel.MetadataSources), (string)Common.GetEnvVar("metadatasource", _config.MetadataConfiguration.MetadataSource.ToString())); _config.MetadataConfiguration.MetadataSource = (HasheousClient.Models.MetadataModel.MetadataSources)Enum.Parse(typeof(HasheousClient.Models.MetadataModel.MetadataSources), (string)Common.GetEnvVar("metadatasource", _config.MetadataConfiguration.MetadataSource.ToString()));
_config.MetadataConfiguration.SignatureSource = (HasheousClient.Models.MetadataModel.SignatureSources)Enum.Parse(typeof(HasheousClient.Models.MetadataModel.SignatureSources), (string)Common.GetEnvVar("signaturesource", _config.MetadataConfiguration.SignatureSource.ToString()));; _config.MetadataConfiguration.SignatureSource = (HasheousClient.Models.MetadataModel.SignatureSources)Enum.Parse(typeof(HasheousClient.Models.MetadataModel.SignatureSources), (string)Common.GetEnvVar("signaturesource", _config.MetadataConfiguration.SignatureSource.ToString())); ;
_config.MetadataConfiguration.MaxLibraryScanWorkers = int.Parse((string)Common.GetEnvVar("maxlibraryscanworkers", _config.MetadataConfiguration.MaxLibraryScanWorkers.ToString())); _config.MetadataConfiguration.MaxLibraryScanWorkers = int.Parse((string)Common.GetEnvVar("maxlibraryscanworkers", _config.MetadataConfiguration.MaxLibraryScanWorkers.ToString()));
_config.MetadataConfiguration.HasheousHost = (string)Common.GetEnvVar("hasheoushost", _config.MetadataConfiguration.HasheousHost); _config.MetadataConfiguration.HasheousHost = (string)Common.GetEnvVar("hasheoushost", _config.MetadataConfiguration.HasheousHost);
_config.IGDBConfiguration.ClientId = (string)Common.GetEnvVar("igdbclientid", _config.IGDBConfiguration.ClientId); _config.IGDBConfiguration.ClientId = (string)Common.GetEnvVar("igdbclientid", _config.IGDBConfiguration.ClientId);
@@ -196,13 +197,18 @@ namespace gaseous_server.Classes
{ {
string SettingName = (string)dataRow["Setting"]; string SettingName = (string)dataRow["Setting"];
if (SettingName.StartsWith("LastRun_"))
{
Console.WriteLine("Break");
}
if (AppSettings.ContainsKey(SettingName)) if (AppSettings.ContainsKey(SettingName))
{ {
AppSettings.Remove(SettingName); AppSettings.Remove(SettingName);
} }
Logging.Log(Logging.LogType.Information, "Load Settings", "Loading setting " + SettingName + " from database"); Logging.Log(Logging.LogType.Information, "Load Settings", "Loading setting " + SettingName + " from database");
try try
{ {
if (Database.schema_version >= 1016) if (Database.schema_version >= 1016)
@@ -270,7 +276,7 @@ namespace gaseous_server.Classes
if (Database.schema_version >= 1016) if (Database.schema_version >= 1016)
{ {
sql = "SELECT Value, ValueDate FROM Settings WHERE Setting = @SettingName"; sql = "SELECT Value, ValueDate FROM Settings WHERE Setting = @SettingName";
dbResponse = db.ExecuteCMD(sql, dbDict); dbResponse = db.ExecuteCMD(sql, dbDict);
Type type = typeof(T); Type type = typeof(T);
if (dbResponse.Rows.Count == 0) if (dbResponse.Rows.Count == 0)
@@ -296,7 +302,7 @@ namespace gaseous_server.Classes
else else
{ {
sql = "SELECT Value FROM Settings WHERE Setting = @SettingName"; sql = "SELECT Value FROM Settings WHERE Setting = @SettingName";
dbResponse = db.ExecuteCMD(sql, dbDict); dbResponse = db.ExecuteCMD(sql, dbDict);
Type type = typeof(T); Type type = typeof(T);
if (dbResponse.Rows.Count == 0) if (dbResponse.Rows.Count == 0)
@@ -350,30 +356,43 @@ namespace gaseous_server.Classes
Database db = new Database(Database.databaseType.MySql, Config.DatabaseConfiguration.ConnectionString); Database db = new Database(Database.databaseType.MySql, Config.DatabaseConfiguration.ConnectionString);
string sql; string sql;
Dictionary<string, object> dbDict; Dictionary<string, object> dbDict;
if (Database.schema_version >= 1016) if (Database.schema_version >= 1016)
{ {
sql = "REPLACE INTO Settings (Setting, ValueType, Value, ValueDate) VALUES (@SettingName, @ValueType, @Value, @ValueDate)"; sql = "REPLACE INTO Settings (Setting, ValueType, Value, ValueDate) VALUES (@SettingName, @ValueType, @Value, @ValueDate)";
Type type = typeof(T); Type type = typeof(T);
if (type.ToString() == "System.DateTime")
switch (type.ToString())
{ {
dbDict = new Dictionary<string, object> case "System.DateTime":
{ dbDict = new Dictionary<string, object>
{ "SettingName", SettingName }, {
{ "ValueType", 1 }, { "SettingName", SettingName },
{ "Value", null }, { "ValueType", 1 },
{ "ValueDate", Value } { "Value", null },
}; { "ValueDate", Value }
} };
else break;
{
dbDict = new Dictionary<string, object> case "System.Collections.Generic.List`1[gaseous_server.Classes.Metadata.Games+SearchType]":
{ dbDict = new Dictionary<string, object>
{ "SettingName", SettingName }, {
{ "ValueType", 0 }, { "SettingName", SettingName },
{ "Value", Value }, { "ValueType", 2 },
{ "ValueDate", null } { "Value", JsonConvert.SerializeObject(Value) },
}; { "ValueDate", null }
};
break;
default:
dbDict = new Dictionary<string, object>
{
{ "SettingName", SettingName },
{ "ValueType", 0 },
{ "Value", Value },
{ "ValueDate", null }
};
break;
} }
} }
else else
@@ -422,7 +441,8 @@ namespace gaseous_server.Classes
public class Database public class Database
{ {
private static string _DefaultHostName { private static string _DefaultHostName
{
get get
{ {
if (!String.IsNullOrEmpty(Environment.GetEnvironmentVariable("dbhost"))) if (!String.IsNullOrEmpty(Environment.GetEnvironmentVariable("dbhost")))

View File

@@ -9,8 +9,21 @@ namespace gaseous_server.Classes
{ {
public class Database public class Database
{ {
public static int schema_version = 0; private static int _schema_version { get; set; } = 0;
public static int schema_version
{
get
{
//Logging.Log(Logging.LogType.Information, "Database Schema", "Schema version is " + _schema_version);
return _schema_version;
}
set
{
//Logging.Log(Logging.LogType.Information, "Database Schema", "Setting schema version " + _schema_version);
_schema_version = value;
}
}
public Database() public Database()
{ {
@@ -80,7 +93,16 @@ namespace gaseous_server.Classes
ExecuteCMD(sql, dbDict); ExecuteCMD(sql, dbDict);
} }
for (int i = 1000; i < 10000; i++) sql = "SELECT schema_version FROM schema_version;";
dbDict = new Dictionary<string, object>();
DataTable SchemaVersion = ExecuteCMD(sql, dbDict);
int OuterSchemaVer = (int)SchemaVersion.Rows[0][0];
if (OuterSchemaVer == 0)
{
OuterSchemaVer = 1000;
}
for (int i = OuterSchemaVer; i < 10000; i++)
{ {
string resourceName = "gaseous_server.Support.Database.MySQL.gaseous-" + i + ".sql"; string resourceName = "gaseous_server.Support.Database.MySQL.gaseous-" + i + ".sql";
string dbScript = ""; string dbScript = "";
@@ -96,7 +118,7 @@ namespace gaseous_server.Classes
// apply script // apply script
sql = "SELECT schema_version FROM schema_version;"; sql = "SELECT schema_version FROM schema_version;";
dbDict = new Dictionary<string, object>(); dbDict = new Dictionary<string, object>();
DataTable SchemaVersion = ExecuteCMD(sql, dbDict); SchemaVersion = ExecuteCMD(sql, dbDict);
if (SchemaVersion.Rows.Count == 0) if (SchemaVersion.Rows.Count == 0)
{ {
// something is broken here... where's the table? // something is broken here... where's the table?
@@ -107,6 +129,8 @@ namespace gaseous_server.Classes
{ {
int SchemaVer = (int)SchemaVersion.Rows[0][0]; int SchemaVer = (int)SchemaVersion.Rows[0][0];
Logging.Log(Logging.LogType.Information, "Database", "Schema version is " + SchemaVer); Logging.Log(Logging.LogType.Information, "Database", "Schema version is " + SchemaVer);
// update schema version variable
Database.schema_version = SchemaVer;
if (SchemaVer < i) if (SchemaVer < i)
{ {
try try

View File

@@ -4,11 +4,11 @@ using System.Reflection;
namespace gaseous_server.Classes namespace gaseous_server.Classes
{ {
public static class DatabaseMigration public static class DatabaseMigration
{ {
public static List<int> BackgroundUpgradeTargetSchemaVersions = new List<int>(); public static List<int> BackgroundUpgradeTargetSchemaVersions = new List<int>();
public static void PreUpgradeScript(int TargetSchemaVersion, Database.databaseType? DatabaseType) public static void PreUpgradeScript(int TargetSchemaVersion, Database.databaseType? DatabaseType)
{ {
// load resources // load resources
var assembly = Assembly.GetExecutingAssembly(); var assembly = Assembly.GetExecutingAssembly();
@@ -20,14 +20,14 @@ namespace gaseous_server.Classes
Logging.Log(Logging.LogType.Information, "Database", "Checking for pre-upgrade for schema version " + TargetSchemaVersion); Logging.Log(Logging.LogType.Information, "Database", "Checking for pre-upgrade for schema version " + TargetSchemaVersion);
switch(DatabaseType) switch (DatabaseType)
{ {
case Database.databaseType.MySql: case Database.databaseType.MySql:
switch (TargetSchemaVersion) switch (TargetSchemaVersion)
{ {
case 1005: case 1005:
Logging.Log(Logging.LogType.Information, "Database", "Running pre-upgrade for schema version " + TargetSchemaVersion); Logging.Log(Logging.LogType.Information, "Database", "Running pre-upgrade for schema version " + TargetSchemaVersion);
// there was a mistake at dbschema version 1004-1005 // there was a mistake at dbschema version 1004-1005
// the first preview release of v1.7 reused dbschema version 1004 // the first preview release of v1.7 reused dbschema version 1004
// if table "Relation_Game_AgeRatings" exists - then we need to apply the gaseous-fix-1005.sql script before applying the standard 1005 script // if table "Relation_Game_AgeRatings" exists - then we need to apply the gaseous-fix-1005.sql script before applying the standard 1005 script
@@ -62,14 +62,16 @@ namespace gaseous_server.Classes
} }
} }
public static void PostUpgradeScript(int TargetSchemaVersion, Database.databaseType? DatabaseType) public static void PostUpgradeScript(int TargetSchemaVersion, Database.databaseType? DatabaseType)
{ {
var assembly = Assembly.GetExecutingAssembly();
Database db = new Database(Database.databaseType.MySql, Config.DatabaseConfiguration.ConnectionString); Database db = new Database(Database.databaseType.MySql, Config.DatabaseConfiguration.ConnectionString);
string sql = ""; string sql = "";
Dictionary<string, object> dbDict = new Dictionary<string, object>(); Dictionary<string, object> dbDict = new Dictionary<string, object>();
DataTable data; DataTable data;
switch(DatabaseType) switch (DatabaseType)
{ {
case Database.databaseType.MySql: case Database.databaseType.MySql:
switch (TargetSchemaVersion) switch (TargetSchemaVersion)
@@ -78,7 +80,7 @@ namespace gaseous_server.Classes
// this is a safe background task // this is a safe background task
BackgroundUpgradeTargetSchemaVersions.Add(1002); BackgroundUpgradeTargetSchemaVersions.Add(1002);
break; break;
case 1004: case 1004:
// needs to run on start up // needs to run on start up
@@ -103,6 +105,48 @@ namespace gaseous_server.Classes
sql = "DELETE FROM Settings WHERE Setting LIKE 'LastRun_%';"; sql = "DELETE FROM Settings WHERE Setting LIKE 'LastRun_%';";
db.ExecuteNonQuery(sql); db.ExecuteNonQuery(sql);
break; break;
case 1023:
// load country list
Logging.Log(Logging.LogType.Information, "Database Upgrade", "Adding country look up table contents");
string countryResourceName = "gaseous_server.Support.Country.txt";
using (Stream stream = assembly.GetManifestResourceStream(countryResourceName))
using (StreamReader reader = new StreamReader(stream))
{
do
{
string[] line = reader.ReadLine().Split("|");
sql = "INSERT INTO Country (Code, Value) VALUES (@code, @value);";
dbDict = new Dictionary<string, object>{
{ "code", line[0] },
{ "value", line[1] }
};
db.ExecuteNonQuery(sql, dbDict);
} while (reader.EndOfStream == false);
}
// load language list
Logging.Log(Logging.LogType.Information, "Database Upgrade", "Adding language look up table contents");
string languageResourceName = "gaseous_server.Support.Language.txt";
using (Stream stream = assembly.GetManifestResourceStream(languageResourceName))
using (StreamReader reader = new StreamReader(stream))
{
do
{
string[] line = reader.ReadLine().Split("|");
sql = "INSERT INTO Language (Code, Value) VALUES (@code, @value);";
dbDict = new Dictionary<string, object>{
{ "code", line[0] },
{ "value", line[1] }
};
db.ExecuteNonQuery(sql, dbDict);
} while (reader.EndOfStream == false);
}
break;
} }
break; break;
} }
@@ -121,7 +165,8 @@ namespace gaseous_server.Classes
} }
} }
public static void MySql_1002_MigrateMetadataVersion() { public static void MySql_1002_MigrateMetadataVersion()
{
Database db = new Database(Database.databaseType.MySql, Config.DatabaseConfiguration.ConnectionString); Database db = new Database(Database.databaseType.MySql, Config.DatabaseConfiguration.ConnectionString);
string sql = ""; string sql = "";
Dictionary<string, object> dbDict = new Dictionary<string, object>(); Dictionary<string, object> dbDict = new Dictionary<string, object>();
@@ -134,7 +179,7 @@ namespace gaseous_server.Classes
Logging.Log(Logging.LogType.Information, "Signature Ingestor - Database Update", "Updating " + data.Rows.Count + " database entries"); Logging.Log(Logging.LogType.Information, "Signature Ingestor - Database Update", "Updating " + data.Rows.Count + " database entries");
int Counter = 0; int Counter = 0;
int LastCounterCheck = 0; int LastCounterCheck = 0;
foreach (DataRow row in data.Rows) foreach (DataRow row in data.Rows)
{ {
List<string> Flags = Newtonsoft.Json.JsonConvert.DeserializeObject<List<string>>((string)Common.ReturnValueIfNull(row["flags"], "[]")); List<string> Flags = Newtonsoft.Json.JsonConvert.DeserializeObject<List<string>>((string)Common.ReturnValueIfNull(row["flags"], "[]"));
List<KeyValuePair<string, object>> Attributes = new List<KeyValuePair<string, object>>(); List<KeyValuePair<string, object>> Attributes = new List<KeyValuePair<string, object>>();
@@ -207,7 +252,7 @@ namespace gaseous_server.Classes
dbDict.Add("id", (int)row["Id"]); dbDict.Add("id", (int)row["Id"]);
db.ExecuteCMD(updateSQL, dbDict); db.ExecuteCMD(updateSQL, dbDict);
if ((Counter - LastCounterCheck) > 10) if ((Counter - LastCounterCheck) > 10)
{ {
LastCounterCheck = Counter; LastCounterCheck = Counter;
Logging.Log(Logging.LogType.Information, "Signature Ingestor - Database Update", "Updating " + Counter + " / " + data.Rows.Count + " database entries"); Logging.Log(Logging.LogType.Information, "Signature Ingestor - Database Update", "Updating " + Counter + " / " + data.Rows.Count + " database entries");

View File

@@ -31,7 +31,7 @@ namespace gaseous_server.Classes
if (!Directory.Exists(ExtractPath)) { Directory.CreateDirectory(ExtractPath); } if (!Directory.Exists(ExtractPath)) { Directory.CreateDirectory(ExtractPath); }
try try
{ {
switch(ImportedFileExtension) switch (ImportedFileExtension)
{ {
case ".zip": case ".zip":
Logging.Log(Logging.LogType.Information, "Get Signature", "Decompressing using zip"); Logging.Log(Logging.LogType.Information, "Get Signature", "Decompressing using zip");
@@ -105,7 +105,7 @@ namespace gaseous_server.Classes
} }
break; break;
} }
Logging.Log(Logging.LogType.Information, "Get Signature", "Processing decompressed files for signature matches"); Logging.Log(Logging.LogType.Information, "Get Signature", "Processing decompressed files for signature matches");
// loop through contents until we find the first signature match // loop through contents until we find the first signature match
List<ArchiveData> archiveFiles = new List<ArchiveData>(); List<ArchiveData> archiveFiles = new List<ArchiveData>();
@@ -116,12 +116,13 @@ namespace gaseous_server.Classes
{ {
FileInfo zfi = new FileInfo(file); FileInfo zfi = new FileInfo(file);
Common.hashObject zhash = new Common.hashObject(file); Common.hashObject zhash = new Common.hashObject(file);
Logging.Log(Logging.LogType.Information, "Get Signature", "Checking signature of decompressed file " + file); Logging.Log(Logging.LogType.Information, "Get Signature", "Checking signature of decompressed file " + file);
if (zfi != null) if (zfi != null)
{ {
ArchiveData archiveData = new ArchiveData{ ArchiveData archiveData = new ArchiveData
{
FileName = Path.GetFileName(file), FileName = Path.GetFileName(file),
FilePath = zfi.Directory.FullName.Replace(ExtractPath, ""), FilePath = zfi.Directory.FullName.Replace(ExtractPath, ""),
Size = zfi.Length, Size = zfi.Length,
@@ -138,7 +139,7 @@ namespace gaseous_server.Classes
if (zDiscoveredSignature.Score > discoveredSignature.Score) if (zDiscoveredSignature.Score > discoveredSignature.Score)
{ {
if ( if (
zDiscoveredSignature.Rom.SignatureSource == gaseous_server.Models.Signatures_Games.RomItem.SignatureSourceType.MAMEArcade || zDiscoveredSignature.Rom.SignatureSource == gaseous_server.Models.Signatures_Games.RomItem.SignatureSourceType.MAMEArcade ||
zDiscoveredSignature.Rom.SignatureSource == gaseous_server.Models.Signatures_Games.RomItem.SignatureSourceType.MAMEMess zDiscoveredSignature.Rom.SignatureSource == gaseous_server.Models.Signatures_Games.RomItem.SignatureSourceType.MAMEMess
) )
{ {
@@ -195,7 +196,7 @@ namespace gaseous_server.Classes
{ {
// signature retrieved from Hasheous // signature retrieved from Hasheous
Logging.Log(Logging.LogType.Information, "Import Game", "Signature retrieved from Hasheous for game: " + dbSignature.Game.Name); Logging.Log(Logging.LogType.Information, "Import Game", "Signature retrieved from Hasheous for game: " + dbSignature.Game.Name);
discoveredSignature = dbSignature; discoveredSignature = dbSignature;
} }
else else
@@ -203,7 +204,7 @@ namespace gaseous_server.Classes
// construct a signature from file data // construct a signature from file data
dbSignature = _GetFileSignatureFromFileData(hash, ImageName, ImageExtension, ImageSize, GameFileImportPath); dbSignature = _GetFileSignatureFromFileData(hash, ImageName, ImageExtension, ImageSize, GameFileImportPath);
Logging.Log(Logging.LogType.Information, "Import Game", "Signature generated from provided file for game: " + dbSignature.Game.Name); Logging.Log(Logging.LogType.Information, "Import Game", "Signature generated from provided file for game: " + dbSignature.Game.Name);
discoveredSignature = dbSignature; discoveredSignature = dbSignature;
} }
} }
@@ -216,8 +217,8 @@ namespace gaseous_server.Classes
return discoveredSignature; return discoveredSignature;
} }
private gaseous_server.Models.Signatures_Games? _GetFileSignatureFromDatabase(Common.hashObject hash, string ImageName, string ImageExtension, long ImageSize, string GameFileImportPath) private gaseous_server.Models.Signatures_Games? _GetFileSignatureFromDatabase(Common.hashObject hash, string ImageName, string ImageExtension, long ImageSize, string GameFileImportPath)
{ {
Logging.Log(Logging.LogType.Information, "Get Signature", "Checking local database for MD5: " + hash.md5hash); Logging.Log(Logging.LogType.Information, "Get Signature", "Checking local database for MD5: " + hash.md5hash);
// check 1: do we have a signature for it? // check 1: do we have a signature for it?
@@ -264,37 +265,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

@@ -16,48 +16,49 @@ using HasheousClient.Models;
namespace gaseous_server.Classes namespace gaseous_server.Classes
{ {
public class ImportGame : QueueItemStatus public class ImportGame : QueueItemStatus
{ {
public void ProcessDirectory(string ImportPath) public void ProcessDirectory(string ImportPath)
{ {
if (Directory.Exists(ImportPath)) if (Directory.Exists(ImportPath))
{ {
string[] importContents = Directory.GetFiles(ImportPath, "*.*", SearchOption.AllDirectories); string[] importContents = Directory.GetFiles(ImportPath, "*.*", SearchOption.AllDirectories);
Logging.Log(Logging.LogType.Information, "Import Games", "Found " + importContents.Length + " files to process in import directory: " + ImportPath); Logging.Log(Logging.LogType.Information, "Import Games", "Found " + importContents.Length + " files to process in import directory: " + ImportPath);
// import files first // import files first
int importCount = 1; int importCount = 1;
foreach (string importContent in importContents) { foreach (string importContent in importContents)
{
SetStatus(importCount, importContents.Length, "Importing file: " + importContent); SetStatus(importCount, importContents.Length, "Importing file: " + importContent);
ImportGameFile(importContent, null); ImportGameFile(importContent, null);
importCount += 1; importCount += 1;
} }
ClearStatus(); ClearStatus();
DeleteOrphanedDirectories(ImportPath); DeleteOrphanedDirectories(ImportPath);
} }
else else
{ {
Logging.Log(Logging.LogType.Critical, "Import Games", "The import directory " + ImportPath + " does not exist."); Logging.Log(Logging.LogType.Critical, "Import Games", "The import directory " + ImportPath + " does not exist.");
throw new DirectoryNotFoundException("Invalid path: " + ImportPath); throw new DirectoryNotFoundException("Invalid path: " + ImportPath);
} }
} }
public void ImportGameFile(string GameFileImportPath, IGDB.Models.Platform? OverridePlatform) public void ImportGameFile(string GameFileImportPath, IGDB.Models.Platform? OverridePlatform)
{ {
Database db = new Database(Database.databaseType.MySql, Config.DatabaseConfiguration.ConnectionString); Database db = new Database(Database.databaseType.MySql, Config.DatabaseConfiguration.ConnectionString);
string sql = ""; string sql = "";
Dictionary<string, object> dbDict = new Dictionary<string, object>(); Dictionary<string, object> dbDict = new Dictionary<string, object>();
if (Common.SkippableFiles.Contains<string>(Path.GetFileName(GameFileImportPath), StringComparer.OrdinalIgnoreCase)) if (Common.SkippableFiles.Contains<string>(Path.GetFileName(GameFileImportPath), StringComparer.OrdinalIgnoreCase))
{ {
Logging.Log(Logging.LogType.Debug, "Import Game", "Skipping item " + GameFileImportPath); Logging.Log(Logging.LogType.Debug, "Import Game", "Skipping item " + GameFileImportPath);
} }
else else
{ {
FileInfo fi = new FileInfo(GameFileImportPath); FileInfo fi = new FileInfo(GameFileImportPath);
Common.hashObject hash = new Common.hashObject(GameFileImportPath); Common.hashObject hash = new Common.hashObject(GameFileImportPath);
@@ -87,7 +88,7 @@ namespace gaseous_server.Classes
} }
File.Move(GameFileImportPath, targetPathWithFileName, true); File.Move(GameFileImportPath, targetPathWithFileName, true);
} }
// import source was the upload directory // import source was the upload directory
if (GameFileImportPath.StartsWith(Config.LibraryConfiguration.LibraryUploadDirectory)) if (GameFileImportPath.StartsWith(Config.LibraryConfiguration.LibraryUploadDirectory))
{ {
@@ -146,8 +147,8 @@ namespace gaseous_server.Classes
} }
} }
} }
} }
} }
public static IGDB.Models.Game SearchForGame(gaseous_server.Models.Signatures_Games Signature, long PlatformId, bool FullDownload) public static IGDB.Models.Game SearchForGame(gaseous_server.Models.Signatures_Games Signature, long PlatformId, bool FullDownload)
{ {
@@ -173,14 +174,20 @@ namespace gaseous_server.Classes
string GameName = Signature.Game.Name; string GameName = Signature.Game.Name;
List<string> SearchCandidates = GetSearchCandidates(GameName); List<string> SearchCandidates = GetSearchCandidates(GameName);
foreach (string SearchCandidate in SearchCandidates) foreach (string SearchCandidate in SearchCandidates)
{ {
bool GameFound = false; bool GameFound = false;
Logging.Log(Logging.LogType.Information, "Import Game", " Searching for title: " + SearchCandidate); Logging.Log(Logging.LogType.Information, "Import Game", " Searching for title: " + SearchCandidate);
foreach (Metadata.Games.SearchType searchType in Enum.GetValues(typeof(Metadata.Games.SearchType))) List<Metadata.Games.SearchType> allowedSearchTypes = Config.ReadSetting<List<Metadata.Games.SearchType>>("DefaultSearchMethods", new List<gaseous_server.Classes.Metadata.Games.SearchType>() {
Games.SearchType.where,
Games.SearchType.wherefuzzy,
Games.SearchType.search,
Games.SearchType.searchNoPlatform
});
foreach (Metadata.Games.SearchType searchType in allowedSearchTypes)
{ {
Logging.Log(Logging.LogType.Information, "Import Game", " Search type: " + searchType.ToString()); Logging.Log(Logging.LogType.Information, "Import Game", " Search type: " + searchType.ToString());
IGDB.Models.Game[] games = Metadata.Games.SearchForGame(SearchCandidate, PlatformId, searchType); IGDB.Models.Game[] games = Metadata.Games.SearchForGame(SearchCandidate, PlatformId, searchType);
@@ -197,8 +204,10 @@ namespace gaseous_server.Classes
Logging.Log(Logging.LogType.Information, "Import Game", " " + games.Length + " search results found"); Logging.Log(Logging.LogType.Information, "Import Game", " " + games.Length + " search results found");
// quite likely we've found sequels and alternate types // quite likely we've found sequels and alternate types
foreach (Game game in games) { foreach (Game game in games)
if (game.Name == SearchCandidate) { {
if (game.Name == SearchCandidate)
{
// found game title matches the search candidate // found game title matches the search candidate
determinedGame = Metadata.Games.GetGame((long)games[0].Id, false, false, false); determinedGame = Metadata.Games.GetGame((long)games[0].Id, false, false, false);
Logging.Log(Logging.LogType.Information, "Import Game", "Found exact match!"); Logging.Log(Logging.LogType.Information, "Import Game", "Found exact match!");
@@ -236,7 +245,13 @@ namespace gaseous_server.Classes
foreach (string SearchCandidate in SearchCandidates) foreach (string SearchCandidate in SearchCandidates)
{ {
foreach (Metadata.Games.SearchType searchType in Enum.GetValues(typeof(Metadata.Games.SearchType))) List<Metadata.Games.SearchType> allowedSearchTypes = Config.ReadSetting<List<Metadata.Games.SearchType>>("DefaultSearchMethods", new List<gaseous_server.Classes.Metadata.Games.SearchType>() {
Games.SearchType.where,
Games.SearchType.wherefuzzy,
Games.SearchType.search,
Games.SearchType.searchNoPlatform
});
foreach (Metadata.Games.SearchType searchType in allowedSearchTypes)
{ {
if ((PlatformId == 0 && searchType == SearchType.searchNoPlatform) || (PlatformId != 0 && searchType != SearchType.searchNoPlatform)) if ((PlatformId == 0 && searchType == SearchType.searchNoPlatform) || (PlatformId != 0 && searchType != SearchType.searchNoPlatform))
{ {
@@ -273,7 +288,8 @@ namespace gaseous_server.Classes
// assumption: no games have () in their titles so we'll remove them // assumption: no games have () in their titles so we'll remove them
int idx = GameName.IndexOf('('); int idx = GameName.IndexOf('(');
if (idx >= 0) { if (idx >= 0)
{
GameName = GameName.Substring(0, idx); GameName = GameName.Substring(0, idx);
} }
@@ -305,7 +321,8 @@ namespace gaseous_server.Classes
if (UpdateId == 0) if (UpdateId == 0)
{ {
sql = "INSERT INTO Games_Roms (PlatformId, GameId, Name, Size, CRC, MD5, SHA1, DevelopmentStatus, Attributes, RomType, RomTypeMedia, MediaLabel, Path, MetadataSource, MetadataGameName, MetadataVersion, LibraryId) VALUES (@platformid, @gameid, @name, @size, @crc, @md5, @sha1, @developmentstatus, @Attributes, @romtype, @romtypemedia, @medialabel, @path, @metadatasource, @metadatagamename, @metadataversion, @libraryid); SELECT CAST(LAST_INSERT_ID() AS SIGNED);"; sql = "INSERT INTO Games_Roms (PlatformId, GameId, Name, Size, CRC, MD5, SHA1, DevelopmentStatus, Attributes, RomType, RomTypeMedia, MediaLabel, Path, MetadataSource, MetadataGameName, MetadataVersion, LibraryId) VALUES (@platformid, @gameid, @name, @size, @crc, @md5, @sha1, @developmentstatus, @Attributes, @romtype, @romtypemedia, @medialabel, @path, @metadatasource, @metadatagamename, @metadataversion, @libraryid); SELECT CAST(LAST_INSERT_ID() AS SIGNED);";
} else }
else
{ {
sql = "UPDATE Games_Roms SET PlatformId=@platformid, GameId=@gameid, Name=@name, Size=@size, DevelopmentStatus=@developmentstatus, Attributes=@Attributes, RomType=@romtype, RomTypeMedia=@romtypemedia, MediaLabel=@medialabel, MetadataSource=@metadatasource, MetadataGameName=@metadatagamename, MetadataVersion=@metadataversion WHERE Id=@id;"; sql = "UPDATE Games_Roms SET PlatformId=@platformid, GameId=@gameid, Name=@name, Size=@size, DevelopmentStatus=@developmentstatus, Attributes=@Attributes, RomType=@romtype, RomTypeMedia=@romtypemedia, MediaLabel=@medialabel, MetadataSource=@metadatasource, MetadataGameName=@metadatagamename, MetadataVersion=@metadataversion WHERE Id=@id;";
dbDict.Add("id", UpdateId); dbDict.Add("id", UpdateId);
@@ -348,7 +365,8 @@ namespace gaseous_server.Classes
if (UpdateId == 0) if (UpdateId == 0)
{ {
romId = (long)romInsert.Rows[0][0]; romId = (long)romInsert.Rows[0][0];
} else }
else
{ {
romId = UpdateId; romId = UpdateId;
} }
@@ -362,73 +380,70 @@ namespace gaseous_server.Classes
return romId; return romId;
} }
public static string ComputeROMPath(long RomId) public static string ComputeROMPath(long RomId)
{ {
Classes.Roms.GameRomItem rom = Classes.Roms.GetRom(RomId);
// get metadata
IGDB.Models.Platform platform = gaseous_server.Classes.Metadata.Platforms.GetPlatform(rom.PlatformId);
IGDB.Models.Game game = gaseous_server.Classes.Metadata.Games.GetGame(rom.GameId, false, false, false);
// build path
string platformSlug = "Unknown Platform";
if (platform != null)
{
platformSlug = platform.Slug;
}
string gameSlug = "Unknown Title";
if (game != null)
{
gameSlug = game.Slug;
}
string DestinationPath = Path.Combine(GameLibrary.GetDefaultLibrary.Path, gameSlug, platformSlug);
if (!Directory.Exists(DestinationPath))
{
Directory.CreateDirectory(DestinationPath);
}
string DestinationPathName = Path.Combine(DestinationPath, rom.Name);
return DestinationPathName;
}
public static bool MoveGameFile(long RomId)
{
Classes.Roms.GameRomItem rom = Classes.Roms.GetRom(RomId); Classes.Roms.GameRomItem rom = Classes.Roms.GetRom(RomId);
string romPath = rom.Path;
// get metadata
IGDB.Models.Platform platform = gaseous_server.Classes.Metadata.Platforms.GetPlatform(rom.PlatformId);
IGDB.Models.Game game = gaseous_server.Classes.Metadata.Games.GetGame(rom.GameId, false, false, false);
// build path
string platformSlug = "Unknown Platform";
if (platform != null)
{
platformSlug = platform.Slug;
}
string gameSlug = "Unknown Title";
if (game != null)
{
gameSlug = game.Slug;
}
string DestinationPath = Path.Combine(GameLibrary.GetDefaultLibrary.Path, gameSlug, platformSlug);
if (!Directory.Exists(DestinationPath))
{
Directory.CreateDirectory(DestinationPath);
}
string DestinationPathName = Path.Combine(DestinationPath, rom.Name);
return DestinationPathName;
}
public static bool MoveGameFile(long RomId)
{
Classes.Roms.GameRomItem rom = Classes.Roms.GetRom(RomId);
string romPath = rom.Path;
if (File.Exists(romPath)) if (File.Exists(romPath))
{ {
string DestinationPath = ComputeROMPath(RomId); string DestinationPath = ComputeROMPath(RomId);
if (romPath == DestinationPath) if (romPath == DestinationPath)
{ {
Logging.Log(Logging.LogType.Debug, "Move Game ROM", "Destination path is the same as the current path - aborting"); Logging.Log(Logging.LogType.Debug, "Move Game ROM", "Destination path is the same as the current path - aborting");
return true; return true;
} }
else else
{ {
Logging.Log(Logging.LogType.Information, "Move Game ROM", "Moving " + romPath + " to " + DestinationPath); Logging.Log(Logging.LogType.Information, "Move Game ROM", "Moving " + romPath + " to " + DestinationPath);
if (File.Exists(DestinationPath)) if (File.Exists(DestinationPath))
{ {
Logging.Log(Logging.LogType.Information, "Move Game ROM", "A file with the same name exists at the destination - aborting"); Logging.Log(Logging.LogType.Information, "Move Game ROM", "A file with the same name exists at the destination - overwriting");
return false; }
}
else
{
File.Move(romPath, DestinationPath);
// update the db File.Move(romPath, DestinationPath, true);
Database db = new Database(Database.databaseType.MySql, Config.DatabaseConfiguration.ConnectionString);
string sql = "UPDATE Games_Roms SET Path=@path WHERE Id=@id";
Dictionary<string, object> dbDict = new Dictionary<string, object>();
dbDict.Add("id", RomId);
dbDict.Add("path", DestinationPath);
db.ExecuteCMD(sql, dbDict);
return true; // update the db
} Database db = new Database(Database.databaseType.MySql, Config.DatabaseConfiguration.ConnectionString);
} string sql = "UPDATE Games_Roms SET Path=@path WHERE Id=@id";
Dictionary<string, object> dbDict = new Dictionary<string, object>();
dbDict.Add("id", RomId);
dbDict.Add("path", DestinationPath);
db.ExecuteCMD(sql, dbDict);
return true;
}
} }
else else
{ {
@@ -437,8 +452,8 @@ namespace gaseous_server.Classes
} }
} }
public void OrganiseLibrary() public void OrganiseLibrary()
{ {
Logging.Log(Logging.LogType.Information, "Organise Library", "Starting default library organisation"); Logging.Log(Logging.LogType.Information, "Organise Library", "Starting default library organisation");
GameLibrary.LibraryItem library = GameLibrary.GetDefaultLibrary; GameLibrary.LibraryItem library = GameLibrary.GetDefaultLibrary;
@@ -451,19 +466,19 @@ namespace gaseous_server.Classes
DataTable romDT = db.ExecuteCMD(sql, dbDict); DataTable romDT = db.ExecuteCMD(sql, dbDict);
if (romDT.Rows.Count > 0) if (romDT.Rows.Count > 0)
{ {
for (int i = 0; i < romDT.Rows.Count; i++) for (int i = 0; i < romDT.Rows.Count; i++)
{ {
SetStatus(i, romDT.Rows.Count, "Processing file " + romDT.Rows[i]["name"]); SetStatus(i, romDT.Rows.Count, "Processing file " + romDT.Rows[i]["name"]);
Logging.Log(Logging.LogType.Information, "Organise Library", "(" + i + "/" + romDT.Rows.Count + ") Processing ROM " + romDT.Rows[i]["name"]); Logging.Log(Logging.LogType.Information, "Organise Library", "(" + i + "/" + romDT.Rows.Count + ") Processing ROM " + romDT.Rows[i]["name"]);
long RomId = (long)romDT.Rows[i]["id"]; long RomId = (long)romDT.Rows[i]["id"];
MoveGameFile(RomId); MoveGameFile(RomId);
} }
} }
ClearStatus(); ClearStatus();
// clean up empty directories // clean up empty directories
DeleteOrphanedDirectories(GameLibrary.GetDefaultLibrary.Path); DeleteOrphanedDirectories(GameLibrary.GetDefaultLibrary.Path);
Logging.Log(Logging.LogType.Information, "Organise Library", "Finsihed default library organisation"); Logging.Log(Logging.LogType.Information, "Organise Library", "Finsihed default library organisation");
} }
@@ -476,7 +491,7 @@ namespace gaseous_server.Classes
string[] files = Directory.GetFiles(directory); string[] files = Directory.GetFiles(directory);
string[] directories = Directory.GetDirectories(directory); string[] directories = Directory.GetDirectories(directory);
if (files.Length == 0 && if (files.Length == 0 &&
directories.Length == 0) directories.Length == 0)
{ {
@@ -563,7 +578,7 @@ namespace gaseous_server.Classes
public void LibrarySpecificScan(GameLibrary.LibraryItem library) public void LibrarySpecificScan(GameLibrary.LibraryItem library)
{ {
Logging.Log(Logging.LogType.Information, "Library Scan", "Starting scan of library: " + library.Name); Logging.Log(Logging.LogType.Information, "Library Scan", "Starting scan of library: " + library.Name);
Database db = new Database(Database.databaseType.MySql, Config.DatabaseConfiguration.ConnectionString); Database db = new Database(Database.databaseType.MySql, Config.DatabaseConfiguration.ConnectionString);
@@ -632,7 +647,7 @@ namespace gaseous_server.Classes
{ {
// file is not in database - process it // file is not in database - process it
Logging.Log(Logging.LogType.Information, "Library Scan", "Orphaned file found in library: " + LibraryFile); Logging.Log(Logging.LogType.Information, "Library Scan", "Orphaned file found in library: " + LibraryFile);
Common.hashObject hash = new Common.hashObject(LibraryFile); Common.hashObject hash = new Common.hashObject(LibraryFile);
FileInfo fi = new FileInfo(LibraryFile); FileInfo fi = new FileInfo(LibraryFile);
@@ -644,8 +659,8 @@ namespace gaseous_server.Classes
// get discovered platform // get discovered platform
long PlatformId; long PlatformId;
IGDB.Models.Platform determinedPlatform; IGDB.Models.Platform determinedPlatform;
if (sig.Flags.IGDBPlatformId == null || sig.Flags.IGDBPlatformId == 0 ) if (sig.Flags.IGDBPlatformId == null || sig.Flags.IGDBPlatformId == 0)
{ {
// no platform discovered in the signature // no platform discovered in the signature
PlatformId = library.DefaultPlatformId; PlatformId = library.DefaultPlatformId;
@@ -770,8 +785,8 @@ namespace gaseous_server.Classes
// get discovered platform // get discovered platform
long PlatformId; long PlatformId;
IGDB.Models.Platform determinedPlatform; IGDB.Models.Platform determinedPlatform;
if (sig.Flags.IGDBPlatformId == null || sig.Flags.IGDBPlatformId == 0 ) if (sig.Flags.IGDBPlatformId == null || sig.Flags.IGDBPlatformId == 0)
{ {
// no platform discovered in the signature // no platform discovered in the signature
PlatformId = library.DefaultPlatformId; PlatformId = library.DefaultPlatformId;

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");
long deletedCount = 1;
long deletedEventCount = 0;
long maxLoops = 1000;
sql = "DELETE FROM ServerLogs WHERE EventTime < @EventRetentionDate LIMIT 1000; SELECT ROW_COUNT() AS Count;";
dbDict.Add("EventRetentionDate", DateTime.UtcNow.AddDays(Config.LoggingConfiguration.LogRetention * -1)); dbDict.Add("EventRetentionDate", DateTime.UtcNow.AddDays(Config.LoggingConfiguration.LogRetention * -1));
db.ExecuteCMD(sql, dbDict); while (deletedCount > 0)
{
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,9 +5,9 @@ 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,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)
@@ -78,15 +78,17 @@ namespace gaseous_server.Classes.Metadata
if (cacheStatus == Storage.CacheStatus.Current) { cacheStatus = Storage.CacheStatus.Expired; } if (cacheStatus == Storage.CacheStatus.Current) { cacheStatus = Storage.CacheStatus.Expired; }
} }
// set up where clause
string WhereClause = ""; string WhereClause = "";
string searchField = "";
switch (searchUsing) switch (searchUsing)
{ {
case SearchUsing.id: case SearchUsing.id:
WhereClause = "where id = " + searchValue; WhereClause = "where id = " + searchValue;
searchField = "id";
break; break;
case SearchUsing.slug: case SearchUsing.slug:
WhereClause = "where slug = " + searchValue; WhereClause = "where slug = \"" + searchValue + "\"";
searchField = "slug";
break; break;
default: default:
throw new Exception("Invalid search type"); throw new Exception("Invalid search type");
@@ -110,11 +112,11 @@ namespace gaseous_server.Classes.Metadata
catch (Exception ex) catch (Exception ex)
{ {
Logging.Log(Logging.LogType.Warning, "Metadata: " + returnValue.GetType().Name, "An error occurred while connecting to IGDB. WhereClause: " + WhereClause, ex); Logging.Log(Logging.LogType.Warning, "Metadata: " + returnValue.GetType().Name, "An error occurred while connecting to IGDB. WhereClause: " + WhereClause, ex);
returnValue = Storage.GetCacheValue<Game>(returnValue, "id", (long)searchValue); returnValue = Storage.GetCacheValue<Game>(returnValue, searchField, searchValue);
} }
return returnValue; return returnValue;
case Storage.CacheStatus.Current: case Storage.CacheStatus.Current:
returnValue = Storage.GetCacheValue<Game>(returnValue, "id", (long)searchValue); returnValue = Storage.GetCacheValue<Game>(returnValue, searchField, searchValue);
UpdateSubClasses(returnValue, false, false, false); UpdateSubClasses(returnValue, false, false, false);
return returnValue; return returnValue;
default: default:
@@ -125,17 +127,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 +287,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 +349,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 +366,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 +430,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 +441,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 +479,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 +516,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 +536,7 @@ namespace gaseous_server.Classes.Metadata
{ {
} }
public MinimalGameItem(Game gameObject) public MinimalGameItem(Game gameObject)
{ {
this.Id = gameObject.Id; this.Id = gameObject.Id;
@@ -561,6 +564,7 @@ namespace gaseous_server.Classes.Metadata
} }
public long? Id { get; set; } public long? Id { get; set; }
public long Index { get; set; }
public string Name { get; set; } public string Name { get; set; }
public string Slug { get; set; } public string Slug { get; set; }
public double? TotalRating { get; set; } public double? TotalRating { get; set; }

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)
@@ -78,13 +78,16 @@ namespace gaseous_server.Classes.Metadata
// set up where clause // set up where clause
string WhereClause = ""; string WhereClause = "";
string searchField = "";
switch (searchUsing) switch (searchUsing)
{ {
case SearchUsing.id: case SearchUsing.id:
WhereClause = "where id = " + searchValue; WhereClause = "where id = " + searchValue;
searchField = "id";
break; break;
case SearchUsing.slug: case SearchUsing.slug:
WhereClause = "where slug = " + searchValue; WhereClause = "where slug = \"" + searchValue + "\"";
searchField = "slug";
break; break;
default: default:
throw new Exception("Invalid search type"); throw new Exception("Invalid search type");
@@ -96,7 +99,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,23 +107,23 @@ 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;
} }
catch (Exception ex) catch (Exception ex)
{ {
Logging.Log(Logging.LogType.Warning, "Metadata: " + returnValue.GetType().Name, "An error occurred while connecting to IGDB. WhereClause: " + WhereClause, ex); Logging.Log(Logging.LogType.Warning, "Metadata: " + returnValue.GetType().Name, "An error occurred while connecting to IGDB. WhereClause: " + WhereClause, ex);
return Storage.GetCacheValue<Platform>(returnValue, "id", (long)searchValue); return Storage.GetCacheValue<Platform>(returnValue, searchField, searchValue);
} }
case Storage.CacheStatus.Current: case Storage.CacheStatus.Current:
return Storage.GetCacheValue<Platform>(returnValue, "id", (long)searchValue); return Storage.GetCacheValue<Platform>(returnValue, searchField, searchValue);
default: default:
throw new Exception("How did you get here?"); throw new Exception("How did you get here?");
} }
} }
private static void UpdateSubClasses(Platform platform) private static void UpdateSubClasses(Platform platform, bool GetImages)
{ {
if (platform.Versions != null) if (platform.Versions != null)
{ {
@@ -130,15 +133,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 +164,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

@@ -5,6 +5,9 @@ using Microsoft.VisualBasic;
using IGDB.Models; using IGDB.Models;
using gaseous_server.Classes.Metadata; using gaseous_server.Classes.Metadata;
using System.IO.Compression; using System.IO.Compression;
using SharpCompress.Archives;
using SharpCompress.Common;
using gaseous_server.Models;
namespace gaseous_server.Classes namespace gaseous_server.Classes
{ {
@@ -259,6 +262,7 @@ namespace gaseous_server.Classes
{ {
Game GameObject = Games.GetGame(mediaGroupItem.GameId, false, false, false); Game GameObject = Games.GetGame(mediaGroupItem.GameId, false, false, false);
Platform PlatformObject = Platforms.GetPlatform(mediaGroupItem.PlatformId, false); Platform PlatformObject = Platforms.GetPlatform(mediaGroupItem.PlatformId, false);
PlatformMapping.PlatformMapItem platformMapItem = PlatformMapping.GetPlatformMap(mediaGroupItem.PlatformId);
Logging.Log(Logging.LogType.Information, "Media Group", "Beginning build of media group: " + GameObject.Name + " for platform " + PlatformObject.Name); Logging.Log(Logging.LogType.Information, "Media Group", "Beginning build of media group: " + GameObject.Name + " for platform " + PlatformObject.Name);
@@ -293,10 +297,124 @@ namespace gaseous_server.Classes
foreach (long RomId in mediaGroupItem.RomIds) foreach (long RomId in mediaGroupItem.RomIds)
{ {
Roms.GameRomItem rom = Roms.GetRom(RomId); Roms.GameRomItem rom = Roms.GetRom(RomId);
bool fileNameFound = false;
if (File.Exists(rom.Path)) if (File.Exists(rom.Path))
{ {
Logging.Log(Logging.LogType.Information, "Media Group", "Copying ROM: " + rom.Name); string romExt = Path.GetExtension(rom.Path);
File.Copy(rom.Path, Path.Combine(ZipFileTempPath, Path.GetFileName(rom.Path))); if (new string[]{ ".zip", ".rar", ".7z" }.Contains(romExt))
{
Logging.Log(Logging.LogType.Information, "Media Group", "Decompressing ROM: " + rom.Name);
// is compressed
switch (romExt)
{
case ".zip":
try
{
using (var archive = SharpCompress.Archives.Zip.ZipArchive.Open(rom.Path))
{
foreach (var entry in archive.Entries.Where(entry => !entry.IsDirectory))
{
Logging.Log(Logging.LogType.Information, "Media Group", "Extracting file: " + entry.Key);
if (fileNameFound == false)
{
//check if extension is in valid extensions
if (platformMapItem.Extensions.SupportedFileExtensions.Contains(Path.GetExtension(entry.Key), StringComparer.InvariantCultureIgnoreCase))
{
// update rom file name
rom.Name = entry.Key;
fileNameFound = true;
}
}
entry.WriteToDirectory(ZipFileTempPath, new ExtractionOptions()
{
ExtractFullPath = true,
Overwrite = true
});
}
}
}
catch (Exception zipEx)
{
Logging.Log(Logging.LogType.Warning, "Media Group", "Unzip error", zipEx);
throw;
}
break;
case ".rar":
try
{
using (var archive = SharpCompress.Archives.Rar.RarArchive.Open(rom.Path))
{
foreach (var entry in archive.Entries.Where(entry => !entry.IsDirectory))
{
Logging.Log(Logging.LogType.Information, "Media Group", "Extracting file: " + entry.Key);
if (fileNameFound == false)
{
//check if extension is in valid extensions
if (platformMapItem.Extensions.SupportedFileExtensions.Contains(Path.GetExtension(entry.Key), StringComparer.InvariantCultureIgnoreCase))
{
// update rom file name
rom.Name = entry.Key;
fileNameFound = true;
}
}
entry.WriteToDirectory(ZipFileTempPath, new ExtractionOptions()
{
ExtractFullPath = true,
Overwrite = true
});
}
}
}
catch (Exception zipEx)
{
Logging.Log(Logging.LogType.Warning, "Media Group", "Unrar error", zipEx);
throw;
}
break;
case ".7z":
try
{
using (var archive = SharpCompress.Archives.SevenZip.SevenZipArchive.Open(rom.Path))
{
foreach (var entry in archive.Entries.Where(entry => !entry.IsDirectory))
{
Logging.Log(Logging.LogType.Information, "Media Group", "Extracting file: " + entry.Key);
if (fileNameFound == false)
{
//check if extension is in valid extensions
if (platformMapItem.Extensions.SupportedFileExtensions.Contains(Path.GetExtension(entry.Key), StringComparer.InvariantCultureIgnoreCase))
{
// update rom file name
rom.Name = entry.Key;
fileNameFound = true;
}
}
entry.WriteToDirectory(ZipFileTempPath, new ExtractionOptions()
{
ExtractFullPath = true,
Overwrite = true
});
}
}
}
catch (Exception zipEx)
{
Logging.Log(Logging.LogType.Warning, "Media Group", "7z error", zipEx);
throw;
}
break;
}
}
else
{
// is uncompressed
Logging.Log(Logging.LogType.Information, "Media Group", "Copying ROM: " + rom.Name);
File.Copy(rom.Path, Path.Combine(ZipFileTempPath, Path.GetFileName(rom.Path)));
}
romItems.Add(rom); romItems.Add(rom);
} }

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,12 @@ 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

@@ -8,21 +8,49 @@ namespace gaseous_server.SignatureIngestors.XML
{ {
public class XMLIngestor : QueueItemStatus public class XMLIngestor : QueueItemStatus
{ {
public void Import(string SearchPath, gaseous_signature_parser.parser.SignatureParser XMLType) public void Import(string SearchPath, string ProcessedDirectory, gaseous_signature_parser.parser.SignatureParser XMLType)
{ {
// connect to database // connect to database
Database db = new Database(Database.databaseType.MySql, Config.DatabaseConfiguration.ConnectionString); Database db = new Database(Database.databaseType.MySql, Config.DatabaseConfiguration.ConnectionString);
string? XMLDBSearchPath = null;
string? XMLDBProcessedDirectory = null;
if (XMLType == gaseous_signature_parser.parser.SignatureParser.NoIntro)
{
XMLDBSearchPath = Path.Combine(SearchPath, "DB");
XMLDBProcessedDirectory = Path.Combine(ProcessedDirectory, "DB");
SearchPath = Path.Combine(SearchPath, "DAT");
ProcessedDirectory = Path.Combine(ProcessedDirectory, "DAT");
}
// process provided files // process provided files
Logging.Log(Logging.LogType.Information, "Signature Ingestor - XML", "Importing from " + SearchPath);
if (!Directory.Exists(SearchPath)) if (!Directory.Exists(SearchPath))
{ {
Directory.CreateDirectory(SearchPath); Directory.CreateDirectory(SearchPath);
} }
if (!Directory.Exists(ProcessedDirectory))
{
Directory.CreateDirectory(ProcessedDirectory);
}
string[] PathContents = Directory.GetFiles(SearchPath); string[] PathContents = Directory.GetFiles(SearchPath);
Array.Sort(PathContents); Array.Sort(PathContents);
string[]? DBPathContents = null;
if (XMLDBSearchPath != null)
{
if (!Directory.Exists(XMLDBSearchPath))
{
Directory.CreateDirectory(XMLDBSearchPath);
}
if (!Directory.Exists(XMLDBProcessedDirectory))
{
Directory.CreateDirectory(XMLDBProcessedDirectory);
}
DBPathContents = Directory.GetFiles(XMLDBSearchPath);
}
string sql = ""; string sql = "";
Dictionary<string, object> dbDict = new Dictionary<string, object>(); Dictionary<string, object> dbDict = new Dictionary<string, object>();
System.Data.DataTable sigDB; System.Data.DataTable sigDB;
@@ -33,226 +61,380 @@ namespace gaseous_server.SignatureIngestors.XML
SetStatus(i + 1, PathContents.Length, "Processing signature file: " + XMLFile); SetStatus(i + 1, PathContents.Length, "Processing signature file: " + XMLFile);
if (Common.SkippableFiles.Contains(Path.GetFileName(XMLFile), StringComparer.OrdinalIgnoreCase)) Logging.Log(Logging.LogType.Information, "Signature Ingest", "(" + (i + 1) + " / " + PathContents.Length + ") Processing " + XMLType.ToString() + " DAT file: " + XMLFile);
{
Logging.Log(Logging.LogType.Information, "Signature Ingestor - XML", "Skipping file: " + XMLFile);
}
else
{
// check xml file md5
Common.hashObject hashObject = new Common.hashObject(XMLFile);
sql = "SELECT * FROM Signatures_Sources WHERE SourceMD5=@sourcemd5";
dbDict = new Dictionary<string, object>();
dbDict.Add("sourcemd5", hashObject.md5hash);
sigDB = db.ExecuteCMD(sql, dbDict);
if (sigDB.Rows.Count == 0) string? DBFile = null;
if (XMLDBSearchPath != null)
{
switch (XMLType)
{ {
try case gaseous_signature_parser.parser.SignatureParser.NoIntro:
{ for (UInt16 x = 0; x < DBPathContents.Length; x++)
Logging.Log(Logging.LogType.Information, "Signature Ingestor - XML", "Importing file: " + XMLFile);
// start parsing file
gaseous_signature_parser.parser Parser = new gaseous_signature_parser.parser();
RomSignatureObject Object = Parser.ParseSignatureDAT(XMLFile, XMLType);
// store in database
string[] flipNameAndDescription = {
"MAMEArcade",
"MAMEMess"
};
// store source object
bool processGames = false;
if (Object.SourceMd5 != null)
{ {
sql = "SELECT * FROM Signatures_Sources WHERE SourceMD5=@sourcemd5"; string tempDBFileName = Path.GetFileNameWithoutExtension(DBPathContents[x].Replace(" (DB Export)", ""));
dbDict = new Dictionary<string, object>(); if (tempDBFileName == Path.GetFileNameWithoutExtension(XMLFile))
string sourceUriStr = "";
if (Object.Url != null)
{ {
sourceUriStr = Object.Url.ToString(); DBFile = DBPathContents[x];
Logging.Log(Logging.LogType.Information, "Signature Ingest", "Using DB file: " + DBFile);
break;
} }
dbDict.Add("name", Common.ReturnValueIfNull(Object.Name, "")); }
dbDict.Add("description", Common.ReturnValueIfNull(Object.Description, "")); break;
dbDict.Add("category", Common.ReturnValueIfNull(Object.Category, "")); }
dbDict.Add("version", Common.ReturnValueIfNull(Object.Version, "")); }
dbDict.Add("author", Common.ReturnValueIfNull(Object.Author, ""));
dbDict.Add("email", Common.ReturnValueIfNull(Object.Email, "")); // check xml file md5
dbDict.Add("homepage", Common.ReturnValueIfNull(Object.Homepage, "")); Common.hashObject hashObject = new Common.hashObject(XMLFile);
dbDict.Add("uri", sourceUriStr); sql = "SELECT * FROM Signatures_Sources WHERE SourceMD5=@sourcemd5";
dbDict.Add("sourcetype", Common.ReturnValueIfNull(Object.SourceType, "")); dbDict = new Dictionary<string, object>();
dbDict.Add("sourcemd5", Object.SourceMd5); dbDict.Add("sourcemd5", hashObject.md5hash);
dbDict.Add("sourcesha1", Object.SourceSHA1); sigDB = db.ExecuteCMD(sql, dbDict);
if (sigDB.Rows.Count == 0)
{
try
{
// start parsing file
gaseous_signature_parser.parser Parser = new gaseous_signature_parser.parser();
RomSignatureObject Object = Parser.ParseSignatureDAT(XMLFile, DBFile, XMLType);
// store in database
string[] flipNameAndDescription = {
"MAMEArcade",
"MAMEMess"
};
// store source object
bool processGames = false;
if (Object.SourceMd5 != null)
{
int sourceId = 0;
sql = "SELECT * FROM Signatures_Sources WHERE `SourceMD5`=@sourcemd5";
dbDict = new Dictionary<string, object>
{
{ "name", Common.ReturnValueIfNull(Object.Name, "") },
{ "description", Common.ReturnValueIfNull(Object.Description, "") },
{ "category", Common.ReturnValueIfNull(Object.Category, "") },
{ "version", Common.ReturnValueIfNull(Object.Version, "") },
{ "author", Common.ReturnValueIfNull(Object.Author, "") },
{ "email", Common.ReturnValueIfNull(Object.Email, "") },
{ "homepage", Common.ReturnValueIfNull(Object.Homepage, "") }
};
if (Object.Url == null)
{
dbDict.Add("uri", "");
}
else
{
dbDict.Add("uri", Common.ReturnValueIfNull(Object.Url.ToString(), ""));
}
dbDict.Add("sourcetype", Common.ReturnValueIfNull(Object.SourceType, ""));
dbDict.Add("sourcemd5", Object.SourceMd5);
dbDict.Add("sourcesha1", Object.SourceSHA1);
sigDB = db.ExecuteCMD(sql, dbDict);
if (sigDB.Rows.Count == 0)
{
// entry not present, insert it
sql = "INSERT INTO Signatures_Sources (`Name`, `Description`, `Category`, `Version`, `Author`, `Email`, `Homepage`, `Url`, `SourceType`, `SourceMD5`, `SourceSHA1`) VALUES (@name, @description, @category, @version, @author, @email, @homepage, @uri, @sourcetype, @sourcemd5, @sourcesha1); SELECT CAST(LAST_INSERT_ID() AS SIGNED);";
sigDB = db.ExecuteCMD(sql, dbDict); sigDB = db.ExecuteCMD(sql, dbDict);
if (sigDB.Rows.Count == 0)
sourceId = Convert.ToInt32(sigDB.Rows[0][0]);
processGames = true;
}
if (processGames == true)
{
for (int x = 0; x < Object.Games.Count; ++x)
{ {
// entry not present, insert it RomSignatureObject.Game gameObject = Object.Games[x];
sql = "INSERT INTO Signatures_Sources (Name, Description, Category, Version, Author, Email, Homepage, Url, SourceType, SourceMD5, SourceSHA1) VALUES (@name, @description, @category, @version, @author, @email, @homepage, @uri, @sourcetype, @sourcemd5, @sourcesha1)";
db.ExecuteCMD(sql, dbDict); // set up game dictionary
dbDict = new Dictionary<string, object>();
processGames = true; if (flipNameAndDescription.Contains(Object.SourceType))
}
if (processGames == true)
{
for (int x = 0; x < Object.Games.Count; ++x)
{ {
RomSignatureObject.Game gameObject = Object.Games[x]; dbDict.Add("name", Common.ReturnValueIfNull(gameObject.Description, ""));
dbDict.Add("description", Common.ReturnValueIfNull(gameObject.Name, ""));
}
else
{
dbDict.Add("name", Common.ReturnValueIfNull(gameObject.Name, ""));
dbDict.Add("description", Common.ReturnValueIfNull(gameObject.Description, ""));
}
dbDict.Add("year", Common.ReturnValueIfNull(gameObject.Year, ""));
dbDict.Add("publisher", Common.ReturnValueIfNull(gameObject.Publisher, ""));
dbDict.Add("demo", (int)gameObject.Demo);
dbDict.Add("system", Common.ReturnValueIfNull(gameObject.System, ""));
dbDict.Add("platform", Common.ReturnValueIfNull(gameObject.System, ""));
dbDict.Add("systemvariant", Common.ReturnValueIfNull(gameObject.SystemVariant, ""));
dbDict.Add("video", Common.ReturnValueIfNull(gameObject.Video, ""));
// set up game dictionary List<int> gameCountries = new List<int>();
dbDict = new Dictionary<string, object>(); if (
if (flipNameAndDescription.Contains(Object.SourceType)) gameObject.Country != null &&
gameObject.Country != "Unknown"
)
{
string[] countries = gameObject.Country.Split(",");
foreach (string country in countries)
{ {
dbDict.Add("name", Common.ReturnValueIfNull(gameObject.Description, "")); int countryId = -1;
dbDict.Add("description", Common.ReturnValueIfNull(gameObject.Name, "")); countryId = Common.GetLookupByCode(Common.LookupTypes.Country, (string)Common.ReturnValueIfNull(country.Trim(), ""));
} if (countryId == -1)
else
{
dbDict.Add("name", Common.ReturnValueIfNull(gameObject.Name, ""));
dbDict.Add("description", Common.ReturnValueIfNull(gameObject.Description, ""));
}
dbDict.Add("year", Common.ReturnValueIfNull(gameObject.Year, ""));
dbDict.Add("publisher", Common.ReturnValueIfNull(gameObject.Publisher, ""));
dbDict.Add("demo", (int)gameObject.Demo);
dbDict.Add("system", Common.ReturnValueIfNull(gameObject.System, ""));
dbDict.Add("platform", Common.ReturnValueIfNull(gameObject.System, ""));
dbDict.Add("systemvariant", Common.ReturnValueIfNull(gameObject.SystemVariant, ""));
dbDict.Add("video", Common.ReturnValueIfNull(gameObject.Video, ""));
dbDict.Add("country", Common.ReturnValueIfNull(gameObject.Country, ""));
dbDict.Add("language", Common.ReturnValueIfNull(gameObject.Language, ""));
dbDict.Add("copyright", Common.ReturnValueIfNull(gameObject.Copyright, ""));
// store platform
int gameSystem = 0;
if (gameObject.System != null)
{
sql = "SELECT Id FROM Signatures_Platforms WHERE Platform=@platform";
sigDB = db.ExecuteCMD(sql, dbDict);
if (sigDB.Rows.Count == 0)
{ {
// entry not present, insert it countryId = Common.GetLookupByValue(Common.LookupTypes.Country, (string)Common.ReturnValueIfNull(country.Trim(), ""));
sql = "INSERT INTO Signatures_Platforms (Platform) VALUES (@platform); SELECT CAST(LAST_INSERT_ID() AS SIGNED);";
sigDB = db.ExecuteCMD(sql, dbDict);
gameSystem = Convert.ToInt32(sigDB.Rows[0][0]); if (countryId == -1)
{
Logging.Log(Logging.LogType.Warning, "Signature Ingest", "Unable to locate country id for " + country.Trim());
sql = "INSERT INTO Country (`Code`, `Value`) VALUES (@code, @name); SELECT CAST(LAST_INSERT_ID() AS SIGNED);";
Dictionary<string, object> countryDict = new Dictionary<string, object>{
{ "code", country.Trim() },
{ "name", country.Trim() }
};
countryId = int.Parse(db.ExecuteCMD(sql, countryDict).Rows[0][0].ToString());
}
} }
else
if (countryId > 0)
{ {
gameSystem = (int)sigDB.Rows[0][0]; gameCountries.Add(countryId);
} }
} }
dbDict.Add("systemid", gameSystem); }
// store publisher List<int> gameLanguages = new List<int>();
int gamePublisher = 0; if (
if (gameObject.Publisher != null) gameObject.Language != null &&
gameObject.Language != "nolang"
)
{
string[] languages = gameObject.Language.Split(",");
foreach (string language in languages)
{ {
sql = "SELECT * FROM Signatures_Publishers WHERE Publisher=@publisher"; int languageId = -1;
languageId = Common.GetLookupByCode(Common.LookupTypes.Language, (string)Common.ReturnValueIfNull(language.Trim(), ""));
if (languageId == -1)
{
languageId = Common.GetLookupByValue(Common.LookupTypes.Language, (string)Common.ReturnValueIfNull(language.Trim(), ""));
sigDB = db.ExecuteCMD(sql, dbDict); if (languageId == -1)
if (sigDB.Rows.Count == 0) {
{ Logging.Log(Logging.LogType.Warning, "Signature Ingest", "Unable to locate language id for " + language.Trim());
// entry not present, insert it sql = "INSERT INTO Language (`Code`, `Value`) VALUES (@code, @name); SELECT CAST(LAST_INSERT_ID() AS SIGNED);";
sql = "INSERT INTO Signatures_Publishers (Publisher) VALUES (@publisher); SELECT CAST(LAST_INSERT_ID() AS SIGNED);"; Dictionary<string, object> langDict = new Dictionary<string, object>{
sigDB = db.ExecuteCMD(sql, dbDict); { "code", language.Trim() },
gamePublisher = Convert.ToInt32(sigDB.Rows[0][0]); { "name", language.Trim() }
};
languageId = int.Parse(db.ExecuteCMD(sql, langDict).Rows[0][0].ToString());
}
} }
else
if (languageId > 0)
{ {
gamePublisher = (int)sigDB.Rows[0][0]; gameLanguages.Add(languageId);
} }
} }
dbDict.Add("publisherid", gamePublisher); }
// store game dbDict.Add("copyright", Common.ReturnValueIfNull(gameObject.Copyright, ""));
int gameId = 0;
sql = "SELECT * FROM Signatures_Games WHERE Name=@name AND Year=@year AND Publisherid=@publisher AND Systemid=@systemid AND Country=@country AND Language=@language"; // store platform
int gameSystem = 0;
if (gameObject.System != null)
{
sql = "SELECT `Id` FROM Signatures_Platforms WHERE `Platform`=@platform";
sigDB = db.ExecuteCMD(sql, dbDict); sigDB = db.ExecuteCMD(sql, dbDict);
if (sigDB.Rows.Count == 0) if (sigDB.Rows.Count == 0)
{ {
// entry not present, insert it // entry not present, insert it
sql = "INSERT INTO Signatures_Games " + sql = "INSERT INTO Signatures_Platforms (`Platform`) VALUES (@platform); SELECT CAST(LAST_INSERT_ID() AS SIGNED);";
"(Name, Description, Year, PublisherId, Demo, SystemId, SystemVariant, Video, Country, Language, Copyright) VALUES " +
"(@name, @description, @year, @publisherid, @demo, @systemid, @systemvariant, @video, @country, @language, @copyright); SELECT CAST(LAST_INSERT_ID() AS SIGNED);";
sigDB = db.ExecuteCMD(sql, dbDict); sigDB = db.ExecuteCMD(sql, dbDict);
gameId = Convert.ToInt32(sigDB.Rows[0][0]); gameSystem = Convert.ToInt32(sigDB.Rows[0][0]);
} }
else else
{ {
gameId = (int)sigDB.Rows[0][0]; gameSystem = (int)sigDB.Rows[0][0];
} }
}
dbDict.Add("systemid", gameSystem);
// store rom // store publisher
foreach (RomSignatureObject.Game.Rom romObject in gameObject.Roms) int gamePublisher = 0;
if (gameObject.Publisher != null)
{
sql = "SELECT * FROM Signatures_Publishers WHERE `Publisher`=@publisher";
sigDB = db.ExecuteCMD(sql, dbDict);
if (sigDB.Rows.Count == 0)
{ {
if (romObject.Md5 != null || romObject.Sha1 != null) // entry not present, insert it
sql = "INSERT INTO Signatures_Publishers (`Publisher`) VALUES (@publisher); SELECT CAST(LAST_INSERT_ID() AS SIGNED);";
sigDB = db.ExecuteCMD(sql, dbDict);
gamePublisher = Convert.ToInt32(sigDB.Rows[0][0]);
}
else
{
gamePublisher = (int)sigDB.Rows[0][0];
}
}
dbDict.Add("publisherid", gamePublisher);
// store game
long gameId = 0;
sql = "SELECT * FROM Signatures_Games WHERE `Name`=@name AND `Year`=@year AND `PublisherId`=@publisherid AND `SystemId`=@systemid";
sigDB = db.ExecuteCMD(sql, dbDict);
if (sigDB.Rows.Count == 0)
{
// entry not present, insert it
sql = "INSERT INTO Signatures_Games " +
"(`Name`, `Description`, `Year`, `PublisherId`, `Demo`, `SystemId`, `SystemVariant`, `Video`, `Copyright`) VALUES " +
"(@name, @description, @year, @publisherid, @demo, @systemid, @systemvariant, @video, @copyright); SELECT CAST(LAST_INSERT_ID() AS SIGNED);";
sigDB = db.ExecuteCMD(sql, dbDict);
gameId = Convert.ToInt32(sigDB.Rows[0][0]);
}
else
{
gameId = (int)sigDB.Rows[0][0];
}
// insert countries
foreach (int gameCountry in gameCountries)
{
try
{
sql = "SELECT * FROM Signatures_Games_Countries WHERE GameId = @gameid AND CountryId = @Countryid";
Dictionary<string, object> countryDict = new Dictionary<string, object>{
{ "gameid", gameId },
{ "Countryid", gameCountry }
};
if (db.ExecuteCMD(sql, countryDict).Rows.Count == 0)
{ {
int romId = 0; sql = "INSERT INTO Signatures_Games_Countries (GameId, CountryId) VALUES (@gameid, @Countryid)";
sql = "SELECT * FROM Signatures_Roms WHERE GameId=@gameid AND MD5=@md5"; db.ExecuteCMD(sql, countryDict);
dbDict = new Dictionary<string, object>(); }
dbDict.Add("gameid", gameId); }
dbDict.Add("name", Common.ReturnValueIfNull(romObject.Name, "")); catch
dbDict.Add("size", Common.ReturnValueIfNull(romObject.Size, "")); {
dbDict.Add("crc", Common.ReturnValueIfNull(romObject.Crc, "").ToString().ToLower()); Console.WriteLine("Game id: " + gameId + " with Country " + gameCountry);
dbDict.Add("md5", Common.ReturnValueIfNull(romObject.Md5, "").ToString().ToLower()); }
dbDict.Add("sha1", Common.ReturnValueIfNull(romObject.Sha1, "").ToString().ToLower()); }
dbDict.Add("developmentstatus", Common.ReturnValueIfNull(romObject.DevelopmentStatus, ""));
if (romObject.Attributes != null) // insert languages
foreach (int gameLanguage in gameLanguages)
{
try
{
sql = "SELECT * FROM Signatures_Games_Languages WHERE GameId = @gameid AND LanguageId = @languageid";
Dictionary<string, object> langDict = new Dictionary<string, object>{
{ "gameid", gameId },
{ "languageid", gameLanguage }
};
if (db.ExecuteCMD(sql, langDict).Rows.Count == 0)
{
sql = "INSERT INTO Signatures_Games_Languages (GameId, LanguageId) VALUES (@gameid, @languageid)";
db.ExecuteCMD(sql, langDict);
}
}
catch
{
Console.WriteLine("Game id: " + gameId + " with language " + gameLanguage);
}
}
// store rom
foreach (RomSignatureObject.Game.Rom romObject in gameObject.Roms)
{
if (romObject.Md5 != null || romObject.Sha1 != null)
{
long romId = 0;
sql = "SELECT * FROM Signatures_Roms WHERE `GameId`=@gameid AND (`MD5`=@md5 OR `SHA1`=@sha1)";
dbDict = new Dictionary<string, object>();
dbDict.Add("gameid", gameId);
dbDict.Add("name", Common.ReturnValueIfNull(romObject.Name, ""));
dbDict.Add("size", Common.ReturnValueIfNull(romObject.Size, ""));
dbDict.Add("crc", Common.ReturnValueIfNull(romObject.Crc, "").ToString().ToLower());
dbDict.Add("md5", Common.ReturnValueIfNull(romObject.Md5, "").ToString().ToLower());
dbDict.Add("sha1", Common.ReturnValueIfNull(romObject.Sha1, "").ToString().ToLower());
dbDict.Add("developmentstatus", Common.ReturnValueIfNull(romObject.DevelopmentStatus, ""));
if (romObject.Attributes != null)
{
if (romObject.Attributes.Count > 0)
{ {
if (romObject.Attributes.Count > 0) dbDict.Add("attributes", Newtonsoft.Json.JsonConvert.SerializeObject(romObject.Attributes));
{
dbDict.Add("attributes", Newtonsoft.Json.JsonConvert.SerializeObject(romObject.Attributes));
}
else
{
dbDict.Add("attributes", "[ ]");
}
} }
else else
{ {
dbDict.Add("attributes", "[ ]"); dbDict.Add("attributes", "");
} }
dbDict.Add("romtype", (int)romObject.RomType); }
dbDict.Add("romtypemedia", Common.ReturnValueIfNull(romObject.RomTypeMedia, "")); else
dbDict.Add("medialabel", Common.ReturnValueIfNull(romObject.MediaLabel, "")); {
dbDict.Add("metadatasource", romObject.SignatureSource); dbDict.Add("attributes", "");
dbDict.Add("ingestorversion", 2); }
dbDict.Add("romtype", (int)romObject.RomType);
dbDict.Add("romtypemedia", Common.ReturnValueIfNull(romObject.RomTypeMedia, ""));
dbDict.Add("medialabel", Common.ReturnValueIfNull(romObject.MediaLabel, ""));
dbDict.Add("metadatasource", romObject.SignatureSource);
dbDict.Add("ingestorversion", 2);
sigDB = db.ExecuteCMD(sql, dbDict);
if (sigDB.Rows.Count == 0)
{
// entry not present, insert it
sql = "INSERT INTO Signatures_Roms (`GameId`, `Name`, `Size`, `CRC`, `MD5`, `SHA1`, `DevelopmentStatus`, `Attributes`, `RomType`, `RomTypeMedia`, `MediaLabel`, `MetadataSource`, `IngestorVersion`) VALUES (@gameid, @name, @size, @crc, @md5, @sha1, @developmentstatus, @attributes, @romtype, @romtypemedia, @medialabel, @metadatasource, @ingestorversion); SELECT CAST(LAST_INSERT_ID() AS SIGNED);";
sigDB = db.ExecuteCMD(sql, dbDict); sigDB = db.ExecuteCMD(sql, dbDict);
if (sigDB.Rows.Count == 0)
{
// entry not present, insert it
sql = "INSERT INTO Signatures_Roms (GameId, Name, Size, CRC, MD5, SHA1, DevelopmentStatus, Attributes, RomType, RomTypeMedia, MediaLabel, MetadataSource, IngestorVersion) VALUES (@gameid, @name, @size, @crc, @md5, @sha1, @developmentstatus, @attributes, @romtype, @romtypemedia, @medialabel, @metadatasource, @ingestorversion); SELECT CAST(LAST_INSERT_ID() AS SIGNED);";
sigDB = db.ExecuteCMD(sql, dbDict);
romId = Convert.ToInt32(sigDB.Rows[0][0]);
}
else
{
romId = (int)sigDB.Rows[0][0];
}
romId = Convert.ToInt32(sigDB.Rows[0][0]); // map the rom to the source
} sql = "SELECT * FROM Signatures_RomToSource WHERE SourceId=@sourceid AND RomId=@romid;";
else dbDict.Add("romid", romId);
{ dbDict.Add("sourceId", sourceId);
romId = (int)sigDB.Rows[0][0];
} sigDB = db.ExecuteCMD(sql, dbDict);
if (sigDB.Rows.Count == 0)
{
sql = "INSERT INTO Signatures_RomToSource (`SourceId`, `RomId`) VALUES (@sourceid, @romid);";
db.ExecuteCMD(sql, dbDict);
} }
} }
} }
} }
} }
} }
catch (Exception ex)
File.Move(XMLFile, Path.Combine(ProcessedDirectory, Path.GetFileName(XMLFile)));
if (DBFile != null)
{ {
Logging.Log(Logging.LogType.Warning, "Signature Ingestor - XML", "Invalid import file: " + XMLFile, ex); File.Move(DBFile, Path.Combine(XMLDBProcessedDirectory, Path.GetFileName(DBFile)));
} }
} }
else catch (Exception ex)
{ {
Logging.Log(Logging.LogType.Debug, "Signature Ingestor - XML", "Rejecting already imported file: " + XMLFile); Logging.Log(Logging.LogType.Warning, "Signature Ingest", "Error ingesting " + XMLType.ToString() + " file: " + XMLFile, ex);
}
}
else
{
Logging.Log(Logging.LogType.Information, "Signature Ingest", "Rejecting already imported " + XMLType.ToString() + " file: " + XMLFile);
File.Move(XMLFile, Path.Combine(ProcessedDirectory, Path.GetFileName(XMLFile)));
if (DBFile != null)
{
File.Move(DBFile, Path.Combine(XMLDBProcessedDirectory, Path.GetFileName(DBFile)));
} }
} }
} }

View File

@@ -1,4 +1,5 @@
using System.Data; using System.Data;
using gaseous_server.Models;
using gaseous_signature_parser.models.RomSignatureObject; using gaseous_signature_parser.models.RomSignatureObject;
namespace gaseous_server.Classes namespace gaseous_server.Classes
@@ -10,7 +11,8 @@ namespace gaseous_server.Classes
if (md5.Length > 0) if (md5.Length > 0)
{ {
return _GetSignature("Signatures_Roms.md5 = @searchstring", md5); return _GetSignature("Signatures_Roms.md5 = @searchstring", md5);
} else }
else
{ {
return _GetSignature("Signatures_Roms.sha1 = @searchstring", sha1); return _GetSignature("Signatures_Roms.sha1 = @searchstring", sha1);
} }
@@ -21,7 +23,8 @@ namespace gaseous_server.Classes
if (TosecName.Length > 0) if (TosecName.Length > 0)
{ {
return _GetSignature("Signatures_Roms.name = @searchstring", TosecName); return _GetSignature("Signatures_Roms.name = @searchstring", TosecName);
} else }
else
{ {
return null; return null;
} }
@@ -53,8 +56,8 @@ namespace gaseous_server.Classes
System = (string)sigDbRow["Platform"], System = (string)sigDbRow["Platform"],
SystemVariant = (string)sigDbRow["SystemVariant"], SystemVariant = (string)sigDbRow["SystemVariant"],
Video = (string)sigDbRow["Video"], Video = (string)sigDbRow["Video"],
Country = (string)sigDbRow["Country"], Country = "",
Language = (string)sigDbRow["Language"], Language = "",
Copyright = (string)sigDbRow["Copyright"] Copyright = (string)sigDbRow["Copyright"]
}, },
Rom = new gaseous_server.Models.Signatures_Games.RomItem Rom = new gaseous_server.Models.Signatures_Games.RomItem
@@ -66,16 +69,66 @@ namespace gaseous_server.Classes
Md5 = ((string)sigDbRow["MD5"]).ToLower(), Md5 = ((string)sigDbRow["MD5"]).ToLower(),
Sha1 = ((string)sigDbRow["SHA1"]).ToLower(), Sha1 = ((string)sigDbRow["SHA1"]).ToLower(),
DevelopmentStatus = (string)sigDbRow["DevelopmentStatus"], DevelopmentStatus = (string)sigDbRow["DevelopmentStatus"],
Attributes = Newtonsoft.Json.JsonConvert.DeserializeObject<List<KeyValuePair<string, object>>>((string)Common.ReturnValueIfNull(sigDbRow["Attributes"], "[]")),
RomType = (gaseous_server.Models.Signatures_Games.RomItem.RomTypes)(int)sigDbRow["RomType"], RomType = (gaseous_server.Models.Signatures_Games.RomItem.RomTypes)(int)sigDbRow["RomType"],
RomTypeMedia = (string)sigDbRow["RomTypeMedia"], RomTypeMedia = (string)sigDbRow["RomTypeMedia"],
MediaLabel = (string)sigDbRow["MediaLabel"], MediaLabel = (string)sigDbRow["MediaLabel"],
SignatureSource = (gaseous_server.Models.Signatures_Games.RomItem.SignatureSourceType)(Int32)sigDbRow["MetadataSource"] SignatureSource = (gaseous_server.Models.Signatures_Games.RomItem.SignatureSourceType)(Int32)sigDbRow["MetadataSource"]
} }
}; };
string attributeValues = (string)Common.ReturnValueIfNull(sigDbRow["Attributes"], "[]");
Dictionary<string, object> attributesDict = Newtonsoft.Json.JsonConvert.DeserializeObject<Dictionary<string, object>>(attributeValues);
if (attributesDict != null)
{
gameItem.Rom.Attributes = [.. attributesDict];
}
else
{
gameItem.Rom.Attributes = new List<KeyValuePair<string, object>>();
}
GamesList.Add(gameItem); GamesList.Add(gameItem);
} }
return GamesList; return GamesList;
} }
public List<Signatures_Sources> GetSources()
{
Database db = new Database(Database.databaseType.MySql, Config.DatabaseConfiguration.ConnectionString);
string sql = "SELECT * FROM Signatures_Sources ORDER BY `SourceType`, `Name`;";
DataTable sigDb = db.ExecuteCMD(sql);
List<Signatures_Sources> SourcesList = new List<Signatures_Sources>();
foreach (DataRow sigDbRow in sigDb.Rows)
{
Signatures_Sources sourceItem = new Signatures_Sources
{
Id = (int)sigDbRow["Id"],
Name = (string)sigDbRow["Name"],
Description = (string)sigDbRow["Description"],
URL = (string)sigDbRow["URL"],
Category = (string)sigDbRow["Category"],
Version = (string)sigDbRow["Version"],
Author = (string)sigDbRow["Author"],
Email = (string)sigDbRow["Email"],
Homepage = (string)sigDbRow["Homepage"],
SourceType = (gaseous_signature_parser.parser.SignatureParser)Enum.Parse(typeof(gaseous_signature_parser.parser.SignatureParser), sigDbRow["SourceType"].ToString()),
MD5 = (string)sigDbRow["SourceMD5"],
SHA1 = (string)sigDbRow["SourceSHA1"]
};
SourcesList.Add(sourceItem);
}
return SourcesList;
}
public void DeleteSource(int sourceId)
{
Database db = new Database(Database.databaseType.MySql, Config.DatabaseConfiguration.ConnectionString);
string sql = "DELETE FROM Signatures_Sources WHERE Id = @sourceId;";
Dictionary<string, object> dbDict = new Dictionary<string, object>
{
{ "sourceId", sourceId }
};
db.ExecuteCMD(sql, dbDict);
}
} }
} }

View File

@@ -15,7 +15,7 @@ namespace gaseous_server.Controllers
{ {
[ApiController] [ApiController]
[Route("api/v{version:apiVersion}/[controller]")] [Route("api/v{version:apiVersion}/[controller]")]
[ApiVersion("1.0")] [ApiVersion("1.0", Deprecated = true)]
[ApiVersion("1.1")] [ApiVersion("1.1")]
[Authorize] [Authorize]
public class AccountController : Controller public class AccountController : Controller

View File

@@ -10,7 +10,7 @@ namespace gaseous_server.Controllers
{ {
[ApiController] [ApiController]
[Route("api/v{version:apiVersion}/[controller]")] [Route("api/v{version:apiVersion}/[controller]")]
[ApiVersion("1.0")] [ApiVersion("1.0", Deprecated = true)]
[ApiVersion("1.1")] [ApiVersion("1.1")]
[Authorize(Roles = "Admin,Gamer,Player")] [Authorize(Roles = "Admin,Gamer,Player")]
public class BackgroundTasksController : Controller public class BackgroundTasksController : Controller

View File

@@ -12,7 +12,7 @@ namespace gaseous_server.Controllers
{ {
[ApiController] [ApiController]
[Route("api/v{version:apiVersion}/[controller]")] [Route("api/v{version:apiVersion}/[controller]")]
[ApiVersion("1.0")] [ApiVersion("1.0", Deprecated = true)]
[ApiVersion("1.1")] [ApiVersion("1.1")]
[Authorize] [Authorize]
public class BiosController : Controller public class BiosController : Controller

View File

@@ -14,7 +14,7 @@ namespace gaseous_server.Controllers
{ {
[ApiController] [ApiController]
[Route("api/v{version:apiVersion}/[controller]")] [Route("api/v{version:apiVersion}/[controller]")]
[ApiVersion("1.0")] [ApiVersion("1.0", Deprecated = true)]
[ApiVersion("1.1")] [ApiVersion("1.1")]
[Authorize] [Authorize]
public class CollectionsController : Controller public class CollectionsController : Controller
@@ -29,7 +29,7 @@ namespace gaseous_server.Controllers
_userManager = userManager; _userManager = userManager;
_signInManager = signInManager; _signInManager = signInManager;
} }
/// <summary> /// <summary>
/// Gets all ROM collections /// Gets all ROM collections
/// </summary> /// </summary>
@@ -145,7 +145,7 @@ namespace gaseous_server.Controllers
} }
catch (Exception ex) catch (Exception ex)
{ {
return NotFound(ex); return NotFound(ex);
} }
} }
else else
@@ -212,7 +212,7 @@ namespace gaseous_server.Controllers
public async Task<ActionResult> NewCollectionAsync(Classes.Collections.CollectionItem Item) public async Task<ActionResult> NewCollectionAsync(Classes.Collections.CollectionItem Item)
{ {
var user = await _userManager.GetUserAsync(User); var user = await _userManager.GetUserAsync(User);
if (user != null) if (user != null)
{ {
try try
@@ -246,7 +246,7 @@ namespace gaseous_server.Controllers
public async Task<ActionResult> EditCollection(long CollectionId, Classes.Collections.CollectionItem Item) public async Task<ActionResult> EditCollection(long CollectionId, Classes.Collections.CollectionItem Item)
{ {
var user = await _userManager.GetUserAsync(User); var user = await _userManager.GetUserAsync(User);
if (user != null) if (user != null)
{ {
try try
@@ -277,10 +277,10 @@ namespace gaseous_server.Controllers
[Route("{CollectionId}/AlwaysInclude")] [Route("{CollectionId}/AlwaysInclude")]
[ProducesResponseType(typeof(Classes.Collections.CollectionItem), StatusCodes.Status200OK)] [ProducesResponseType(typeof(Classes.Collections.CollectionItem), StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status404NotFound)] [ProducesResponseType(StatusCodes.Status404NotFound)]
public async Task<ActionResult> EditCollectionAlwaysInclude(long CollectionId, [FromQuery]bool Rebuild, [FromBody]Collections.CollectionItem.AlwaysIncludeItem Inclusion) public async Task<ActionResult> EditCollectionAlwaysInclude(long CollectionId, [FromQuery] bool Rebuild, [FromBody] Collections.CollectionItem.AlwaysIncludeItem Inclusion)
{ {
var user = await _userManager.GetUserAsync(User); var user = await _userManager.GetUserAsync(User);
if (user != null) if (user != null)
{ {
try try
@@ -326,7 +326,7 @@ namespace gaseous_server.Controllers
public async Task<ActionResult> DeleteCollection(long CollectionId) public async Task<ActionResult> DeleteCollection(long CollectionId)
{ {
var user = await _userManager.GetUserAsync(User); var user = await _userManager.GetUserAsync(User);
if (user != null) if (user != null)
{ {
try try

View File

@@ -15,7 +15,7 @@ using Asp.Versioning;
namespace gaseous_server.Controllers namespace gaseous_server.Controllers
{ {
[Route("api/v{version:apiVersion}/[controller]")] [Route("api/v{version:apiVersion}/[controller]")]
[ApiVersion("1.0")] [ApiVersion("1.0", Deprecated = true)]
[ApiVersion("1.1")] [ApiVersion("1.1")]
[Authorize] [Authorize]
[ApiController] [ApiController]
@@ -23,7 +23,7 @@ namespace gaseous_server.Controllers
{ {
private readonly UserManager<ApplicationUser> _userManager; private readonly UserManager<ApplicationUser> _userManager;
private readonly SignInManager<ApplicationUser> _signInManager; private readonly SignInManager<ApplicationUser> _signInManager;
public FilterController( public FilterController(
UserManager<ApplicationUser> userManager, UserManager<ApplicationUser> userManager,
SignInManager<ApplicationUser> signInManager) SignInManager<ApplicationUser> signInManager)
@@ -40,7 +40,7 @@ namespace gaseous_server.Controllers
public async Task<IActionResult> FilterAsync() public async Task<IActionResult> FilterAsync()
{ {
var user = await _userManager.GetUserAsync(User); var user = await _userManager.GetUserAsync(User);
return Ok(Filters.Filter(user.SecurityProfile.AgeRestrictionPolicy.MaximumAgeRestriction, user.SecurityProfile.AgeRestrictionPolicy.IncludeUnrated)); return Ok(Filters.Filter(user.SecurityProfile.AgeRestrictionPolicy.MaximumAgeRestriction, user.SecurityProfile.AgeRestrictionPolicy.IncludeUnrated));
} }
} }

View File

@@ -22,7 +22,7 @@ using Asp.Versioning;
namespace gaseous_server.Controllers namespace gaseous_server.Controllers
{ {
[Route("api/v{version:apiVersion}/[controller]")] [Route("api/v{version:apiVersion}/[controller]")]
[ApiVersion("1.0")] [ApiVersion("1.0", Deprecated = true)]
[ApiVersion("1.1")] [ApiVersion("1.1")]
[Authorize] [Authorize]
[ApiController] [ApiController]
@@ -30,7 +30,7 @@ namespace gaseous_server.Controllers
{ {
private readonly UserManager<ApplicationUser> _userManager; private readonly UserManager<ApplicationUser> _userManager;
private readonly SignInManager<ApplicationUser> _signInManager; private readonly SignInManager<ApplicationUser> _signInManager;
public GamesController( public GamesController(
UserManager<ApplicationUser> userManager, UserManager<ApplicationUser> userManager,
SignInManager<ApplicationUser> signInManager SignInManager<ApplicationUser> signInManager
@@ -43,7 +43,7 @@ namespace gaseous_server.Controllers
[MapToApiVersion("1.0")] [MapToApiVersion("1.0")]
[HttpGet] [HttpGet]
[ProducesResponseType(typeof(List<Game>), StatusCodes.Status200OK)] [ProducesResponseType(typeof(List<Game>), StatusCodes.Status200OK)]
public ActionResult Game( public async Task<ActionResult> Game(
string name = "", string name = "",
string platform = "", string platform = "",
string genre = "", string genre = "",
@@ -53,7 +53,7 @@ namespace gaseous_server.Controllers
int minrating = -1, int minrating = -1,
int maxrating = -1, int maxrating = -1,
bool sortdescending = false) bool sortdescending = false)
{ {
return Ok(GetGames(name, platform, genre, gamemode, playerperspective, theme, minrating, maxrating, "Adult", true, true, sortdescending)); return Ok(GetGames(name, platform, genre, gamemode, playerperspective, theme, minrating, maxrating, "Adult", true, true, sortdescending));
} }
@@ -303,7 +303,7 @@ namespace gaseous_server.Controllers
[ProducesResponseType(typeof(Game), StatusCodes.Status200OK)] [ProducesResponseType(typeof(Game), StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status404NotFound)] [ProducesResponseType(StatusCodes.Status404NotFound)]
[ResponseCache(CacheProfileName = "5Minute")] [ResponseCache(CacheProfileName = "5Minute")]
public ActionResult Game(long GameId) public async Task<ActionResult> Game(long GameId)
{ {
try try
{ {
@@ -331,7 +331,7 @@ namespace gaseous_server.Controllers
[ProducesResponseType(typeof(List<AlternativeName>), StatusCodes.Status200OK)] [ProducesResponseType(typeof(List<AlternativeName>), StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status404NotFound)] [ProducesResponseType(StatusCodes.Status404NotFound)]
[ResponseCache(CacheProfileName = "7Days")] [ResponseCache(CacheProfileName = "7Days")]
public ActionResult GameAlternativeNames(long GameId) public async Task<ActionResult> GameAlternativeNames(long GameId)
{ {
try try
{ {
@@ -364,7 +364,7 @@ namespace gaseous_server.Controllers
[ProducesResponseType(typeof(List<AgeRatings.GameAgeRating>), StatusCodes.Status200OK)] [ProducesResponseType(typeof(List<AgeRatings.GameAgeRating>), StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status404NotFound)] [ProducesResponseType(StatusCodes.Status404NotFound)]
[ResponseCache(CacheProfileName = "7Days")] [ResponseCache(CacheProfileName = "7Days")]
public ActionResult GameAgeClassification(long GameId) public async Task<ActionResult> GameAgeClassification(long GameId)
{ {
try try
{ {
@@ -397,7 +397,7 @@ namespace gaseous_server.Controllers
[ProducesResponseType(typeof(List<Artwork>), StatusCodes.Status200OK)] [ProducesResponseType(typeof(List<Artwork>), StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status404NotFound)] [ProducesResponseType(StatusCodes.Status404NotFound)]
[ResponseCache(CacheProfileName = "7Days")] [ResponseCache(CacheProfileName = "7Days")]
public ActionResult GameArtwork(long GameId) public async Task<ActionResult> GameArtwork(long GameId)
{ {
try try
{ {
@@ -428,7 +428,7 @@ namespace gaseous_server.Controllers
[ProducesResponseType(typeof(Artwork), StatusCodes.Status200OK)] [ProducesResponseType(typeof(Artwork), StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status404NotFound)] [ProducesResponseType(StatusCodes.Status404NotFound)]
[ResponseCache(CacheProfileName = "7Days")] [ResponseCache(CacheProfileName = "7Days")]
public ActionResult GameArtwork(long GameId, long ArtworkId) public async Task<ActionResult> GameArtwork(long GameId, long ArtworkId)
{ {
try try
{ {
@@ -464,7 +464,7 @@ namespace gaseous_server.Controllers
[Route("{GameId}/artwork/{ArtworkId}/image/{size}/{ImageName}")] [Route("{GameId}/artwork/{ArtworkId}/image/{size}/{ImageName}")]
[ProducesResponseType(typeof(FileStreamResult), StatusCodes.Status200OK)] [ProducesResponseType(typeof(FileStreamResult), StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status404NotFound)] [ProducesResponseType(StatusCodes.Status404NotFound)]
public ActionResult GameCoverImage(long GameId, long ArtworkId, Communications.IGDBAPI_ImageSize size, string ImageName) public async Task<ActionResult> GameCoverImage(long GameId, long ArtworkId, Communications.IGDBAPI_ImageSize size, string ImageName)
{ {
try try
{ {
@@ -473,14 +473,15 @@ namespace gaseous_server.Controllers
try try
{ {
IGDB.Models.Artwork artworkObject = Artworks.GetArtwork(ArtworkId, Config.LibraryConfiguration.LibraryMetadataDirectory_Game(gameObject), true); IGDB.Models.Artwork artworkObject = Artworks.GetArtwork(ArtworkId, Config.LibraryConfiguration.LibraryMetadataDirectory_Game(gameObject), true);
if (artworkObject != null) { if (artworkObject != null)
{
//string coverFilePath = Path.Combine(Config.LibraryConfiguration.LibraryMetadataDirectory_Game(gameObject), "Artwork", size.ToString(), artworkObject.ImageId + ".jpg"); //string coverFilePath = Path.Combine(Config.LibraryConfiguration.LibraryMetadataDirectory_Game(gameObject), "Artwork", size.ToString(), artworkObject.ImageId + ".jpg");
string basePath = Path.Combine(Config.LibraryConfiguration.LibraryMetadataDirectory_Game(gameObject), "Artwork"); string basePath = Path.Combine(Config.LibraryConfiguration.LibraryMetadataDirectory_Game(gameObject), "Artwork");
Communications comms = new Communications(); Communications comms = new Communications();
Task<string> ImgFetch = comms.GetSpecificImageFromServer(basePath, artworkObject.ImageId, size, new List<Communications.IGDBAPI_ImageSize>{ Communications.IGDBAPI_ImageSize.original }); Task<string> ImgFetch = comms.GetSpecificImageFromServer(basePath, artworkObject.ImageId, size, new List<Communications.IGDBAPI_ImageSize> { Communications.IGDBAPI_ImageSize.original });
string coverFilePath = ImgFetch.Result; string coverFilePath = ImgFetch.Result;
@@ -531,7 +532,7 @@ namespace gaseous_server.Controllers
[ProducesResponseType(typeof(Cover), StatusCodes.Status200OK)] [ProducesResponseType(typeof(Cover), StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status404NotFound)] [ProducesResponseType(StatusCodes.Status404NotFound)]
[ResponseCache(CacheProfileName = "7Days")] [ResponseCache(CacheProfileName = "7Days")]
public ActionResult GameCover(long GameId) public async Task<ActionResult> GameCover(long GameId)
{ {
try try
{ {
@@ -566,7 +567,7 @@ namespace gaseous_server.Controllers
[Route("{GameId}/cover/image/{size}/{imagename}")] [Route("{GameId}/cover/image/{size}/{imagename}")]
[ProducesResponseType(typeof(FileStreamResult), StatusCodes.Status200OK)] [ProducesResponseType(typeof(FileStreamResult), StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status404NotFound)] [ProducesResponseType(StatusCodes.Status404NotFound)]
public ActionResult GameCoverImage(long GameId, Communications.IGDBAPI_ImageSize size, string imagename = "") public async Task<ActionResult> GameCoverImage(long GameId, Communications.IGDBAPI_ImageSize size, string imagename = "")
{ {
try try
{ {
@@ -578,13 +579,14 @@ namespace gaseous_server.Controllers
{ {
IGDB.Models.Cover cover = Classes.Metadata.Covers.GetCover(gameObject.Cover.Id, Config.LibraryConfiguration.LibraryMetadataDirectory_Game(gameObject), false); IGDB.Models.Cover cover = Classes.Metadata.Covers.GetCover(gameObject.Cover.Id, Config.LibraryConfiguration.LibraryMetadataDirectory_Game(gameObject), false);
string basePath = Path.Combine(Config.LibraryConfiguration.LibraryMetadataDirectory_Game(gameObject), "Covers"); string basePath = Path.Combine(Config.LibraryConfiguration.LibraryMetadataDirectory_Game(gameObject), "Covers");
Communications comms = new Communications(); Communications comms = new Communications();
Task<string> ImgFetch = comms.GetSpecificImageFromServer(basePath, cover.ImageId, size, new List<Communications.IGDBAPI_ImageSize>{ Communications.IGDBAPI_ImageSize.cover_big, Communications.IGDBAPI_ImageSize.original }); Task<string> ImgFetch = comms.GetSpecificImageFromServer(basePath, cover.ImageId, size, new List<Communications.IGDBAPI_ImageSize> { Communications.IGDBAPI_ImageSize.cover_big, Communications.IGDBAPI_ImageSize.original });
string coverFilePath = ImgFetch.Result; string coverFilePath = ImgFetch.Result;
if (System.IO.File.Exists(coverFilePath)) { if (System.IO.File.Exists(coverFilePath))
{
string filename = cover.ImageId + ".jpg"; string filename = cover.ImageId + ".jpg";
string filepath = coverFilePath; string filepath = coverFilePath;
byte[] filedata = System.IO.File.ReadAllBytes(filepath); byte[] filedata = System.IO.File.ReadAllBytes(filepath);
@@ -627,7 +629,7 @@ namespace gaseous_server.Controllers
if (gameObject != null) if (gameObject != null)
{ {
var user = await _userManager.GetUserAsync(User); var user = await _userManager.GetUserAsync(User);
if (user != null) if (user != null)
{ {
Favourites favourites = new Favourites(); Favourites favourites = new Favourites();
@@ -664,7 +666,7 @@ namespace gaseous_server.Controllers
if (gameObject != null) if (gameObject != null)
{ {
var user = await _userManager.GetUserAsync(User); var user = await _userManager.GetUserAsync(User);
if (user != null) if (user != null)
{ {
Favourites favourites = new Favourites(); Favourites favourites = new Favourites();
@@ -693,7 +695,7 @@ namespace gaseous_server.Controllers
[ProducesResponseType(typeof(List<Genre>), StatusCodes.Status200OK)] [ProducesResponseType(typeof(List<Genre>), StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status404NotFound)] [ProducesResponseType(StatusCodes.Status404NotFound)]
[ResponseCache(CacheProfileName = "7Days")] [ResponseCache(CacheProfileName = "7Days")]
public ActionResult GameGenre(long GameId) public async Task<ActionResult> GameGenre(long GameId)
{ {
try try
{ {
@@ -731,7 +733,7 @@ namespace gaseous_server.Controllers
[ProducesResponseType(typeof(List<Dictionary<string, object>>), StatusCodes.Status200OK)] [ProducesResponseType(typeof(List<Dictionary<string, object>>), StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status404NotFound)] [ProducesResponseType(StatusCodes.Status404NotFound)]
[ResponseCache(CacheProfileName = "7Days")] [ResponseCache(CacheProfileName = "7Days")]
public ActionResult GameInvolvedCompanies(long GameId) public async Task<ActionResult> GameInvolvedCompanies(long GameId)
{ {
try try
{ {
@@ -776,7 +778,7 @@ namespace gaseous_server.Controllers
[ProducesResponseType(typeof(Dictionary<string, object>), StatusCodes.Status200OK)] [ProducesResponseType(typeof(Dictionary<string, object>), StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status404NotFound)] [ProducesResponseType(StatusCodes.Status404NotFound)]
[ResponseCache(CacheProfileName = "7Days")] [ResponseCache(CacheProfileName = "7Days")]
public ActionResult GameInvolvedCompanies(long GameId, long CompanyId) public async Task<ActionResult> GameInvolvedCompanies(long GameId, long CompanyId)
{ {
try try
{ {
@@ -796,7 +798,8 @@ namespace gaseous_server.Controllers
companyData.Add("company", company); companyData.Add("company", company);
return Ok(companyData); return Ok(companyData);
} else }
else
{ {
return NotFound(); return NotFound();
} }
@@ -818,7 +821,7 @@ namespace gaseous_server.Controllers
[Route("{GameId}/companies/{CompanyId}/image")] [Route("{GameId}/companies/{CompanyId}/image")]
[ProducesResponseType(typeof(FileStreamResult), StatusCodes.Status200OK)] [ProducesResponseType(typeof(FileStreamResult), StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status404NotFound)] [ProducesResponseType(StatusCodes.Status404NotFound)]
public ActionResult GameCompanyImage(long GameId, long CompanyId) public async Task<ActionResult> GameCompanyImage(long GameId, long CompanyId)
{ {
try try
{ {
@@ -863,7 +866,7 @@ namespace gaseous_server.Controllers
[Route("{GameId}/platforms")] [Route("{GameId}/platforms")]
[ProducesResponseType(typeof(List<KeyValuePair<long, string>>), StatusCodes.Status200OK)] [ProducesResponseType(typeof(List<KeyValuePair<long, string>>), StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status404NotFound)] [ProducesResponseType(StatusCodes.Status404NotFound)]
public ActionResult GamePlatforms(long GameId) public async Task<ActionResult> GamePlatforms(long GameId)
{ {
try try
{ {
@@ -882,7 +885,7 @@ namespace gaseous_server.Controllers
[ProducesResponseType(typeof(List<ReleaseDate>), StatusCodes.Status200OK)] [ProducesResponseType(typeof(List<ReleaseDate>), StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status404NotFound)] [ProducesResponseType(StatusCodes.Status404NotFound)]
[ResponseCache(CacheProfileName = "7Days")] [ResponseCache(CacheProfileName = "7Days")]
public ActionResult GameReleaseDates(long GameId) public async Task<ActionResult> GameReleaseDates(long GameId)
{ {
try try
{ {
@@ -895,7 +898,7 @@ namespace gaseous_server.Controllers
foreach (long icId in gameObject.ReleaseDates.Ids) foreach (long icId in gameObject.ReleaseDates.Ids)
{ {
ReleaseDate releaseDate = Classes.Metadata.ReleaseDates.GetReleaseDates(icId); ReleaseDate releaseDate = Classes.Metadata.ReleaseDates.GetReleaseDates(icId);
rdObjects.Add(releaseDate); rdObjects.Add(releaseDate);
} }
} }
@@ -923,7 +926,7 @@ namespace gaseous_server.Controllers
public async Task<ActionResult> GameRomAsync(long GameId, int pageNumber = 0, int pageSize = 0, long PlatformId = -1, string NameSearch = "") public async Task<ActionResult> GameRomAsync(long GameId, int pageNumber = 0, int pageSize = 0, long PlatformId = -1, string NameSearch = "")
{ {
var user = await _userManager.GetUserAsync(User); var user = await _userManager.GetUserAsync(User);
try try
{ {
Game gameObject = Classes.Metadata.Games.GetGame(GameId, false, false, false); Game gameObject = Classes.Metadata.Games.GetGame(GameId, false, false, false);
@@ -943,7 +946,7 @@ namespace gaseous_server.Controllers
[ProducesResponseType(typeof(Classes.Roms.GameRomItem), StatusCodes.Status200OK)] [ProducesResponseType(typeof(Classes.Roms.GameRomItem), StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status404NotFound)] [ProducesResponseType(StatusCodes.Status404NotFound)]
//[ResponseCache(CacheProfileName = "5Minute")] //[ResponseCache(CacheProfileName = "5Minute")]
public ActionResult GameRom(long GameId, long RomId) public async Task<ActionResult> GameRom(long GameId, long RomId)
{ {
try try
{ {
@@ -972,7 +975,7 @@ namespace gaseous_server.Controllers
[Route("{GameId}/roms/{RomId}")] [Route("{GameId}/roms/{RomId}")]
[ProducesResponseType(typeof(Classes.Roms.GameRomItem), StatusCodes.Status200OK)] [ProducesResponseType(typeof(Classes.Roms.GameRomItem), StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status404NotFound)] [ProducesResponseType(StatusCodes.Status404NotFound)]
public ActionResult GameRomRename(long GameId, long RomId, long NewPlatformId, long NewGameId) public async Task<ActionResult> GameRomRename(long GameId, long RomId, long NewPlatformId, long NewGameId)
{ {
try try
{ {
@@ -1002,7 +1005,7 @@ namespace gaseous_server.Controllers
[Route("{GameId}/roms/{RomId}")] [Route("{GameId}/roms/{RomId}")]
[ProducesResponseType(typeof(Classes.Roms.GameRomItem), StatusCodes.Status200OK)] [ProducesResponseType(typeof(Classes.Roms.GameRomItem), StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status404NotFound)] [ProducesResponseType(StatusCodes.Status404NotFound)]
public ActionResult GameRomDelete(long GameId, long RomId) public async Task<ActionResult> GameRomDelete(long GameId, long RomId)
{ {
try try
{ {
@@ -1034,7 +1037,7 @@ namespace gaseous_server.Controllers
[Route("{GameId}/roms/{RomId}/file")] [Route("{GameId}/roms/{RomId}/file")]
[ProducesResponseType(typeof(FileStreamResult), StatusCodes.Status200OK)] [ProducesResponseType(typeof(FileStreamResult), StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status404NotFound)] [ProducesResponseType(StatusCodes.Status404NotFound)]
public ActionResult GameRomFile(long GameId, long RomId) public async Task<ActionResult> GameRomFile(long GameId, long RomId)
{ {
try try
{ {
@@ -1073,7 +1076,7 @@ namespace gaseous_server.Controllers
[Route("{GameId}/roms/{RomId}/{FileName}")] [Route("{GameId}/roms/{RomId}/{FileName}")]
[ProducesResponseType(typeof(FileStreamResult), StatusCodes.Status200OK)] [ProducesResponseType(typeof(FileStreamResult), StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status404NotFound)] [ProducesResponseType(StatusCodes.Status404NotFound)]
public ActionResult GameRomFile(long GameId, long RomId, string FileName) public async Task<ActionResult> GameRomFile(long GameId, long RomId, string FileName)
{ {
try try
{ {
@@ -1113,7 +1116,7 @@ namespace gaseous_server.Controllers
public async Task<ActionResult> GameRomGroupAsync(long GameId, long RomGroupId) public async Task<ActionResult> GameRomGroupAsync(long GameId, long RomGroupId)
{ {
var user = await _userManager.GetUserAsync(User); var user = await _userManager.GetUserAsync(User);
try try
{ {
Game gameObject = Classes.Metadata.Games.GetGame(GameId, false, false, false); Game gameObject = Classes.Metadata.Games.GetGame(GameId, false, false, false);
@@ -1144,7 +1147,7 @@ namespace gaseous_server.Controllers
public async Task<ActionResult> GetGameRomGroupAsync(long GameId) public async Task<ActionResult> GetGameRomGroupAsync(long GameId)
{ {
var user = await _userManager.GetUserAsync(User); var user = await _userManager.GetUserAsync(User);
try try
{ {
Game gameObject = Classes.Metadata.Games.GetGame(GameId, false, false, false); Game gameObject = Classes.Metadata.Games.GetGame(GameId, false, false, false);
@@ -1172,7 +1175,7 @@ namespace gaseous_server.Controllers
[Route("{GameId}/romgroup")] [Route("{GameId}/romgroup")]
[ProducesResponseType(typeof(Classes.RomMediaGroup.GameRomMediaGroupItem), StatusCodes.Status200OK)] [ProducesResponseType(typeof(Classes.RomMediaGroup.GameRomMediaGroupItem), StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status404NotFound)] [ProducesResponseType(StatusCodes.Status404NotFound)]
public ActionResult NewGameRomGroup(long GameId, long PlatformId, [FromBody] List<long> RomIds) public async Task<ActionResult> NewGameRomGroup(long GameId, long PlatformId, [FromBody] List<long> RomIds)
{ {
try try
{ {
@@ -1204,7 +1207,7 @@ namespace gaseous_server.Controllers
public async Task<ActionResult> GameRomGroupMembersAsync(long GameId, long RomGroupId, [FromBody] List<long> RomIds) public async Task<ActionResult> GameRomGroupMembersAsync(long GameId, long RomGroupId, [FromBody] List<long> RomIds)
{ {
var user = await _userManager.GetUserAsync(User); var user = await _userManager.GetUserAsync(User);
try try
{ {
Game gameObject = Classes.Metadata.Games.GetGame(GameId, false, false, false); Game gameObject = Classes.Metadata.Games.GetGame(GameId, false, false, false);
@@ -1233,7 +1236,7 @@ namespace gaseous_server.Controllers
[Route("{GameId}/romgroup/{RomGroupId}")] [Route("{GameId}/romgroup/{RomGroupId}")]
[ProducesResponseType(typeof(Classes.RomMediaGroup.GameRomMediaGroupItem), StatusCodes.Status200OK)] [ProducesResponseType(typeof(Classes.RomMediaGroup.GameRomMediaGroupItem), StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status404NotFound)] [ProducesResponseType(StatusCodes.Status404NotFound)]
public ActionResult GameRomGroupDelete(long GameId, long RomGroupId) public async Task<ActionResult> GameRomGroupDelete(long GameId, long RomGroupId)
{ {
try try
{ {
@@ -1266,7 +1269,7 @@ namespace gaseous_server.Controllers
[Route("{GameId}/romgroup/{RomGroupId}/{filename}")] [Route("{GameId}/romgroup/{RomGroupId}/{filename}")]
[ProducesResponseType(typeof(FileStreamResult), StatusCodes.Status200OK)] [ProducesResponseType(typeof(FileStreamResult), StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status404NotFound)] [ProducesResponseType(StatusCodes.Status404NotFound)]
public ActionResult GameRomGroupFile(long GameId, long RomGroupId, string filename = "") public async Task<ActionResult> GameRomGroupFile(long GameId, long RomGroupId, string filename = "")
{ {
try try
{ {
@@ -1311,7 +1314,7 @@ namespace gaseous_server.Controllers
[Route("search")] [Route("search")]
[ProducesResponseType(typeof(List<Game>), StatusCodes.Status200OK)] [ProducesResponseType(typeof(List<Game>), StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status404NotFound)] [ProducesResponseType(StatusCodes.Status404NotFound)]
public ActionResult GameSearch(long RomId = 0, string SearchString = "") public async Task<ActionResult> GameSearch(long RomId = 0, string SearchString = "")
{ {
try try
{ {
@@ -1352,7 +1355,7 @@ namespace gaseous_server.Controllers
[ProducesResponseType(typeof(List<Screenshot>), StatusCodes.Status200OK)] [ProducesResponseType(typeof(List<Screenshot>), StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status404NotFound)] [ProducesResponseType(StatusCodes.Status404NotFound)]
[ResponseCache(CacheProfileName = "7Days")] [ResponseCache(CacheProfileName = "7Days")]
public ActionResult GameScreenshot(long GameId) public async Task<ActionResult> GameScreenshot(long GameId)
{ {
try try
{ {
@@ -1383,12 +1386,13 @@ namespace gaseous_server.Controllers
[ProducesResponseType(typeof(Screenshot), StatusCodes.Status200OK)] [ProducesResponseType(typeof(Screenshot), StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status404NotFound)] [ProducesResponseType(StatusCodes.Status404NotFound)]
[ResponseCache(CacheProfileName = "7Days")] [ResponseCache(CacheProfileName = "7Days")]
public ActionResult GameScreenshot(long GameId, long ScreenshotId) public async Task<ActionResult> GameScreenshot(long GameId, long ScreenshotId)
{ {
try try
{ {
IGDB.Models.Game gameObject = Classes.Metadata.Games.GetGame(GameId, false, false, false); IGDB.Models.Game gameObject = Classes.Metadata.Games.GetGame(GameId, false, false, false);
if (gameObject != null) { if (gameObject != null)
{
IGDB.Models.Screenshot screenshotObject = Screenshots.GetScreenshot(ScreenshotId, Config.LibraryConfiguration.LibraryMetadataDirectory_Game(gameObject), false); IGDB.Models.Screenshot screenshotObject = Screenshots.GetScreenshot(ScreenshotId, Config.LibraryConfiguration.LibraryMetadataDirectory_Game(gameObject), false);
if (screenshotObject != null) if (screenshotObject != null)
{ {
@@ -1417,7 +1421,7 @@ namespace gaseous_server.Controllers
[Route("{GameId}/screenshots/{ScreenshotId}/image/{size}/{ImageName}")] [Route("{GameId}/screenshots/{ScreenshotId}/image/{size}/{ImageName}")]
[ProducesResponseType(typeof(FileStreamResult), StatusCodes.Status200OK)] [ProducesResponseType(typeof(FileStreamResult), StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status404NotFound)] [ProducesResponseType(StatusCodes.Status404NotFound)]
public ActionResult GameScreenshotImage(long GameId, long ScreenshotId, Communications.IGDBAPI_ImageSize Size, string ImageName) public async Task<ActionResult> GameScreenshotImage(long GameId, long ScreenshotId, Communications.IGDBAPI_ImageSize Size, string ImageName)
{ {
try try
{ {
@@ -1428,7 +1432,7 @@ namespace gaseous_server.Controllers
string basePath = Path.Combine(Config.LibraryConfiguration.LibraryMetadataDirectory_Game(gameObject), "Screenshots"); string basePath = Path.Combine(Config.LibraryConfiguration.LibraryMetadataDirectory_Game(gameObject), "Screenshots");
Communications comms = new Communications(); Communications comms = new Communications();
Task<string> ImgFetch = comms.GetSpecificImageFromServer(basePath, screenshotObject.ImageId, Size, new List<Communications.IGDBAPI_ImageSize>{ Communications.IGDBAPI_ImageSize.original }); Task<string> ImgFetch = comms.GetSpecificImageFromServer(basePath, screenshotObject.ImageId, Size, new List<Communications.IGDBAPI_ImageSize> { Communications.IGDBAPI_ImageSize.original });
string coverFilePath = ImgFetch.Result; string coverFilePath = ImgFetch.Result;
@@ -1468,7 +1472,7 @@ namespace gaseous_server.Controllers
[ProducesResponseType(typeof(List<GameVideo>), StatusCodes.Status200OK)] [ProducesResponseType(typeof(List<GameVideo>), StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status404NotFound)] [ProducesResponseType(StatusCodes.Status404NotFound)]
[ResponseCache(CacheProfileName = "7Days")] [ResponseCache(CacheProfileName = "7Days")]
public ActionResult GameVideo(long GameId) public async Task<ActionResult> GameVideo(long GameId)
{ {
try try
{ {

View File

@@ -11,7 +11,7 @@ namespace gaseous_server.Controllers
{ {
[ApiController] [ApiController]
[Route("api/v{version:apiVersion}/[controller]")] [Route("api/v{version:apiVersion}/[controller]")]
[ApiVersion("1.0")] [ApiVersion("1.0", Deprecated = true)]
[ApiVersion("1.1")] [ApiVersion("1.1")]
[Authorize(Roles = "Admin")] [Authorize(Roles = "Admin")]
public class LibraryController : Controller public class LibraryController : Controller

View File

@@ -11,7 +11,7 @@ namespace gaseous_server.Controllers
{ {
[ApiController] [ApiController]
[Route("api/v{version:apiVersion}/[controller]")] [Route("api/v{version:apiVersion}/[controller]")]
[ApiVersion("1.0")] [ApiVersion("1.0", Deprecated = true)]
[ApiVersion("1.1")] [ApiVersion("1.1")]
[Authorize(Roles = "Admin")] [Authorize(Roles = "Admin")]
public class LogsController : Controller public class LogsController : Controller

View File

@@ -19,7 +19,7 @@ using Asp.Versioning;
namespace gaseous_server.Controllers namespace gaseous_server.Controllers
{ {
[Route("api/v{version:apiVersion}/[controller]")] [Route("api/v{version:apiVersion}/[controller]")]
[ApiVersion("1.0")] [ApiVersion("1.0", Deprecated = true)]
[ApiVersion("1.1")] [ApiVersion("1.1")]
[ApiController] [ApiController]
[Authorize] [Authorize]

View File

@@ -18,7 +18,7 @@ using Asp.Versioning;
namespace gaseous_server.Controllers namespace gaseous_server.Controllers
{ {
[Route("api/v{version:apiVersion}/[controller]")] [Route("api/v{version:apiVersion}/[controller]")]
[ApiVersion("1.0")] [ApiVersion("1.0", Deprecated = true)]
[ApiVersion("1.1")] [ApiVersion("1.1")]
[Authorize] [Authorize]
[ApiController] [ApiController]

View File

@@ -19,7 +19,7 @@ using Asp.Versioning;
namespace gaseous_server.Controllers namespace gaseous_server.Controllers
{ {
[Route("api/v{version:apiVersion}/[controller]")] [Route("api/v{version:apiVersion}/[controller]")]
[ApiVersion("1.0")] [ApiVersion("1.0", Deprecated = true)]
[ApiVersion("1.1")] [ApiVersion("1.1")]
[Authorize] [Authorize]
[ApiController] [ApiController]

View File

@@ -18,7 +18,7 @@ namespace gaseous_server.Controllers
{ {
[ApiController] [ApiController]
[Route("api/v{version:apiVersion}/[controller]")] [Route("api/v{version:apiVersion}/[controller]")]
[ApiVersion("1.0")] [ApiVersion("1.0", Deprecated = true)]
[ApiVersion("1.1")] [ApiVersion("1.1")]
[Authorize] [Authorize]
public class SearchController : Controller public class SearchController : Controller
@@ -73,9 +73,11 @@ 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;";
List<GaseousGame>? searchCache = Communications.GetSearchCache<List<GaseousGame>>(searchFields, searchBody); List<GaseousGame>? searchCache = Communications.GetSearchCache<List<GaseousGame>>(searchFields, searchBody);
@@ -85,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

@@ -9,6 +9,7 @@ using gaseous_signature_parser.models.RomSignatureObject;
using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc;
using Asp.Versioning; using Asp.Versioning;
using gaseous_server.Models;
// For more information on enabling MVC for empty projects, visit https://go.microsoft.com/fwlink/?LinkID=397860 // For more information on enabling MVC for empty projects, visit https://go.microsoft.com/fwlink/?LinkID=397860
@@ -16,7 +17,7 @@ namespace gaseous_server.Controllers
{ {
[ApiController] [ApiController]
[Route("api/v{version:apiVersion}/[controller]/[action]")] [Route("api/v{version:apiVersion}/[controller]/[action]")]
[ApiVersion("1.0")] [ApiVersion("1.0", Deprecated = true)]
[ApiVersion("1.1")] [ApiVersion("1.1")]
[Authorize] [Authorize]
public class SignaturesController : Controller public class SignaturesController : Controller
@@ -54,11 +55,34 @@ namespace gaseous_server.Controllers
{ {
SignatureManagement signatureManagement = new SignatureManagement(); SignatureManagement signatureManagement = new SignatureManagement();
return signatureManagement.GetByTosecName(TosecName); return signatureManagement.GetByTosecName(TosecName);
} else }
else
{ {
return null; return null;
} }
} }
[MapToApiVersion("1.0")]
[MapToApiVersion("1.1")]
[HttpGet]
[ProducesResponseType(StatusCodes.Status200OK)]
public List<Signatures_Sources> GetSignatureSources()
{
SignatureManagement signatureManagement = new SignatureManagement();
return signatureManagement.GetSources();
}
[MapToApiVersion("1.0")]
[MapToApiVersion("1.1")]
[HttpDelete]
[ProducesResponseType(StatusCodes.Status200OK)]
public IActionResult DeleteSignatureSource(int Id)
{
SignatureManagement signatureManagement = new SignatureManagement();
signatureManagement.DeleteSource(Id);
return Ok();
}
} }
} }

View File

@@ -19,7 +19,7 @@ namespace gaseous_server.Controllers
{ {
[ApiController] [ApiController]
[Route("api/v{version:apiVersion}/[controller]")] [Route("api/v{version:apiVersion}/[controller]")]
[ApiVersion("1.0")] [ApiVersion("1.0", Deprecated = true)]
[ApiVersion("1.1")] [ApiVersion("1.1")]
[Authorize] [Authorize]
public class SystemController : Controller public class SystemController : Controller
@@ -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,10 +256,17 @@ 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())),
SearchTypes = Config.ReadSetting<List<Classes.Metadata.Games.SearchType>>("DefaultSearchMethods", new List<gaseous_server.Classes.Metadata.Games.SearchType>() {
Games.SearchType.where,
Games.SearchType.wherefuzzy,
Games.SearchType.search,
Games.SearchType.searchNoPlatform
})
}; };
return Ok(systemSettingsModel); return Ok(systemSettingsModel);
@@ -273,6 +285,7 @@ namespace gaseous_server.Controllers
Config.LoggingConfiguration.AlwaysLogToDisk = model.AlwaysLogToDisk; Config.LoggingConfiguration.AlwaysLogToDisk = model.AlwaysLogToDisk;
Config.LoggingConfiguration.LogRetention = model.MinimumLogRetentionPeriod; Config.LoggingConfiguration.LogRetention = model.MinimumLogRetentionPeriod;
Config.SetSetting<string>("emulatorDebugMode", model.EmulatorDebugMode.ToString()); Config.SetSetting<string>("emulatorDebugMode", model.EmulatorDebugMode.ToString());
Config.SetSetting<List<Classes.Metadata.Games.SearchType>>("DefaultSearchMethods", model.SearchTypes);
Config.UpdateConfig(); Config.UpdateConfig();
} }
@@ -281,7 +294,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 +307,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 +367,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 +604,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 +658,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>();
@@ -710,5 +726,6 @@ namespace gaseous_server.Controllers
public bool AlwaysLogToDisk { get; set; } public bool AlwaysLogToDisk { get; set; }
public int MinimumLogRetentionPeriod { get; set; } public int MinimumLogRetentionPeriod { get; set; }
public bool EmulatorDebugMode { get; set; } public bool EmulatorDebugMode { get; set; }
public List<Classes.Metadata.Games.SearchType> SearchTypes { get; set; }
} }
} }

View File

@@ -479,6 +479,7 @@ namespace gaseous_server.Controllers.v1_1
Database db = new Database(Database.databaseType.MySql, Config.DatabaseConfiguration.ConnectionString); Database db = new Database(Database.databaseType.MySql, Config.DatabaseConfiguration.ConnectionString);
string sql = @" string sql = @"
SET SESSION sql_mode=(SELECT REPLACE(@@sql_mode,'ONLY_FULL_GROUP_BY',''));
SELECT DISTINCT SELECT DISTINCT
Game.Id, Game.Id,
Game.`Name`, Game.`Name`,
@@ -569,6 +570,7 @@ FROM
Game retGame = Storage.BuildCacheObject<Game>(new Game() , dbResponse.Rows[i]); Game retGame = Storage.BuildCacheObject<Game>(new Game() , dbResponse.Rows[i]);
Games.MinimalGameItem retMinGame = new Games.MinimalGameItem(retGame); Games.MinimalGameItem retMinGame = new Games.MinimalGameItem(retGame);
retMinGame.Index = i;
if (dbResponse.Rows[i]["RomSaveCount"] != DBNull.Value || dbResponse.Rows[i]["MediaGroupSaveCount"] != DBNull.Value) if (dbResponse.Rows[i]["RomSaveCount"] != DBNull.Value || dbResponse.Rows[i]["MediaGroupSaveCount"] != DBNull.Value)
{ {
retMinGame.HasSavedGame = true; retMinGame.HasSavedGame = true;
@@ -591,23 +593,29 @@ FROM
// build alpha list // build alpha list
Dictionary<string, int> AlphaList = new Dictionary<string, int>(); Dictionary<string, int> AlphaList = new Dictionary<string, int>();
int CurrentPage = 0; int CurrentPage = 1;
int NextPageIndex = 0; int NextPageIndex = pageSize;
for (int i = 0; i < dbResponse.Rows.Count; i++) for (int i = 0; i < dbResponse.Rows.Count; i++)
{ {
string firstChar = dbResponse.Rows[i]["NameThe"].ToString().Substring(0, 1).ToUpperInvariant(); string firstChar = dbResponse.Rows[i]["NameThe"].ToString().Substring(0, 1).ToUpperInvariant();
if (!"ABCDEFGHIJKLMNOPQRSTUVWXYZ".Contains(firstChar)) if (!"ABCDEFGHIJKLMNOPQRSTUVWXYZ".Contains(firstChar))
{ {
firstChar = "#"; if (!AlphaList.ContainsKey("#"))
{
AlphaList.Add("#", 1);
}
} }
if (!AlphaList.ContainsKey(firstChar)) else
{ {
AlphaList.Add(firstChar, CurrentPage); if (!AlphaList.ContainsKey(firstChar))
} {
if (NextPageIndex == i) AlphaList.Add(firstChar, CurrentPage);
{ }
NextPageIndex += pageSize; if (NextPageIndex == i + 1)
CurrentPage += 1; {
NextPageIndex += pageSize;
CurrentPage += 1;
}
} }
} }

View File

@@ -0,0 +1,19 @@
using Microsoft.AspNetCore.Mvc;
using Asp.Versioning;
namespace gaseous_server.Controllers.v1_1
{
[Route("api/v{version:apiVersion}/[controller]")]
[ApiVersion("1.1")]
[ApiController]
public class HealthCheckController : ControllerBase
{
[MapToApiVersion("1.1")]
[HttpGet]
[ProducesResponseType(StatusCodes.Status200OK)]
public ActionResult Healthcheck()
{
return Ok();
}
}
}

View File

@@ -6,14 +6,15 @@ 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
{ {
[Route("api/v{version:apiVersion}/[controller]")] [Route("api/v{version:apiVersion}/[controller]")]
[ApiVersion("1.0")] [ApiVersion("1.0", Deprecated = true)]
[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

@@ -9,10 +9,10 @@ using Asp.Versioning;
namespace gaseous_server.Controllers.v1_1 namespace gaseous_server.Controllers.v1_1
{ {
[Route("api/v{version:apiVersion}/[controller]")] [Route("api/v{version:apiVersion}/[controller]")]
[ApiVersion("1.0")] [ApiVersion("1.0", Deprecated = true)]
[ApiVersion("1.1")] [ApiVersion("1.1")]
[ApiController] [ApiController]
public class StatisticsController: ControllerBase public class StatisticsController : ControllerBase
{ {
private readonly UserManager<ApplicationUser> _userManager; private readonly UserManager<ApplicationUser> _userManager;
private readonly SignInManager<ApplicationUser> _signInManager; private readonly SignInManager<ApplicationUser> _signInManager;

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

@@ -4,11 +4,11 @@ using gaseous_signature_parser.models.RomSignatureObject;
namespace gaseous_server.Models namespace gaseous_server.Models
{ {
public class Signatures_Games : HasheousClient.Models.LookupResponseModel public class Signatures_Games : HasheousClient.Models.LookupResponseModel
{ {
public Signatures_Games() public Signatures_Games()
{ {
} }
public SignatureFlags Flags = new SignatureFlags(); public SignatureFlags Flags = new SignatureFlags();
@@ -20,4 +20,3 @@ namespace gaseous_server.Models
} }
} }
} }

View File

@@ -0,0 +1,20 @@
using NuGet.Protocol.Core.Types;
namespace gaseous_server.Models
{
public class Signatures_Sources
{
public int Id { get; set; }
public string Name { get; set; }
public string Description { get; set; }
public string URL { get; set; }
public string Category { get; set; }
public string Version { get; set; }
public string Author { get; set; }
public string Email { get; set; }
public string Homepage { get; set; }
public gaseous_signature_parser.parser.SignatureParser SourceType { get; set; }
public string MD5 { get; set; }
public string SHA1 { get; set; }
}
}

View File

@@ -9,8 +9,8 @@ using NuGet.Packaging;
namespace gaseous_server namespace gaseous_server
{ {
public static class ProcessQueue public static class ProcessQueue
{ {
public static List<QueueItem> QueueItems = new List<QueueItem>(); public static List<QueueItem> QueueItems = new List<QueueItem>();
public class QueueItem public class QueueItem
@@ -115,8 +115,8 @@ namespace gaseous_server
}; };
private List<QueueItemType> _Blocks = new List<QueueItemType>(); private List<QueueItemType> _Blocks = new List<QueueItemType>();
public List<DayOfWeek> AllowedDays public List<DayOfWeek> AllowedDays
{ {
get get
{ {
return _AllowedDays; return _AllowedDays;
@@ -124,7 +124,7 @@ namespace gaseous_server
set set
{ {
_AllowedDays = value; _AllowedDays = value;
} }
} }
public int AllowedStartHours { get; set; } = 0; public int AllowedStartHours { get; set; } = 0;
public int AllowedStartMinutes { get; set; } = 0; public int AllowedStartMinutes { get; set; } = 0;
@@ -135,7 +135,7 @@ namespace gaseous_server
public DateTime LastRunTime => _LastRunTime; public DateTime LastRunTime => _LastRunTime;
public DateTime LastFinishTime => _LastFinishTime; public DateTime LastFinishTime => _LastFinishTime;
public double LastRunDuration => _LastRunDuration; public double LastRunDuration => _LastRunDuration;
public DateTime NextRunTime public DateTime NextRunTime
{ {
get get
{ {
@@ -245,15 +245,33 @@ namespace gaseous_server
CallingQueueItem = this CallingQueueItem = this
}; };
Logging.Log(Logging.LogType.Debug, "Signature Import", "Processing TOSEC files"); foreach (int i in Enum.GetValues(typeof(gaseous_signature_parser.parser.SignatureParser)))
tIngest.Import(Path.Combine(Config.LibraryConfiguration.LibrarySignatureImportDirectory, "TOSEC"), gaseous_signature_parser.parser.SignatureParser.TOSEC); {
gaseous_signature_parser.parser.SignatureParser parserType = (gaseous_signature_parser.parser.SignatureParser)i;
Logging.Log(Logging.LogType.Debug, "Signature Import", "Processing MAME Arcade files"); if (
tIngest.Import(Path.Combine(Config.LibraryConfiguration.LibrarySignatureImportDirectory, "MAME Arcade"), gaseous_signature_parser.parser.SignatureParser.MAMEArcade); parserType != gaseous_signature_parser.parser.SignatureParser.Auto &&
parserType != gaseous_signature_parser.parser.SignatureParser.Unknown
)
{
Logging.Log(Logging.LogType.Debug, "Signature Import", "Processing " + parserType + " files");
string SignaturePath = Path.Combine(Config.LibraryConfiguration.LibrarySignatureImportDirectory, parserType.ToString());
string SignatureProcessedPath = Path.Combine(Config.LibraryConfiguration.LibrarySignatureImportDirectory, parserType.ToString());
if (!Directory.Exists(SignaturePath))
{
Directory.CreateDirectory(SignaturePath);
}
if (!Directory.Exists(SignatureProcessedPath))
{
Directory.CreateDirectory(SignatureProcessedPath);
}
tIngest.Import(SignaturePath, SignatureProcessedPath, parserType);
}
}
Logging.Log(Logging.LogType.Debug, "Signature Import", "Processing MAME MESS files");
tIngest.Import(Path.Combine(Config.LibraryConfiguration.LibrarySignatureImportDirectory, "MAME MESS"), gaseous_signature_parser.parser.SignatureParser.MAMEMess);
_SaveLastRunTime = true; _SaveLastRunTime = true;
break; break;
@@ -350,7 +368,8 @@ namespace gaseous_server
case QueueItemType.DailyMaintainer: case QueueItemType.DailyMaintainer:
Logging.Log(Logging.LogType.Debug, "Timered Event", "Starting Daily Maintenance"); Logging.Log(Logging.LogType.Debug, "Timered Event", "Starting Daily Maintenance");
Classes.Maintenance maintenance = new Maintenance{ Classes.Maintenance maintenance = new Maintenance
{
CallingQueueItem = this CallingQueueItem = this
}; };
maintenance.RunDailyMaintenance(); maintenance.RunDailyMaintenance();
@@ -361,7 +380,8 @@ namespace gaseous_server
case QueueItemType.WeeklyMaintainer: case QueueItemType.WeeklyMaintainer:
Logging.Log(Logging.LogType.Debug, "Timered Event", "Starting Weekly Maintenance"); Logging.Log(Logging.LogType.Debug, "Timered Event", "Starting Weekly Maintenance");
Classes.Maintenance weeklyMaintenance = new Maintenance{ Classes.Maintenance weeklyMaintenance = new Maintenance
{
CallingQueueItem = this CallingQueueItem = this
}; };
weeklyMaintenance.RunWeeklyMaintenance(); weeklyMaintenance.RunWeeklyMaintenance();

View File

@@ -36,12 +36,27 @@ 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();
// set default search settings
Config.SetSetting<List<gaseous_server.Classes.Metadata.Games.SearchType>>("DefaultSearchMethods", new List<gaseous_server.Classes.Metadata.Games.SearchType>() {
Games.SearchType.where,
Games.SearchType.wherefuzzy,
Games.SearchType.search,
Games.SearchType.searchNoPlatform
});
// 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();
@@ -121,7 +136,7 @@ builder.Services.AddControllers(options =>
}); });
builder.Services.AddApiVersioning(config => builder.Services.AddApiVersioning(config =>
{ {
config.DefaultApiVersion = new ApiVersion(1, 0); config.DefaultApiVersion = new ApiVersion(1, 1);
config.AssumeDefaultVersionWhenUnspecified = true; config.AssumeDefaultVersionWhenUnspecified = true;
config.ReportApiVersions = true; config.ReportApiVersions = true;
config.ApiVersionReader = ApiVersionReader.Combine(new UrlSegmentApiVersionReader(), config.ApiVersionReader = ApiVersionReader.Combine(new UrlSegmentApiVersionReader(),
@@ -192,6 +207,9 @@ builder.Services.AddSwaggerGen(options =>
// using System.Reflection; // using System.Reflection;
var xmlFilename = $"{Assembly.GetExecutingAssembly().GetName().Name}.xml"; var xmlFilename = $"{Assembly.GetExecutingAssembly().GetName().Name}.xml";
options.IncludeXmlComments(Path.Combine(AppContext.BaseDirectory, xmlFilename)); options.IncludeXmlComments(Path.Combine(AppContext.BaseDirectory, xmlFilename));
// sort the endpoints
options.OrderActionsBy((apiDesc) => $"{apiDesc.RelativePath}_{apiDesc.HttpMethod}");
} }
); );
builder.Services.AddHostedService<TimedHostedService>(); builder.Services.AddHostedService<TimedHostedService>();
@@ -256,9 +274,12 @@ app.UseSwaggerUI(options =>
var descriptions = app.DescribeApiVersions(); var descriptions = app.DescribeApiVersions();
foreach (var description in descriptions) foreach (var description in descriptions)
{ {
var url = $"/swagger/{description.GroupName}/swagger.json"; if (description.IsDeprecated == false)
var name = description.GroupName.ToUpperInvariant(); {
options.SwaggerEndpoint(url, name); var url = $"/swagger/{description.GroupName}/swagger.json";
var name = description.GroupName.ToUpperInvariant();
options.SwaggerEndpoint(url, name);
}
} }
} }
); );
@@ -273,7 +294,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 +324,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 +350,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 +366,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

@@ -0,0 +1,73 @@
AE|United Arab Emirates
AL|Albania
AS|Asia
AT|Austria
AU|Australia
BA|Bosnia and Herzegovina
BE|Belgium
BG|Bulgaria
BR|Brazil
CA|Canada
CH|Switzerland
CL|Chile
CN|China
CS|Serbia and Montenegro
CY|Cyprus
CZ|Czech Republic
DE|Germany
DK|Denmark
EE|Estonia
EG|Egypt
ES|Spain
EU|Europe
FI|Finland
FR|France
GB|United Kingdom
GR|Greece
HK|Hong Kong
HR|Croatia
HU|Hungary
ID|Indonesia
IE|Ireland
IL|Israel
IN|India
IR|Iran
IS|Iceland
IT|Italy
JO|Jordan
JP|Japan
KR|Korea
KR|South Korea
LT|Lithuania
LU|Luxembourg
LV|Latvia
MN|Mongolia
MX|Mexico
MY|Malaysia
NL|Netherlands
NO|Norway
NP|Nepal
NZ|New Zealand
OM|Oman
PE|Peru
PH|Philippines
PL|Poland
PT|Portugal
QA|Qatar
RO|Romania
RU|Russia
SE|Sweden
SG|Singapore
SI|Slovenia
SK|Slovakia
TH|Thailand
TR|Turkey
TW|Taiwan
US|United States
USA|United States
VN|Vietnam
YU|Yugoslavia
ZA|South Africa
World|World
Europe|Europe
Asia|Asia

View File

@@ -0,0 +1 @@
ALTER TABLE `Platform` CHANGE `Name` `Name` varchar(255);

View File

@@ -0,0 +1,37 @@
CREATE TABLE `Country` (
`Id` INT NOT NULL AUTO_INCREMENT,
`Code` VARCHAR(20) NULL,
`Value` VARCHAR(255) NULL,
PRIMARY KEY (`Id`),
INDEX `id_Code` (`Code` ASC) VISIBLE,
INDEX `id_Value` (`Value` ASC) VISIBLE
);
CREATE TABLE `Language` (
`Id` INT NOT NULL AUTO_INCREMENT,
`Code` VARCHAR(20) NULL,
`Value` VARCHAR(255) NULL,
PRIMARY KEY (`Id`),
INDEX `id_Code` (`Code` ASC) VISIBLE,
INDEX `id_Value` (`Value` ASC) VISIBLE
);
CREATE TABLE `Signatures_RomToSource` (
`SourceId` int NOT NULL,
`RomId` int NOT NULL,
PRIMARY KEY (`SourceId`, `RomId`)
);
CREATE TABLE `Signatures_Games_Countries` (
`GameId` INT NOT NULL,
`CountryId` INT NOT NULL,
PRIMARY KEY (`GameId`, `CountryId`),
CONSTRAINT `GameCountry` FOREIGN KEY (`GameId`) REFERENCES `Signatures_Games` (`Id`) ON DELETE CASCADE ON UPDATE NO ACTION
);
CREATE TABLE `Signatures_Games_Languages` (
`GameId` INT NOT NULL,
`LanguageId` INT NOT NULL,
PRIMARY KEY (`GameId`, `LanguageId`),
CONSTRAINT `GameLanguage` FOREIGN KEY (`GameId`) REFERENCES `Signatures_Games` (`Id`) ON DELETE CASCADE ON UPDATE NO ACTION
);

View File

@@ -0,0 +1,47 @@
ar|Arabic
bg|Bulgarian
bs|Bosnian
cs|Czech
cy|Welsh
da|Danish
de|German
el|Greek
en|English
eo|Esperanto
es|Spanish
et|Estonian
fa|Persian
fi|Finnish
fr|French
fr-ca|French Canadian
ga|Irish
gd|Gaelic
gu|Gujarati
he|Hebrew
hi|Hindi
hr|Croatian
hu|Hungarian
is|Icelandic
it|Italian
ja|Japanese
ko|Korean
lt|Lithuanian
lv|Latvian
ms|Malay
nl|Dutch
no|Norwegian
pl|Polish
pt|Portuguese
ro|Romanian
ru|Russian
sk|Slovakian
sl|Slovenian
sq|Albanian
sr|Serbian
sv|Swedish
th|Thai
tr|Turkish
ur|Urdu
vi|Vietnamese
yi|Yiddish
zh|Chinese

File diff suppressed because it is too large Load Diff

View File

@@ -16,20 +16,20 @@
<DocumentationFile>bin\Release\net8.0\gaseous-server.xml</DocumentationFile> <DocumentationFile>bin\Release\net8.0\gaseous-server.xml</DocumentationFile>
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="Asp.Versioning.Mvc" Version="8.0.0" /> <PackageReference Include="Asp.Versioning.Mvc" Version="8.1.0" />
<PackageReference Include="Asp.Versioning.Mvc.ApiExplorer" Version="8.0.0" /> <PackageReference Include="Asp.Versioning.Mvc.ApiExplorer" Version="8.1.0" />
<PackageReference Include="gaseous-signature-parser" Version="2.1.0" /> <PackageReference Include="gaseous-signature-parser" Version="2.3.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.38.0" />
<PackageReference Include="Squid-Box.SevenZipSharp" Version="1.6.1.23" /> <PackageReference Include="Squid-Box.SevenZipSharp" Version="1.6.2.24" />
<PackageReference Include="Swashbuckle.AspNetCore" Version="6.5.0" /> <PackageReference Include="Swashbuckle.AspNetCore" Version="6.9.0" />
<PackageReference Include="MySqlConnector" Version="2.3.5" /> <PackageReference Include="MySqlConnector" Version="2.3.7" />
<PackageReference Include="Microsoft.AspNetCore.Identity.UI" Version="8.0.1" /> <PackageReference Include="Microsoft.AspNetCore.Identity.UI" Version="8.0.10" />
<PackageReference Include="Microsoft.AspNetCore.OpenApi" Version="8.0.1" /> <PackageReference Include="Microsoft.AspNetCore.OpenApi" Version="8.0.10" />
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="8.0.0" /> <PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="8.0.1" />
<PackageReference Include="Microsoft.VisualStudio.Web.CodeGeneration.Design" Version="8.0.0" /> <PackageReference Include="Microsoft.VisualStudio.Web.CodeGeneration.Design" Version="8.0.6" />
<PackageReference Include="Newtonsoft.Json" Version="13.0.3" /> <PackageReference Include="Newtonsoft.Json" Version="13.0.3" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
@@ -64,6 +64,8 @@
<None Remove="Support\Database\MySQL\gaseous-1019.sql" /> <None Remove="Support\Database\MySQL\gaseous-1019.sql" />
<None Remove="Support\Database\MySQL\gaseous-1020.sql" /> <None Remove="Support\Database\MySQL\gaseous-1020.sql" />
<None Remove="Support\Database\MySQL\gaseous-1021.sql" /> <None Remove="Support\Database\MySQL\gaseous-1021.sql" />
<None Remove="Support\Database\MySQL\gaseous-1022.sql" />
<None Remove="Support\Database\MySQL\gaseous-1023.sql" />
<None Remove="Classes\Metadata\" /> <None Remove="Classes\Metadata\" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
@@ -85,6 +87,8 @@
<ExcludeFromSingleFile>true</ExcludeFromSingleFile> <ExcludeFromSingleFile>true</ExcludeFromSingleFile>
<CopyToPublishDirectory>PreserveNewest</CopyToPublishDirectory> <CopyToPublishDirectory>PreserveNewest</CopyToPublishDirectory>
</EmbeddedResource> </EmbeddedResource>
<EmbeddedResource Include="Support\Country.txt" />
<EmbeddedResource Include="Support\Language.txt" />
<EmbeddedResource Include="Support\Database\MySQL\gaseous-1000.sql" /> <EmbeddedResource Include="Support\Database\MySQL\gaseous-1000.sql" />
<EmbeddedResource Include="Support\Database\MySQL\gaseous-1001.sql" /> <EmbeddedResource Include="Support\Database\MySQL\gaseous-1001.sql" />
<EmbeddedResource Include="Support\Database\MySQL\gaseous-1002.sql" /> <EmbeddedResource Include="Support\Database\MySQL\gaseous-1002.sql" />
@@ -108,5 +112,7 @@
<EmbeddedResource Include="Support\Database\MySQL\gaseous-1019.sql" /> <EmbeddedResource Include="Support\Database\MySQL\gaseous-1019.sql" />
<EmbeddedResource Include="Support\Database\MySQL\gaseous-1020.sql" /> <EmbeddedResource Include="Support\Database\MySQL\gaseous-1020.sql" />
<EmbeddedResource Include="Support\Database\MySQL\gaseous-1021.sql" /> <EmbeddedResource Include="Support\Database\MySQL\gaseous-1021.sql" />
<EmbeddedResource Include="Support\Database\MySQL\gaseous-1022.sql" />
<EmbeddedResource Include="Support\Database\MySQL\gaseous-1023.sql" />
</ItemGroup> </ItemGroup>
</Project> </Project>

View File

@@ -40,7 +40,11 @@
EJS_threads = false; EJS_threads = false;
EJS_onSaveState = function(e) { EJS_Buttons = {
exitEmulation: false
}
EJS_onSaveState = function (e) {
var returnValue = { var returnValue = {
"ScreenshotByteArrayBase64": btoa(Uint8ToString(e.screenshot)), "ScreenshotByteArrayBase64": btoa(Uint8ToString(e.screenshot)),
"StateByteArrayBase64": btoa(Uint8ToString(e.state)) "StateByteArrayBase64": btoa(Uint8ToString(e.state))
@@ -67,7 +71,7 @@
returnValue = undefined; returnValue = undefined;
} }
EJS_onLoadState = function(e) { EJS_onLoadState = function (e) {
showDialog('emulatorloadstate', { "romId": romId, "IsMediaGroup": IsMediaGroup }); showDialog('emulatorloadstate', { "romId": romId, "IsMediaGroup": IsMediaGroup });
} }
</script> </script>

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 480 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 92 KiB

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

@@ -1,34 +1,30 @@
<div style="padding-top: 5px;"> <table style="width: 98%; margin-top: 15px; margin-bottom: 15px;">
<strong>New Library</strong> <tr>
</div> <th style="width: 20%;">Name</th>
<td style="width: 80%;"><input type="text" id="newlibrary_name" style="width: 98%;" /></td>
</tr>
<tr>
<th>Default Platform</th>
<td><select id="newlibrary_defaultplatform" style="width: 100%;"></select></td>
</tr>
<tr>
<th>Path</th>
<td><input type="text" id="newlibrary_path" style="width: 98%;" /></td>
</tr>
</table>
<div style="width: 300px;"> <div style="width: 100%; text-align: right; margin-top: 160px;">
<table style="width: 98%; margin-top: 15px; margin-bottom: 15px;"> <div style="display: inline-block; margin-right: 20px;">
<tr> <button value="OK" onclick="newLibrary();">OK</button>
<th>Name</th> </div>
<td><input type="text" id="newlibrary_name" style="width: 95%;" /></td> <div style="display: inline-block;">
</tr> <button value="Cancel" onclick="closeDialog();">Cancel</button>
<tr>
<th>Default Platform</th>
<td><select id="newlibrary_defaultplatform" style="width: 100%;"></select></td>
</tr>
<tr>
<th>Path</th>
<td><input type="text" id="newlibrary_path" style="width: 95%;" /></td>
</tr>
</table>
<div style="width: 100%; text-align: right;">
<div style="display: inline-block; margin-right: 20px;">
<button value="OK" onclick="newLibrary();">OK</button>
</div>
<div style="display: inline-block;">
<button value="Cancel" onclick="closeSubDialog();">Cancel</button>
</div>
</div> </div>
</div> </div>
<script type="text/javascript"> <script type="text/javascript">
document.getElementById('modal-heading').innerHTML = "New Library";
$('#newlibrary_defaultplatform').select2({ $('#newlibrary_defaultplatform').select2({
minimumInputLength: 3, minimumInputLength: 3,
ajax: { ajax: {

View File

@@ -78,7 +78,7 @@
<td style="width: 75%;"><select id="properties_fixgame" style="width: 100%;"></select></td> <td style="width: 75%;"><select id="properties_fixgame" style="width: 100%;"></select></td>
</tr> </tr>
<tr> <tr>
<td colspan="2" style="text-align: right;"><button id="properties_fixsave" value="Save Match" onclick="SaveFixedGame();">Save Match</button></td> <td colspan="2" style="text-align: right;"><button id="properties_fixclear" value="Clear Match" onclick="ClearFixedGame();">Clear Match</button><button id="properties_fixsave" value="Save Match" onclick="SaveFixedGame();">Save Match</button></td>
</tr> </tr>
</table> </table>
</div> </div>
@@ -252,6 +252,7 @@
var fixplatform = $('#properties_fixplatform').select2('data'); var fixplatform = $('#properties_fixplatform').select2('data');
var fixgame = $('#properties_fixgame').select2('data'); var fixgame = $('#properties_fixgame').select2('data');
document.getElementById('properties_fixclear').setAttribute("disabled", "disabled");
document.getElementById('properties_fixsave').setAttribute("disabled", "disabled"); document.getElementById('properties_fixsave').setAttribute("disabled", "disabled");
ajaxCall('/api/v1.1/Games/' + gameId + '/roms/' + modalVariables + '?NewPlatformId=' + fixplatform[0].id + '&NewGameId=' + fixgame[0].id, 'PATCH', function (result) { ajaxCall('/api/v1.1/Games/' + gameId + '/roms/' + modalVariables + '?NewPlatformId=' + fixplatform[0].id + '&NewGameId=' + fixgame[0].id, 'PATCH', function (result) {
@@ -259,6 +260,18 @@
}); });
} }
function ClearFixedGame() {
var fixplatform = 0;
var fixgame = 0;
document.getElementById('properties_fixclear').setAttribute("disabled", "disabled");
document.getElementById('properties_fixsave').setAttribute("disabled", "disabled");
ajaxCall('/api/v1.1/Games/' + gameId + '/roms/' + modalVariables + '?NewPlatformId=' + fixplatform + '&NewGameId=' + fixgame, 'PATCH', function (result) {
window.location.reload();
});
}
function BuildAttributesTable(attributes, sourceName) { function BuildAttributesTable(attributes, sourceName) {
var aTable = document.createElement('table'); var aTable = document.createElement('table');
aTable.style.width = '100%'; aTable.style.width = '100%';

View File

@@ -72,6 +72,11 @@
</table> </table>
</td> </td>
</tr> </tr>
<tr>
<td>
<strong>Note</strong>: The page will need to be reloaded for changes to take effect.
</td>
</tr>
<tr> <tr>
<td style="text-align: right;"> <td style="text-align: right;">
<button id="profile_pref_ok" value="OK" onclick="SavePrefs();">OK</button> <button id="profile_pref_ok" value="OK" onclick="SavePrefs();">OK</button>
@@ -220,7 +225,8 @@
SetPreference_Batch(model); SetPreference_Batch(model);
if (getQueryString('page', 'string') == 'home' || getQueryString('page', 'string') == undefined) { if (getQueryString('page', 'string') == 'home' || getQueryString('page', 'string') == undefined) {
executeFilter1_1(1); setCookie('games_library_last_page', 1);
//location.reload();
} }
closeDialog(); closeDialog();

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;
@@ -23,6 +23,8 @@
var emuBios = ''; var emuBios = '';
var emuBackground = ''; var emuBackground = '';
console.log("Loading rom url: " + decodeURIComponent(getQueryString('rompath', 'string')));
ajaxCall('/api/v1.1/Games/' + gameId, 'GET', function (result) { ajaxCall('/api/v1.1/Games/' + gameId, 'GET', function (result) {
gameData = result; gameData = result;
@@ -51,10 +53,14 @@
emuBios = ''; emuBios = '';
} else { } else {
emuBios = '/api/v1.1/Bios/zip/' + platformId; emuBios = '/api/v1.1/Bios/zip/' + platformId;
console.log("Using BIOS link: " + emuBios);
} }
switch (getQueryString('engine', 'string')) { switch (getQueryString('engine', 'string')) {
case 'EmulatorJS': case 'EmulatorJS':
console.log("Emulator: " + getQueryString('engine', 'string'));
console.log("Core: " + getQueryString('core', 'string'));
$('#emulator').load('/emulators/EmulatorJS.html?v=' + AppVersion); $('#emulator').load('/emulators/EmulatorJS.html?v=' + AppVersion);
break; break;
} }
@@ -89,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

@@ -545,7 +545,7 @@
var saveStatesButton = ''; var saveStatesButton = '';
if (mediaGroup.emulator) { if (mediaGroup.emulator) {
if (mediaGroup.emulator.type.length > 0) { if (mediaGroup.emulator.type.length > 0) {
var romPath = encodeURIComponent('/api/v1.1/Games/' + gameId + '/romgroup/' + mediaGroup.id + '/' + gameData.name + '(' + mediaGroup.id + ')' + '.zip'); var romPath = encodeURIComponent('/api/v1.1/Games/' + gameId + '/romgroup/' + mediaGroup.id + '/' + gameData.name + '.zip');
if (mediaGroup.hasSaveStates == true) { if (mediaGroup.hasSaveStates == true) {
var modalVariables = { var modalVariables = {

View File

@@ -5,15 +5,17 @@
<table style="width: 100%;"> <table style="width: 100%;">
<tr> <tr>
<th style="width: 20%;">Home Page</th> <th style="width: 20%;">Home Page</th>
<td><a href="https://github.com/gaseous-project/gaseous-server" class="romlink">https://github.com/gaseous-project/gaseous-server</a></td> <td><a href="https://github.com/gaseous-project/gaseous-server"
class="romlink">https://github.com/gaseous-project/gaseous-server</a></td>
<td rowspan="5" style="text-align: center; width: 128px;"> <td rowspan="5" style="text-align: center; width: 128px;">
<img src="/images/logo.png" style="display: block; margin: 20px auto; width: 100px;" /> <img src="/images/logo.png" style="display: block; margin: 20px auto; width: 100px;" />
<span style="display: block;">The Gaseous logo was designed by Tom2.0</span> <span style="display: block;">The Gaseous logo was designed by Tom1243</span>
</td> </td>
</tr> </tr>
<tr> <tr>
<th>Bugs and Feature Requests</th> <th>Bugs and Feature Requests</th>
<td><a href="https://github.com/gaseous-project/gaseous-server/issues" class="romlink">https://github.com/gaseous-project/gaseous-server/issues</a></td> <td><a href="https://github.com/gaseous-project/gaseous-server/issues"
class="romlink">https://github.com/gaseous-project/gaseous-server/issues</a></td>
</tr> </tr>
<tr> <tr>
<th>Join our Discord</th> <th>Join our Discord</th>
@@ -33,21 +35,24 @@
</td> </td>
</tr> </tr>
<tr> <tr>
<td style="text-align: center;"><a href="https://github.com/EmulatorJS/EmulatorJS" target="_blank"><img src="/images/EmulatorJS.png" style="height: 36px;" /></a></td> <td style="text-align: center;"><a href="https://github.com/EmulatorJS/EmulatorJS" target="_blank"><img
src="/images/EmulatorJS.png" style="height: 36px;" /></a></td>
<td colspan="3"> <td colspan="3">
The EmulatorJS Project<br /> The EmulatorJS Project<br />
<a href="https://github.com/EmulatorJS/EmulatorJS" target="_blank" class="romlink">https://github.com/EmulatorJS/EmulatorJS</a> <a href="https://github.com/EmulatorJS/EmulatorJS" target="_blank"
class="romlink">https://github.com/EmulatorJS/EmulatorJS</a>
</td> </td>
</tr> </tr>
<tr> <tr>
<td colspan="3"> <td colspan="3">
<h3>Data Sources</h2> <h3>Data Sources</h2>
<h4>Game data</h4> <h4>Game data</h4>
</td> </td>
</tr> </tr>
<tr> <tr>
<td style="text-align: center;"> <td style="text-align: center;">
<a href="https://www.igdb.com/" target="_blank"><img src="/images/IGDB_logo.svg" style="filter: invert(100%); height: 36px;" /></a> <a href="https://www.igdb.com/" target="_blank"><img src="/images/IGDB_logo.svg"
style="filter: invert(100%); height: 36px;" /></a>
</td> </td>
<td colspan="2"> <td colspan="2">
The Internet Game Database<br /> The Internet Game Database<br />
@@ -61,7 +66,8 @@
</tr> </tr>
<tr> <tr>
<td style="text-align: center;"> <td style="text-align: center;">
<a href="https://www.tosecdev.org/" target="_blank"><img src="/images/TOSEC_logo.gif" style="height: 36px;" /></a> <a href="https://www.tosecdev.org/" target="_blank"><img src="/images/TOSEC_logo.gif"
style="height: 36px;" /></a>
</td> </td>
<td colspan="2"> <td colspan="2">
The Old School Emulation Center<br /> The Old School Emulation Center<br />
@@ -70,11 +76,34 @@
</tr> </tr>
<tr> <tr>
<td style="text-align: center;"> <td style="text-align: center;">
<a href="https://www.progettosnaps.net/index.php" target="_blank"><img src="/images/ProgettoSnaps.gif" style="height: 36px;" /></a> <a href="https://www.progettosnaps.net/index.php" target="_blank"><img src="/images/ProgettoSnaps.gif"
style="height: 36px;" /></a>
</td> </td>
<td colspan="2"> <td colspan="2">
Progetto-Snaps<br /> Progetto-Snaps<br />
<a href="https://www.progettosnaps.net/index.php" target="_blank" class="romlink">https://www.progettosnaps.net/index.php</a> <a href="https://www.progettosnaps.net/index.php" target="_blank"
class="romlink">https://www.progettosnaps.net/index.php</a>
</td>
</tr>
<tr>
<td style="text-align: center;">
<a href="https://no-intro.org" target="_blank" rel="noopener noreferrer"><img src="/images/NoIntro-logo.svg"
style="height: 36px;"></a>
</td>
<td colspan="2">
No-Intro<br>
<a href="https://no-intro.org" target="_blank" rel="noopener noreferrer"
class="romlink">https://no-intro.org</a>
</td>
</tr>
<tr>
<td style="text-align: center;">
<a href="http://redump.org" target="_blank" rel="noopener noreferrer"><img src="/images/redump.png"
style="height: 36px;"></a>
</td>
<td colspan="2">
Redump<br>
<a href="http://redump.org" target="_blank" rel="noopener noreferrer" class="romlink">http://redump.org</a>
</td> </td>
</tr> </tr>
</table> </table>

View File

@@ -6,15 +6,17 @@
<table id="settings_libraries" class="romtable" style="width: 100%;" cellspacing="0"> <table id="settings_libraries" class="romtable" style="width: 100%;" cellspacing="0">
</table> </table>
<div style="text-align: right;"><button id="settings_newlibrary" onclick="showSubDialog('librarynew');">New Library</button></div> <div style="text-align: right;"><button id="settings_newlibrary" onclick="showDialog('librarynew');">New
Library</button></div>
<h2>Advanced Settings</h2> <h2>Advanced Settings</h2>
<p><strong>Warning</strong> Do not modify the below settings unless you know what you're doing.</p> <p><strong>Warning</strong> Do not modify the below settings unless you know what you're doing.</p>
<h3>Background Task Timers</h3> <h3>Background Task Timers</h3>
<table id="settings_tasktimers" class="romtable" style="width: 100%;" cellspacing="0"> <table id="settings_tasktimers" class="romtable" style="width: 100%;" cellspacing="0">
</table> </table>
<div style="text-align: right;"><button id="settings_tasktimers_default" onclick="defaultTaskTimers();">Reset to Default</button><button id="settings_tasktimers_new" onclick="saveTaskTimers();">Save</button></div> <div style="text-align: right;"><button id="settings_tasktimers_default" onclick="defaultTaskTimers();">Reset to
Default</button><button id="settings_tasktimers_new" onclick="saveTaskTimers();">Save</button></div>
<h3>System Settings</h3> <h3>System Settings</h3>
<table cellspacing="0" style="width: 100%;"> <table cellspacing="0" style="width: 100%;">
@@ -26,13 +28,15 @@
Write logs Write logs
</th> </th>
<td> <td>
<input type="radio" name="settings_logs_write" id="settings_logs_write_db" value="false" checked="checked"><label for="settings_logs_write_db"> To database only (default)</label> <input type="radio" name="settings_logs_write" id="settings_logs_write_db" value="false"
checked="checked"><label for="settings_logs_write_db"> To database only (default)</label>
</td> </td>
</tr> </tr>
<tr> <tr>
<td></td> <td></td>
<td> <td>
<input type="radio" name="settings_logs_write" id="settings_logs_write_fs" value="true"><label for="settings_logs_write_fs"> To database and disk</label> <input type="radio" name="settings_logs_write" id="settings_logs_write_fs" value="true"><label
for="settings_logs_write_fs"> To database and disk</label>
</td> </td>
</tr> </tr>
<tr> <tr>
@@ -46,6 +50,58 @@
<input type="number" min="1" id="settings_logs_retention" /> <input type="number" min="1" id="settings_logs_retention" />
</td> </td>
</tr> </tr>
<tr>
<th>
Allowed metadata search modes:
</th>
<td>
<input type="checkbox" name="settings_metadata_search" id="settings_metadata_search_where" /><label
for="settings_metadata_search_where">
Exact:
<i>
Searches for exact matches only. Example search: name = "Super Mario Bros."
</i>
</label>
</td>
</tr>
<tr>
<td>&nbsp;</td>
<td>
<input type="checkbox" name="settings_metadata_search" id="settings_metadata_search_wherefuzzy" /><label
for="settings_metadata_search_wherefuzzy">
Partial:
<i>
Searches for partial matches. Example search: name = "Mario"
</i>
</label>
</td>
</tr>
<tr>
<td>&nbsp;</td>
<td>
<input type="checkbox" name="settings_metadata_search" id="settings_metadata_search_search" /><label
for="settings_metadata_search_search">
Search:
<i>
Searches for partial matches using full text search. Example search: name = "Mario"
</i>
</label>
</td>
</tr>
<tr>
<td>&nbsp;</td>
<td>
<input type="checkbox" name="settings_metadata_search"
id="settings_metadata_search_searchNoPlatform" /><label for="settings_metadata_search_searchNoPlatform">
Search (no platform
contraint):
<i>
Searches for partial matches using full text search, but without a platform constraint. Example
search: name = "Mario"
</i>
</label>
</td>
</tr>
<tr> <tr>
<th colspan="2">Emulator</th> <th colspan="2">Emulator</th>
</tr> </tr>
@@ -95,9 +151,9 @@
} }
newTable.appendChild(createTableRow( newTable.appendChild(createTableRow(
false, false,
[ [
result[i].name, result[i].name,
result[i].path, result[i].path,
platformName, platformName,
defaultLibrary, defaultLibrary,
@@ -115,14 +171,14 @@
ajaxCall( ajaxCall(
'/api/v1/System/Settings/BackgroundTasks/Configuration', '/api/v1/System/Settings/BackgroundTasks/Configuration',
'GET', 'GET',
function(result) { function (result) {
var targetTable = document.getElementById('settings_tasktimers'); var targetTable = document.getElementById('settings_tasktimers');
targetTable.innerHTML = ''; targetTable.innerHTML = '';
for (const [key, value] of Object.entries(result)) { for (const [key, value] of Object.entries(result)) {
var newTableRowBody = document.createElement('tbody'); var newTableRowBody = document.createElement('tbody');
newTableRowBody.className = 'romrow'; newTableRowBody.className = 'romrow';
var enabledString = ""; var enabledString = "";
if (value.enabled == true) { if (value.enabled == true) {
enabledString = 'checked="checked"'; enabledString = 'checked="checked"';
@@ -171,7 +227,7 @@
daySelector.multiple = 'multiple'; daySelector.multiple = 'multiple';
daySelector.setAttribute('data-default', value.defaultAllowedDays.join(",")); daySelector.setAttribute('data-default', value.defaultAllowedDays.join(","));
daySelector.style.width = '95%'; daySelector.style.width = '95%';
var days = [ "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday", "Sunday" ]; var days = ["Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday", "Sunday"];
for (var d = 0; d < days.length; d++) { for (var d = 0; d < days.length; d++) {
var dayOpt = document.createElement('option'); var dayOpt = document.createElement('option');
dayOpt.value = days[d]; dayOpt.value = days[d];
@@ -312,7 +368,7 @@
var taskName = timerValues[i].getAttribute('data-name'); var taskName = timerValues[i].getAttribute('data-name');
var taskEnabled = document.getElementById('settings_enabled_' + taskName).checked; var taskEnabled = document.getElementById('settings_enabled_' + taskName).checked;
var taskIntervalObj = document.getElementById('settings_tasktimers_' + taskName); var taskIntervalObj = document.getElementById('settings_tasktimers_' + taskName);
var taskInterval = function() { if (taskIntervalObj.value) { return taskIntervalObj.value; } else { return taskIntervalObj.getAttribute('placeholder'); } }; var taskInterval = function () { if (taskIntervalObj.value) { return taskIntervalObj.value; } else { return taskIntervalObj.getAttribute('placeholder'); } };
var taskDaysRaw = $('#settings_alloweddays_' + taskName).select2('data'); var taskDaysRaw = $('#settings_alloweddays_' + taskName).select2('data');
var taskDays = []; var taskDays = [];
if (taskDaysRaw.length > 0) { if (taskDaysRaw.length > 0) {
@@ -327,10 +383,10 @@
var taskEndHourObj = document.getElementById('settings_tasktimers_' + taskName + '_End_Hour'); var taskEndHourObj = document.getElementById('settings_tasktimers_' + taskName + '_End_Hour');
var taskEndMinuteObj = document.getElementById('settings_tasktimers_' + taskName + '_End_Minute'); var taskEndMinuteObj = document.getElementById('settings_tasktimers_' + taskName + '_End_Minute');
var taskStartHour = function() { if (taskStartHourObj.value) { return taskStartHourObj.value; } else { return taskStartHourObj.getAttribute('placeholder'); } }; var taskStartHour = function () { if (taskStartHourObj.value) { return taskStartHourObj.value; } else { return taskStartHourObj.getAttribute('placeholder'); } };
var taskStartMinute = function() { if (taskStartMinuteObj.value) { return taskStartMinuteObj.value; } else { return taskStartMinuteObj.getAttribute('placeholder'); } }; var taskStartMinute = function () { if (taskStartMinuteObj.value) { return taskStartMinuteObj.value; } else { return taskStartMinuteObj.getAttribute('placeholder'); } };
var taskEndHour = function() { if (taskEndHourObj.value) { return taskEndHourObj.value; } else { return taskEndHourObj.getAttribute('placeholder'); } }; var taskEndHour = function () { if (taskEndHourObj.value) { return taskEndHourObj.value; } else { return taskEndHourObj.getAttribute('placeholder'); } };
var taskEndMinute = function() { if (taskEndMinuteObj.value) { return taskEndMinuteObj.value; } else { return taskEndMinuteObj.getAttribute('placeholder'); } }; var taskEndMinute = function () { if (taskEndMinuteObj.value) { return taskEndMinuteObj.value; } else { return taskEndMinuteObj.getAttribute('placeholder'); } };
model.push( model.push(
{ {
@@ -349,10 +405,10 @@
ajaxCall( ajaxCall(
'/api/v1/System/Settings/BackgroundTasks/Configuration', '/api/v1/System/Settings/BackgroundTasks/Configuration',
'POST', 'POST',
function(result) { function (result) {
getBackgroundTaskTimers(); getBackgroundTaskTimers();
}, },
function(error) { function (error) {
getBackgroundTaskTimers(); getBackgroundTaskTimers();
}, },
JSON.stringify(model) JSON.stringify(model)
@@ -393,7 +449,7 @@
ajaxCall( ajaxCall(
'/api/v1/System/Settings/System', '/api/v1/System/Settings/System',
'GET', 'GET',
function(result) { function (result) {
var optionToSelect = 'settings_logs_write_db'; var optionToSelect = 'settings_logs_write_db';
if (result.alwaysLogToDisk == true) { if (result.alwaysLogToDisk == true) {
optionToSelect = 'settings_logs_write_fs'; optionToSelect = 'settings_logs_write_fs';
@@ -403,6 +459,11 @@
document.getElementById('settings_logs_retention').value = result.minimumLogRetentionPeriod; document.getElementById('settings_logs_retention').value = result.minimumLogRetentionPeriod;
document.getElementById('settings_emulator_debug').checked = result.emulatorDebugMode; document.getElementById('settings_emulator_debug').checked = result.emulatorDebugMode;
for (let i = 0; i < result.searchTypes.length; i++) {
const element = result.searchTypes[i];
document.getElementById('settings_metadata_search_' + element).checked = true;
}
} }
); );
} }
@@ -421,19 +482,29 @@
retentionValue = 7; retentionValue = 7;
} }
let searchTypes = [];
let searchTypeElements = document.getElementsByName('settings_metadata_search');
for (let i = 0; i < searchTypeElements.length; i++) {
const element = searchTypeElements[i];
if (element.checked) {
searchTypes.push(element.id.replace('settings_metadata_search_', ''));
}
}
var model = { var model = {
"alwaysLogToDisk": alwaysLogToDisk, "alwaysLogToDisk": alwaysLogToDisk,
"minimumLogRetentionPeriod": retentionValue, "minimumLogRetentionPeriod": retentionValue,
"emulatorDebugMode": document.getElementById('settings_emulator_debug').checked "emulatorDebugMode": document.getElementById('settings_emulator_debug').checked,
"searchTypes": searchTypes
}; };
ajaxCall( ajaxCall(
'/api/v1/System/Settings/System', '/api/v1/System/Settings/System',
'POST', 'POST',
function(result) { function (result) {
getSystemSettings(); getSystemSettings();
}, },
function(error) { function (error) {
getSystemSettings(); getSystemSettings();
}, },
JSON.stringify(model) JSON.stringify(model)

View File

@@ -27,7 +27,7 @@ function formatFilterPanel(containerElement, result) {
// Cancel the default action, if needed // Cancel the default action, if needed
event.preventDefault(); event.preventDefault();
// Trigger the button element with a click // Trigger the button element with a click
executeFilter1_1(); applyFilters();
} }
}); });
containerPanelSearch.appendChild(containerPanelSearchField); containerPanelSearch.appendChild(containerPanelSearchField);
@@ -99,7 +99,7 @@ function formatFilterPanel(containerElement, result) {
// add filter button // add filter button
var searchButton = document.createElement('div'); var searchButton = document.createElement('div');
searchButton.id = 'games_library_searchbutton'; searchButton.id = 'games_library_searchbutton';
searchButton.setAttribute('onclick', 'executeFilter1_1();'); searchButton.setAttribute('onclick', 'applyFilters();');
searchButton.innerHTML = 'Apply'; searchButton.innerHTML = 'Apply';
buttonsDiv.appendChild(searchButton); buttonsDiv.appendChild(searchButton);
@@ -365,6 +365,12 @@ function filter_panel_range_value(name) {
} }
} }
function applyFilters() {
document.getElementById('games_library').innerHTML = '';
executeFilter1_1();
}
function resetFilters() { function resetFilters() {
// clear name // clear name
document.getElementById('filter_panel_search').value = ''; document.getElementById('filter_panel_search').value = '';
@@ -381,6 +387,7 @@ function resetFilters() {
filter_panel_range_enabled_check(rangeCheckboxes[i].getAttribute('data-name')); filter_panel_range_enabled_check(rangeCheckboxes[i].getAttribute('data-name'));
} }
document.getElementById('games_library').innerHTML = '';
executeFilter1_1(); executeFilter1_1();
} }
@@ -393,8 +400,18 @@ function executeFilter1_1(pageNumber, pageSize) {
existingSearchModel = undefined; existingSearchModel = undefined;
} }
let pageMode = GetPreference('LibraryPagination', 'paged');
if (!pageSize) { if (!pageSize) {
pageSize = 30; switch (pageMode) {
case "infinite":
pageSize = 30;
break;
case "paged":
default:
pageSize = 30;
break;
}
} }
var model; var model;

View File

@@ -56,192 +56,251 @@ var ClassificationRatings = {
}; };
var pageReloadInterval; var pageReloadInterval;
var firstLoad = true;
function formatGamesPanel(targetElement, result, pageNumber, pageSize, forceScrollTop) { function formatGamesPanel(targetElement, result, pageNumber, pageSize, forceScrollTop) {
console.log("Displaying page: " + pageNumber);
console.log("Page size: " + pageSize);
var pageMode = GetPreference('LibraryPagination', 'paged'); var pageMode = GetPreference('LibraryPagination', 'paged');
if (pageNumber == 1 || pageMode == 'paged') { if (pageNumber == 1) {
targetElement.innerHTML = ''; localStorage.setItem("gaseous-library-scrollpos", 0);
} }
if (pageMode == 'paged') { if (pageMode == 'paged') {
if (forceScrollTop == true) { targetElement.innerHTML = '';
window.scrollTo(0, 0);
}
} }
var pagerCheck = document.getElementById('games_library_pagerstore'); switch (pageMode) {
if (pageNumber == 1) { case 'paged':
pagerCheck.innerHTML = "0"; if (forceScrollTop == true) {
} window.scrollTo(0, 0);
}
break;
case 'infinite':
var gamePlaceholders = document.getElementsByName('GamePlaceholder');
if (pageNumber > Number(pagerCheck.innerHTML) || pageMode == 'paged') { let currentPage = 1;
pagerCheck.innerHTML = pageNumber; let totalPages = Math.ceil(result.count / pageSize);
let startIndex = 0;
let endIndex = pageSize;
for (let p = currentPage; p < totalPages + 1; p++) {
//console.log("Page: " + p + " - StartIndex: " + startIndex + " - EndIndex: " + endIndex);
document.getElementById('games_library_recordcount').innerHTML = result.count + ' games'; let newPageAnchor = document.getElementById('pageAnchor' + p);
if (!newPageAnchor) {
var existingLoadPageButton = document.getElementById('games_library_loadmore'); newPageAnchor = document.createElement('span');
if (existingLoadPageButton) { newPageAnchor.id = 'pageAnchor' + p;
existingLoadPageButton.parentNode.removeChild(existingLoadPageButton); newPageAnchor.setAttribute('name', 'pageAnchor' + p);
} newPageAnchor.className = 'pageAnchor';
newPageAnchor.setAttribute('data-page', p);
// setup preferences newPageAnchor.setAttribute('data-loaded', "0");
var showTitle = GetPreference("LibraryShowGameTitle", true); targetElement.appendChild(newPageAnchor);
var showRatings = GetPreference("LibraryShowGameRating", true);
var showClassification = GetPreference("LibraryShowGameClassification", true);
var classificationDisplayOrderString = GetPreference("LibraryGameClassificationDisplayOrder", JSON.stringify([ "ESRB" ]));
var classificationDisplayOrder = JSON.parse(classificationDisplayOrderString);
if (showTitle == "true") { showTitle = true; } else { showTitle = false; }
if (showRatings == "true") { showRatings = true; } else { showRatings = false; }
if (showClassification == "true") { showClassification = true; } else { showClassification = false; }
for (var i = 0; i < result.games.length; i++) {
var game = renderGameIcon(result.games[i], showTitle, showRatings, showClassification, classificationDisplayOrder, false);
targetElement.appendChild(game);
}
$('.lazy').Lazy({
scrollDirection: 'vertical',
effect: 'fadeIn',
visibleOnly: true
});
var pager = document.getElementById('games_pager');
pager.style.display = 'none';
var alphaPager = document.getElementById('games_library_alpha_pager');
alphaPager.innerHTML = '';
switch(pageMode) {
case 'infinite':
if (result.games.length == pageSize) {
var loadPageButton = document.createElement("div");
loadPageButton.id = 'games_library_loadmore';
loadPageButton.innerHTML = 'Load More';
loadPageButton.setAttribute('onclick', 'executeFilter1_1(' + (pageNumber + 1) + ', ' + pageSize + ');');
loadPageButton.setAttribute('data-pagenumber', Number(pageNumber + 1));
loadPageButton.setAttribute('data-pagesize', pageSize);
targetElement.appendChild(loadPageButton);
}
break;
case 'paged':
for (const [key, value] of Object.entries(result.alphaList)) {
var letterPager = document.createElement('span');
letterPager.className = 'games_library_alpha_pager_letter';
letterPager.setAttribute('onclick', 'executeFilter1_1(' + value + ');');
letterPager.innerHTML = key;
alphaPager.appendChild(letterPager);
} }
if (result.count > pageSize) { if (endIndex > result.count) {
// add some padding to the bottom of the games list endIndex = result.count;
var loadPageButton = document.createElement("div"); }
loadPageButton.id = 'games_library_padding';
targetElement.appendChild(loadPageButton);
var pageCount = Math.ceil(result.count / pageSize); for (let i = startIndex; i < endIndex; i++) {
var placeHolderpresent = false;
// add first page button for (var x = 0; x < gamePlaceholders.length; x++) {
var firstPage = document.createElement('span'); if (gamePlaceholders[x].getAttribute('data-index') == i) {
firstPage.innerHTML = '&#124;&lt;'; placeHolderpresent = true;
if (pageNumber == 1) {
firstPage.className = 'games_pager_number_disabled';
} else {
firstPage.className = 'games_pager_number';
firstPage.setAttribute('onclick', 'executeFilter1_1(1);');
}
// add previous page button
var prevPage = document.createElement('span');
prevPage.innerHTML = '&lt;';
if (pageNumber == 1) {
prevPage.className = 'games_pager_number_disabled';
} else {
prevPage.className = 'games_pager_number';
prevPage.setAttribute('onclick', 'executeFilter1_1(' + (pageNumber - 1) + ');');
}
// add page numbers
var pageEitherSide = 4;
var currentPage = Number(pagerCheck.innerHTML);
var pageNumbers = document.createElement('span');
for (var i = 1; i <= pageCount; i++) {
if (
(
(i >= currentPage - pageEitherSide) &&
(i <= currentPage + pageEitherSide)
) ||
(
(
i <= (pageEitherSide * 2 + 1) &&
currentPage <= (pageEitherSide)
) ||
(
i >= (pageCount - (pageEitherSide * 2)) &&
currentPage >= (pageCount - (pageEitherSide))
)
)
) {
var pageNum = document.createElement('span');
if (Number(pagerCheck.innerHTML) == i) {
pageNum.className = 'games_pager_number_disabled';
} else {
pageNum.className = 'games_pager_number';
pageNum.setAttribute('onclick', 'executeFilter1_1(' + i + ');');
}
pageNum.innerHTML = i;
pageNumbers.appendChild(pageNum);
} }
} }
if (placeHolderpresent == false) {
// add next page button var gamePlaceholder = document.createElement('div');
var nextPage = document.createElement('span'); gamePlaceholder.setAttribute('name', 'GamePlaceholder');
nextPage.innerHTML = '&gt;'; gamePlaceholder.id = 'GamePlaceholder' + i;
if (pageNumber == pageCount) { gamePlaceholder.setAttribute('data-index', i);
nextPage.className = 'games_pager_number_disabled'; gamePlaceholder.className = 'game_tile';
} else { newPageAnchor.appendChild(gamePlaceholder);
nextPage.className = 'games_pager_number';
nextPage.setAttribute('onclick', 'executeFilter1_1(' + (pageNumber + 1) + ');');
} }
// add last page button
var lastPage = document.createElement('span');
lastPage.innerHTML = '&gt;&#124;';
if (pageNumber == pageCount) {
lastPage.className = 'games_pager_number_disabled';
} else {
lastPage.className = 'games_pager_number';
lastPage.setAttribute('onclick', 'executeFilter1_1(' + pageCount + ');');
}
pager.innerHTML = '';
pager.appendChild(firstPage);
pager.appendChild(prevPage);
pager.appendChild(pageNumbers);
pager.appendChild(nextPage);
pager.appendChild(lastPage);
pager.style.display = '';
} }
break;
} startIndex = endIndex;
endIndex = startIndex + pageSize;
if (startIndex > result.count) {
break;
}
}
break;
} }
// var pageReloadFunction = function() { document.getElementById('games_library_recordcount').innerHTML = result.count + ' games';
// formatGamesPanel(targetElement, result, pageNumber, pageSize, false);
// ajaxCall('/api/v1.1/Filter', 'GET', function (result) { var existingLoadPageButton = document.getElementById('games_library_loadmore');
// var scrollerElement = document.getElementById('games_filter_scroller'); if (existingLoadPageButton) {
// formatFilterPanel(scrollerElement, result); existingLoadPageButton.parentNode.removeChild(existingLoadPageButton);
// }) }
// };
// window.clearTimeout(pageReloadInterval); // setup preferences
// pageReloadInterval = setTimeout(pageReloadFunction, 10000); var showTitle = GetPreference("LibraryShowGameTitle", true);
var showRatings = GetPreference("LibraryShowGameRating", true);
var showClassification = GetPreference("LibraryShowGameClassification", true);
var classificationDisplayOrderString = GetPreference("LibraryGameClassificationDisplayOrder", JSON.stringify([ "ESRB" ]));
var classificationDisplayOrder = JSON.parse(classificationDisplayOrderString);
if (showTitle == "true") { showTitle = true; } else { showTitle = false; }
if (showRatings == "true") { showRatings = true; } else { showRatings = false; }
if (showClassification == "true") { showClassification = true; } else { showClassification = false; }
for (var i = 0; i < result.games.length; i++) {
var game = renderGameIcon(result.games[i], showTitle, showRatings, showClassification, classificationDisplayOrder, false);
switch (pageMode) {
case "paged":
targetElement.appendChild(game);
break;
case "infinite":
var placeholderElement = document.getElementById('GamePlaceholder' + result.games[i].index);
if (placeholderElement.className != 'game_tile_wrapper') {
placeholderElement.className = 'game_tile_wrapper';
placeholderElement.innerHTML = '';
placeholderElement.appendChild(game);
}
break;
}
$(game).fadeIn(500);
}
var pager = document.getElementById('games_pager');
pager.style.display = 'none';
var alphaPager = document.getElementById('games_library_alpha_pager');
alphaPager.innerHTML = '';
switch(pageMode) {
case 'infinite':
for (const [key, value] of Object.entries(result.alphaList)) {
var letterPager = document.createElement('span');
letterPager.className = 'games_library_alpha_pager_letter';
letterPager.setAttribute('onclick', 'document.location.hash = "#pageAnchor' + (value) + '"; executeFilter1_1(' + (value) + ');');
letterPager.innerHTML = key;
alphaPager.appendChild(letterPager);
}
if (firstLoad == true) {
if (localStorage.getItem("gaseous-library-scrollpos") != null) {
$(window).scrollTop(localStorage.getItem("gaseous-library-scrollpos"));
}
firstLoad = false;
}
IsInView();
break;
case 'paged':
for (const [key, value] of Object.entries(result.alphaList)) {
var letterPager = document.createElement('span');
letterPager.className = 'games_library_alpha_pager_letter';
letterPager.setAttribute('onclick', 'executeFilter1_1(' + value + ');');
letterPager.innerHTML = key;
alphaPager.appendChild(letterPager);
}
if (result.count > pageSize) {
var pageCount = Math.ceil(result.count / pageSize);
// add first page button
var firstPage = document.createElement('span');
firstPage.innerHTML = '&#124;&lt;';
if (pageNumber == 1) {
firstPage.className = 'games_pager_number_disabled';
} else {
firstPage.className = 'games_pager_number';
firstPage.setAttribute('onclick', 'executeFilter1_1(1);');
}
// add previous page button
var prevPage = document.createElement('span');
prevPage.innerHTML = '&lt;';
if (pageNumber == 1) {
prevPage.className = 'games_pager_number_disabled';
} else {
prevPage.className = 'games_pager_number';
prevPage.setAttribute('onclick', 'executeFilter1_1(' + (pageNumber - 1) + ');');
}
// add page numbers
var pageEitherSide = 4;
// var currentPage = Number(pagerCheck.innerHTML);
var currentPage = pageNumber;
var pageNumbers = document.createElement('span');
for (var i = 1; i <= pageCount; i++) {
if (
(
(i >= currentPage - pageEitherSide) &&
(i <= currentPage + pageEitherSide)
) ||
(
(
i <= (pageEitherSide * 2 + 1) &&
currentPage <= (pageEitherSide)
) ||
(
i >= (pageCount - (pageEitherSide * 2)) &&
currentPage >= (pageCount - (pageEitherSide))
)
)
) {
var pageNum = document.createElement('span');
if (pageNumber == i) {
pageNum.className = 'games_pager_number_disabled';
} else {
pageNum.className = 'games_pager_number';
pageNum.setAttribute('onclick', 'executeFilter1_1(' + i + ');');
}
pageNum.innerHTML = i;
pageNumbers.appendChild(pageNum);
}
}
// add next page button
var nextPage = document.createElement('span');
nextPage.innerHTML = '&gt;';
if (pageNumber == pageCount) {
nextPage.className = 'games_pager_number_disabled';
} else {
nextPage.className = 'games_pager_number';
nextPage.setAttribute('onclick', 'executeFilter1_1(' + (pageNumber + 1) + ');');
}
// add last page button
var lastPage = document.createElement('span');
lastPage.innerHTML = '&gt;&#124;';
if (pageNumber == pageCount) {
lastPage.className = 'games_pager_number_disabled';
} else {
lastPage.className = 'games_pager_number';
lastPage.setAttribute('onclick', 'executeFilter1_1(' + pageCount + ');');
}
pager.innerHTML = '';
pager.appendChild(firstPage);
pager.appendChild(prevPage);
pager.appendChild(pageNumbers);
pager.appendChild(nextPage);
pager.appendChild(lastPage);
pager.style.display = '';
}
break;
}
$('.lazy').Lazy({
effect: 'show',
effectTime: 500,
visibleOnly: true,
defaultImage: '/images/unknowngame.png',
delay: 250,
afterLoad: function(element) {
//console.log(element[0].getAttribute('data-id'));
}
});
} }
function isScrolledIntoView(elem) { function isScrolledIntoView(elem) {
@@ -256,14 +315,48 @@ function isScrolledIntoView(elem) {
} }
} }
const elementIsVisibleInViewport = (el, partiallyVisible = false) => {
const { top, left, bottom, right } = el.getBoundingClientRect();
const { innerHeight, innerWidth } = window;
return partiallyVisible
? ((top > 0 && top < innerHeight) ||
(bottom > 0 && bottom < innerHeight)) &&
((left > 0 && left < innerWidth) || (right > 0 && right < innerWidth))
: top >= 0 && left >= 0 && bottom <= innerHeight && right <= innerWidth;
};
function IsInView() { function IsInView() {
var loadElement = document.getElementById('games_library_loadmore'); var pageMode = GetPreference('LibraryPagination', 'paged');
if (loadElement) { switch (pageMode) {
if (isScrolledIntoView(loadElement)) { case "paged":
var pageNumber = Number(document.getElementById('games_library_loadmore').getAttribute('data-pagenumber')); var loadElement = document.getElementById('games_library_loadmore');
var pageSize = document.getElementById('games_library_loadmore').getAttribute('data-pagesize'); if (loadElement) {
executeFilter1_1(pageNumber, pageSize); //if (isScrolledIntoView(loadElement)) {
} if (elementIsVisibleInViewport(loadElement, true)) {
var pageNumber = Number(document.getElementById('games_library_loadmore').getAttribute('data-pagenumber'));
var pageSize = document.getElementById('games_library_loadmore').getAttribute('data-pagesize');
executeFilter1_1(pageNumber);
}
}
break;
case "infinite":
// store scroll location
localStorage.setItem("gaseous-library-scrollpos", $(window).scrollTop());
// load page
let anchors = document.getElementsByClassName('pageAnchor');
for (let i = 0; i < anchors.length; i++) {
//if (isScrolledIntoView(anchors[i])) {
if (elementIsVisibleInViewport(anchors[i], true)) {
if (anchors[i].getAttribute('data-loaded') == "0") {
console.log("Loading page: " + anchors[i].getAttribute('data-page'));
document.getElementById(anchors[i].id).setAttribute('data-loaded', "1");
executeFilter1_1(Number(anchors[i].getAttribute('data-page')));
}
}
}
break;
} }
} }
@@ -279,26 +372,30 @@ function renderGameIcon(gameObject, showTitle, showRatings, showClassification,
var gameBox = document.createElement('div'); var gameBox = document.createElement('div');
gameBox.id = "game_tile_" + gameObject.id; gameBox.id = "game_tile_" + gameObject.id;
if (useSmallCover == true) { if (useSmallCover == true) {
gameBox.className = classes['game_tile game_tile_small']; gameBox.classList.add(...classes['game_tile game_tile_small']);
} else { } else {
gameBox.className = classes['game_tile']; gameBox.classList.add(...classes['game_tile']);
} }
gameBox.setAttribute('onclick', 'window.location.href = "/index.html?page=game&id=' + gameObject.id + '";'); gameBox.setAttribute('onclick', 'window.location.href = "/index.html?page=game&id=' + gameObject.id + '";');
gameBox.style.display = 'none';
var gameImageBox = document.createElement('div'); var gameImageBox = document.createElement('div');
gameImageBox.className = classes['game_tile_box']; gameImageBox.classList.add(...classes['game_tile_box']);
var gameImage = document.createElement('img'); var gameImage = document.createElement('img');
gameImage.id = 'game_tile_cover_' + gameObject.id;
gameImage.setAttribute('data-id', gameObject.id);
if (useSmallCover == true) { if (useSmallCover == true) {
gameImage.className = classes['game_tile_image game_tile_image_small lazy']; gameImage.classList.add(...classes['game_tile_image game_tile_image_small lazy']);
} else { } else {
gameImage.className = classes['game_tile_image lazy']; gameImage.classList.add(...classes['game_tile_image lazy']);
} }
gameImage.src = '/images/unknowngame.png'; // gameImage.src = '/images/unknowngame.png';
if (gameObject.cover) { if (gameObject.cover) {
gameImage.setAttribute('data-src', '/api/v1.1/Games/' + gameObject.id + '/cover/image/cover_big/' + gameObject.cover.imageId + '.jpg'); gameImage.setAttribute('data-src', '/api/v1.1/Games/' + gameObject.id + '/cover/image/cover_big/' + gameObject.cover.imageId + '.jpg');
} else { } else {
gameImage.className = classes['game_tile_image unknown']; gameImage.classList.add(...classes['game_tile_image unknown']);
gameImage.setAttribute('data-src', '/images/unknowngame.png');
} }
gameImageBox.appendChild(gameImage); gameImageBox.appendChild(gameImage);
@@ -312,7 +409,6 @@ function renderGameIcon(gameObject, showTitle, showRatings, showClassification,
if (gameObject.ageRatings[c].category == classificationDisplayOrder[b]) { if (gameObject.ageRatings[c].category == classificationDisplayOrder[b]) {
shownClassificationBoard = classificationDisplayOrder[b]; shownClassificationBoard = classificationDisplayOrder[b];
displayClassification = true; displayClassification = true;
//classificationPath = '/api/v1.1/Ratings/Images/' + classificationDisplayOrder[b] + '/' + getKeyByValue(AgeRatingStrings, gameObject.ageRatings[c].rating) + '/image.svg';
classificationPath = '/images/Ratings/' + classificationDisplayOrder[b] + '/' + gameObject.ageRatings[c].rating + '.svg'; classificationPath = '/images/Ratings/' + classificationDisplayOrder[b] + '/' + gameObject.ageRatings[c].rating + '.svg';
} }
} }
@@ -326,7 +422,7 @@ function renderGameIcon(gameObject, showTitle, showRatings, showClassification,
if (gameObject.hasSavedGame == true) { if (gameObject.hasSavedGame == true) {
var gameSaveIcon = document.createElement('img'); var gameSaveIcon = document.createElement('img');
gameSaveIcon.src = '/images/SaveStates.png'; gameSaveIcon.src = '/images/SaveStates.png';
gameSaveIcon.className = classes['game_tile_box_savedgame savedstateicon']; gameSaveIcon.classList.add(...classes['game_tile_box_savedgame savedstateicon']);
gameImageBox.appendChild(gameSaveIcon); gameImageBox.appendChild(gameSaveIcon);
} }
@@ -334,13 +430,13 @@ function renderGameIcon(gameObject, showTitle, showRatings, showClassification,
if (gameObject.isFavourite == true) { if (gameObject.isFavourite == true) {
var gameFavIcon = document.createElement('img'); var gameFavIcon = document.createElement('img');
gameFavIcon.src = '/images/favourite-filled.svg'; gameFavIcon.src = '/images/favourite-filled.svg';
gameFavIcon.className = classes['game_tile_box_favouritegame favouriteicon']; gameFavIcon.classList.add(...classes['game_tile_box_favouritegame favouriteicon']);
gameImageBox.appendChild(gameFavIcon); gameImageBox.appendChild(gameFavIcon);
} }
if (gameObject.totalRating || displayClassification == true) { if (gameObject.totalRating || displayClassification == true) {
var gameImageRatingBanner = document.createElement('div'); var gameImageRatingBanner = document.createElement('div');
gameImageRatingBanner.className = classes['game_tile_box_ratingbanner']; gameImageRatingBanner.classList.add(...classes['game_tile_box_ratingbanner']);
if (showRatings == true || displayClassification == true) { if (showRatings == true || displayClassification == true) {
if (showRatings == true) { if (showRatings == true) {
@@ -361,7 +457,7 @@ function renderGameIcon(gameObject, showTitle, showRatings, showClassification,
if (displayClassification == true) { if (displayClassification == true) {
var gameImageClassificationLogo = document.createElement('img'); var gameImageClassificationLogo = document.createElement('img');
gameImageClassificationLogo.src = classificationPath; gameImageClassificationLogo.src = classificationPath;
gameImageClassificationLogo.className = classes['rating_image_overlay']; gameImageClassificationLogo.classList.add(...classes['rating_image_overlay']);
gameImageBox.appendChild(gameImageClassificationLogo); gameImageBox.appendChild(gameImageClassificationLogo);
} }
} }
@@ -370,7 +466,7 @@ function renderGameIcon(gameObject, showTitle, showRatings, showClassification,
if (showTitle == true) { if (showTitle == true) {
var gameBoxTitle = document.createElement('div'); var gameBoxTitle = document.createElement('div');
gameBoxTitle.className = classes['game_tile_label']; gameBoxTitle.classList.add(...classes['game_tile_label']);
gameBoxTitle.innerHTML = gameObject.name; gameBoxTitle.innerHTML = gameObject.name;
gameBox.appendChild(gameBoxTitle); gameBox.appendChild(gameBoxTitle);
} }
@@ -381,31 +477,31 @@ function renderGameIcon(gameObject, showTitle, showRatings, showClassification,
function getViewModeClasses(listView) { function getViewModeClasses(listView) {
if (listView == false) { if (listView == false) {
return { return {
"game_tile game_tile_small": "game_tile game_tile_small", "game_tile game_tile_small": [ "game_tile", "game_tile_small" ],
"game_tile": "game_tile", "game_tile": [ "game_tile" ],
"game_tile_box": "game_tile_box", "game_tile_box": [ "game_tile_box" ],
"game_tile_image game_tile_image_small lazy": "game_tile_image game_tile_image_small lazy", "game_tile_image game_tile_image_small lazy": [ "game_tile_image", "game_tile_image_small", "lazy" ],
"game_tile_image lazy": "game_tile_image lazy", "game_tile_image lazy": [ "game_tile_image", "lazy" ],
"game_tile_image unknown": "game_tile_image unknown", "game_tile_image unknown": [ "game_tile_image", "unknown" ],
"game_tile_box_savedgame savedstateicon": "game_tile_box_savedgame savedstateicon", "game_tile_box_savedgame savedstateicon": [ "game_tile_box_savedgame", "savedstateicon" ],
"game_tile_box_favouritegame favouriteicon": "game_tile_box_favouritegame favouriteicon", "game_tile_box_favouritegame favouriteicon": [ "game_tile_box_favouritegame", "favouriteicon" ],
"game_tile_box_ratingbanner": "game_tile_box_ratingbanner", "game_tile_box_ratingbanner": [ "game_tile_box_ratingbanner" ],
"rating_image_overlay": "rating_image_overlay", "rating_image_overlay": [ "rating_image_overlay" ],
"game_tile_label": "game_tile_label" "game_tile_label": [ "game_tile_label" ]
}; };
} else { } else {
return { return {
"game_tile game_tile_small": "game_tile_row game_tile_small", "game_tile game_tile_small": [ "game_tile_row", "game_tile_small" ],
"game_tile": "game_tile_row", "game_tile": [ "game_tile_row" ],
"game_tile_box": "game_tile_box_row", "game_tile_box": [ "game_tile_box_row" ],
"game_tile_image game_tile_image_small lazy": "game_tile_image_row game_tile_image_small lazy", "game_tile_image game_tile_image_small lazy": [ "game_tile_image_row", "game_tile_image_small", "lazy" ],
"game_tile_image lazy": "game_tile_image_row lazy", "game_tile_image lazy": [ "game_tile_image_row", "lazy" ],
"game_tile_image unknown": "game_tile_image_row unknown", "game_tile_image unknown": [ "game_tile_image_row", "unknown" ],
"game_tile_box_savedgame savedstateicon": "game_tile_box_savedgame_row savedstateicon", "game_tile_box_savedgame savedstateicon": [ "game_tile_box_savedgame_row", "savedstateicon" ],
"game_tile_box_favouritegame favouriteicon": "game_tile_box_favouritegame_row favouriteicon", "game_tile_box_favouritegame favouriteicon": [ "game_tile_box_favouritegame_row", "favouriteicon" ],
"game_tile_box_ratingbanner": "game_tile_box_ratingbanner_row", "game_tile_box_ratingbanner": [ "game_tile_box_ratingbanner_row" ],
"rating_image_overlay": "rating_image_overlay_row", "rating_image_overlay": [ "rating_image_overlay_row" ],
"game_tile_label": "game_tile_label_row" "game_tile_label": [ "game_tile_label_row" ]
}; };
} }
} }

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 {
@@ -548,11 +569,25 @@ input[name='filter_panel_range_max'] {
background-color: blue; background-color: blue;
} }
.game_tile_wrapper {
display: inline-block;
}
.game_tile_placeholder {
display: inline-block;
min-height: 243px;
width: 232px;
margin-bottom: 10px;
}
.game_tile { .game_tile {
padding: 5px; padding-top: 10px;
padding-bottom: 5px;
padding-left: 5px;
padding-right: 5px;
display: inline-block; display: inline-block;
width: 220px; width: 220px;
min-height: 200px; min-height: 243px;
align-items: center; align-items: center;
justify-content: center; justify-content: center;
text-align: center; text-align: center;
@@ -590,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;
@@ -689,9 +730,12 @@ input[name='filter_panel_range_max'] {
max-height: 200px; max-height: 200px;
min-height: 200px; min-height: 200px;
background-color: transparent; background-color: transparent;
box-shadow: 5px 5px 19px 0px rgba(0,0,0,0.44); }
-webkit-box-shadow: 5px 5px 19px 0px rgba(0,0,0,0.44);
-moz-box-shadow: 5px 5px 19px 0px rgba(0,0,0,0.44); .game_tile_image_shadow {
box-shadow: 5px 5px 19px 0px rgba(0, 0, 0, 0.44);
-webkit-box-shadow: 5px 5px 19px 0px rgba(0, 0, 0, 0.44);
-moz-box-shadow: 5px 5px 19px 0px rgba(0, 0, 0, 0.44);
} }
.game_tile_image_row { .game_tile_image_row {
@@ -701,16 +745,15 @@ input[name='filter_panel_range_max'] {
min-height: 75px; min-height: 75px;
margin-left: 10px; margin-left: 10px;
background-color: transparent; background-color: transparent;
box-shadow: 5px 5px 19px 0px rgba(0,0,0,0.44);
-webkit-box-shadow: 5px 5px 19px 0px rgba(0,0,0,0.44);
-moz-box-shadow: 5px 5px 19px 0px rgba(0,0,0,0.44);
} }
.game_tile_image, .unknown { .game_tile_image,
.unknown {
background-color: transparent; background-color: transparent;
} }
.game_tile_image_row, .unknown { .game_tile_image_row,
.unknown {
background-color: transparent; background-color: transparent;
} }
@@ -776,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 {
@@ -828,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 {
@@ -889,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 {
@@ -967,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;
@@ -1043,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 {
@@ -1070,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 {
@@ -1101,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;
} }
@@ -1136,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;
} }
@@ -1183,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;
} }
@@ -1288,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;
@@ -1363,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;
} }
@@ -1384,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 {
@@ -1426,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;
} }
@@ -1454,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 {
@@ -1506,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;
@@ -1543,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);
@@ -1561,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;
} }
@@ -1601,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;
@@ -1611,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;