Compare commits

...

79 Commits

Author SHA1 Message Date
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
f2a58d23f0 Fix for broken first run on fresh installs (#297)
* Fix for broken first run on fresh installs
2024-02-11 00:53:22 +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
Michael Green
07c973cd75 Fixed captialisation issue in sql query
* Fixed captialisation issue in sql query
2024-02-08 14:43:20 +11:00
Michael Green
b94950fed0 Create an icon
* Create an icon Fixes #63

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

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

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

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

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

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

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

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

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

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

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

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

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

* Add timezone variable to docker-compose files

* Release note update to include PR's labeled "note"
2024-01-28 15:18:13 +11:00
Michael Green
163aa7a446 Add enhanced schedule management for background tasks (#267) 2024-01-27 23:30:35 +11:00
Michael Green
ec115b33de Add alpha pager, and move main pager to bottom of page (#265)
* Add alpha paging, move main pager to the bottom of the page
2024-01-22 01:17:54 +11:00
Michael Green
9b8874902a Migrate to new EJS CDN, and save state tweaks (#264)
* Saved game icon now displays on game cover art in library

* Fixed casing error on save state download icon

* Migrate EJS from submodule to 7z download during docker build

* Updated README and gitignore

* Resized library search buttons

* Export to JSON now triggers the download rather than display of a formatted platform map
2024-01-20 16:12:21 +11:00
Michael Green
127eab683b Save states are now saved to the Gaseous host, making them available anywhere (#255)
* Added ability to save emulator state

* Save states can now be fully managed during a game

* Save states can also be launched from the game info screen
2024-01-15 11:37:18 +11:00
Michael Green
1efc47f9cd Re-enabled artwork backgrounds
* Re-enabled artwork backgrounds
2024-01-10 22:44:47 +11:00
137 changed files with 9601 additions and 3636 deletions

4
.devcontainer/.env Normal file
View File

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

6
.devcontainer/Dockerfile Normal file
View File

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

View File

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

View File

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

View File

@@ -9,9 +9,7 @@ updates:
directory: "/" # Location of package manifests
schedule:
interval: "weekly"
- package-ecosystem: "gitsubmodule"
- package-ecosystem: "devcontainers"
directory: "/"
allow:
- dependency-name: "gaseous-server/wwwroot/emulators/EmulatorJS"
schedule:
interval: "weekly"
interval: "weekly"

4
.github/release.yml vendored
View File

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

View File

@@ -9,9 +9,14 @@ on:
jobs:
docker:
runs-on: ubuntu-latest
permissions:
packages: write
contents: read
attestations: write
id-token: write
steps:
- name: Checkout
uses: actions/checkout@v3
uses: actions/checkout@v4
with:
submodules: 'true'
- name: Install dotnet tool
@@ -21,18 +26,37 @@ jobs:
- name: Sign in to Nuget
run: dotnet nuget add source --username michael-j-green --password ${{ secrets.NUGETKEY }} --store-password-in-clear-text --name github "https://nuget.pkg.github.com/gaseous-project/index.json"
- name: Set up QEMU
uses: docker/setup-qemu-action@v2
uses: docker/setup-qemu-action@v3
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v2
uses: docker/setup-buildx-action@v3
- name: Login to Docker Hub
uses: docker/login-action@v2
uses: docker/login-action@v3
with:
username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_TOKEN }}
- name: Build and push
uses: docker/build-push-action@v4
- name: Login to GitHub Package Registry
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:
context: .
platforms: linux/amd64 #,linux/arm64
file: ./build/Dockerfile
platforms: linux/amd64,linux/arm64
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:
docker:
runs-on: ubuntu-latest
permissions:
packages: write
contents: read
attestations: write
id-token: write
steps:
- name: Checkout
uses: actions/checkout@v3
uses: actions/checkout@v4
with:
submodules: 'true'
- name: Install dotnet tool
@@ -20,18 +25,41 @@ jobs:
- name: Sign in to Nuget
run: dotnet nuget add source --username michael-j-green --password ${{ secrets.NUGETKEY }} --store-password-in-clear-text --name github "https://nuget.pkg.github.com/gaseous-project/index.json"
- name: Set up QEMU
uses: docker/setup-qemu-action@v2
uses: docker/setup-qemu-action@v3
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v2
uses: docker/setup-buildx-action@v3
- name: Login to Docker Hub
uses: docker/login-action@v2
uses: docker/login-action@v3
with:
username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_TOKEN }}
- name: Build and push
uses: docker/build-push-action@v4
- name: Login to GitHub Package Registry
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:
context: .
platforms: linux/amd64 #,linux/arm64
file: ./build/Dockerfile
platforms: linux/amd64,linux/arm64
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

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

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

View File

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

1
.gitignore vendored
View File

@@ -404,3 +404,4 @@ ASALocalRun/
# Local History for Visual Studio
.localhistory/
gaseous-server/.DS_Store
gaseous-server/wwwroot/emulators/EmulatorJS

3
.gitmodules vendored
View File

@@ -1,3 +0,0 @@
[submodule "gaseous-server/wwwroot/emulators/EmulatorJS"]
path = gaseous-server/wwwroot/emulators/EmulatorJS
url = https://github.com/EmulatorJS/EmulatorJS.git

7
.vscode/launch.json vendored
View File

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

View File

@@ -1,16 +0,0 @@
FROM mcr.microsoft.com/dotnet/sdk:7.0 AS build-env
WORKDIR /App
EXPOSE 80
# Copy everything
COPY . ./
# Restore as distinct layers
RUN dotnet restore "gaseous-server/gaseous-server.csproj"
# Build and publish a release
RUN dotnet publish "gaseous-server/gaseous-server.csproj" --use-current-runtime --self-contained false -c Release -o out
# Build runtime image
FROM mcr.microsoft.com/dotnet/aspnet:7.0
WORKDIR /App
COPY --from=build-env /App/out .
ENTRYPOINT ["dotnet", "gaseous-server.dll"]

View File

@@ -3,7 +3,7 @@ Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio Version 16
VisualStudioVersion = 25.0.1704.4
MinimumVisualStudioVersion = 10.0.40219.1
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "gaseous-server", "gaseous-server\gaseous-server.csproj", "{A01D2EFF-C82E-473B-84D7-7C25E736F5D2}"
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "gaseous-server", "gaseous-server\gaseous-server.csproj", "{A01D2EFF-C82E-473B-84D7-7C25E736F5D2}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{17FA6F12-8532-420C-9489-CB8FDE42137C}"
ProjectSection(SolutionItems) = preProject
@@ -27,30 +27,18 @@ Global
Release|Any CPU = Release|Any CPU
EndGlobalSection
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.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.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
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {979BF092-AFBC-4F19-B55F-32E73959BD1A}
EndGlobalSection
GlobalSection(NestedProjects) = preSolution
{F1A847C7-57BC-4DA9-8F83-CD060A7F5122} = {17FA6F12-8532-420C-9489-CB8FDE42137C}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {979BF092-AFBC-4F19-B55F-32E73959BD1A}
EndGlobalSection
EndGlobal

149
LICENSE
View File

@@ -1,5 +1,5 @@
GNU GENERAL PUBLIC LICENSE
Version 3, 29 June 2007
GNU AFFERO GENERAL PUBLIC LICENSE
Version 3, 19 November 2007
Copyright (C) 2007 Free Software Foundation, Inc. <https://fsf.org/>
Everyone is permitted to copy and distribute verbatim copies
@@ -7,17 +7,15 @@
Preamble
The GNU General Public License is a free, copyleft license for
software and other kinds of works.
The GNU Affero General Public License is a free, copyleft license for
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
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
software for all its users. We, the Free Software Foundation, use the
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.
software for all its users.
When we speak of free software, we are referring to freedom, not
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
free programs, and that you know you can do these things.
To protect your rights, we need to prevent others from denying you
these rights or asking you to surrender the rights. Therefore, you have
certain responsibilities if you distribute copies of the software, or if
you modify it: responsibilities to respect the freedom of others.
Developers that use our General Public Licenses protect your rights
with two steps: (1) assert copyright on the software, and (2) offer
you this License which gives you legal permission to copy, distribute
and/or modify the software.
For example, if you distribute copies of such a program, whether
gratis or for a fee, you must pass on to the recipients the same
freedoms that you received. You must make sure that they, too, receive
or can get the source code. And you must show them these terms so they
know their rights.
A secondary benefit of defending all users' freedom is that
improvements made in alternate versions of the program, if they
receive widespread use, become available for other developers to
incorporate. Many developers of free software are heartened and
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:
(1) assert copyright on the software, and (2) offer you this License
giving you legal permission to copy, distribute and/or modify it.
The GNU Affero General Public License is designed specifically to
ensure that, in such cases, the modified source code becomes available
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
that there is no warranty for this free software. For both users' and
authors' sake, the GPL requires that modified versions be marked as
changed, so that their problems will not be attributed erroneously to
authors of previous versions.
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.
An older license, called the Affero General Public License and
published by Affero, was designed to accomplish similar goals. This is
a different license, not a version of the Affero GPL, but Affero has
released a new version of the Affero GPL which permits relicensing under
this license.
The precise terms and conditions for copying, distribution and
modification follow.
@@ -72,7 +60,7 @@ modification follow.
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
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
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
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
License will continue to apply to the part which is the covered work,
but the special requirements of the GNU Affero General Public License,
section 13, concerning interaction through a network will apply to the
combination as such.
but the work with which it is combined will remain governed by version
3 of the GNU General Public License.
14. Revised Versions of this License.
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
be similar in spirit to the present version, but may differ in detail to
the GNU Affero General Public License from time to time. Such new versions
will be similar in spirit to the present version, but may differ in detail to
address new problems or concerns.
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
option of following the terms and conditions either of that numbered
version or of any later version published by the Free Software
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.
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
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
the "copyright" line and a pointer to where the full notice is found.
Gaseous
Copyright (C) 2023 Gaseous
<one line to give the program's name and a brief idea of what it does.>
Copyright (C) <year> <name of author>
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
the Free Software Foundation, either version 3 of the License, or
it under the terms of the GNU Affero General Public License as published
by the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
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/>.
Also add information on how to contact you by electronic and paper mail.
If the program does terminal interaction, make it output a short
notice like this when it starts in an interactive mode:
<program> Copyright (C) 2023 Gaseous
This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
This is free software, and you are welcome to redistribute it
under certain conditions; type `show c' for details.
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".
If your software can interact with users remotely through a computer
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
interface could display a "Source" link that leads users to an archive
of the code. There are many ways you could offer source, and different
solutions will be better for different programs; see section 13 for the
specific requirements.
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.
For more information on this, and how to apply and follow the GNU GPL, see
<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>.
For more information on this, and how to apply and follow the GNU AGPL, see
<https://www.gnu.org/licenses/>.

193
README.MD
View File

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

41
build/Dockerfile Normal file
View File

@@ -0,0 +1,41 @@
FROM --platform=$BUILDPLATFORM mcr.microsoft.com/dotnet/sdk:8.0 AS build-env
ARG TARGETARCH
ARG BUILDPLATFORM
WORKDIR /App
EXPOSE 80
RUN echo "Target: $TARGETARCH"
RUN echo "Build: $BUILDPLATFORM"
# Copy everything
COPY .. ./
# Restore as distinct layers
RUN dotnet restore "gaseous-server/gaseous-server.csproj" -a $TARGETARCH
# Build and publish a release
RUN dotnet publish "gaseous-server/gaseous-server.csproj" --use-current-runtime --self-contained true -c Release -o out -a $TARGETARCH
# disabled for 1.7.4 as the next version EmulatorJS is not yet available
# # update apt-get
# RUN apt-get update
# # download and unzip EmulatorJS from CDN
# RUN apt-get install -y p7zip-full
# RUN mkdir -p out/wwwroot/emulators/EmulatorJS
# RUN wget https://cdn.emulatorjs.org/releases/4.0.12.7z
# RUN 7z x -y -oout/wwwroot/emulators/EmulatorJS 4.0.12.7z
RUN wget --recursive --no-parent https://cdn.emulatorjs.org/latest/
RUN mkdir -p out/wwwroot/emulators/EmulatorJS
RUN cp -fr cdn.emulatorjs.org/latest/* out/wwwroot/emulators/EmulatorJS
RUN rm -Rf cdn.emulatorjs.org
# clean up apt-get
RUN apt-get clean && rm -rf /var/lib/apt/lists
# Build runtime image
FROM mcr.microsoft.com/dotnet/aspnet:8.0
ENV INDOCKER=1
WORKDIR /App
COPY --from=build-env /App/out .
# start gaseous-server
ENTRYPOINT ["dotnet", "gaseous-server.dll"]

View File

@@ -0,0 +1,77 @@
FROM --platform=$BUILDPLATFORM mcr.microsoft.com/dotnet/sdk:8.0 AS build-env
ARG TARGETARCH
ARG BUILDPLATFORM
WORKDIR /App
EXPOSE 80
RUN echo "Target: $TARGETARCH"
RUN echo "Build: $BUILDPLATFORM"
# Copy everything
COPY .. ./
# Restore as distinct layers
RUN dotnet restore "gaseous-server/gaseous-server.csproj" -a $TARGETARCH
# Build and publish a release
RUN dotnet publish "gaseous-server/gaseous-server.csproj" --use-current-runtime --self-contained true -c Release -o out -a $TARGETARCH
# disabled for 1.7.4 as the next version EmulatorJS is not yet available
# # update apt-get
# RUN apt-get update
# # download and unzip EmulatorJS from CDN
# RUN apt-get install -y p7zip-full
# RUN mkdir -p out/wwwroot/emulators/EmulatorJS
# RUN wget https://cdn.emulatorjs.org/releases/4.0.12.7z
# RUN 7z x -y -oout/wwwroot/emulators/EmulatorJS 4.0.12.7z
RUN wget --recursive --no-parent https://cdn.emulatorjs.org/latest/
RUN mkdir -p out/wwwroot/emulators/EmulatorJS
RUN cp -fr cdn.emulatorjs.org/latest/* out/wwwroot/emulators/EmulatorJS
RUN rm -Rf cdn.emulatorjs.org
# Build runtime image
FROM mcr.microsoft.com/dotnet/aspnet:8.0
ENV INDOCKER=1
WORKDIR /App
COPY --from=build-env /App/out .
# variables
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" ]

12
build/entrypoint.sh Normal file
View File

@@ -0,0 +1,12 @@
#!/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 -G sudo
usermod -p "*" gaseous
mkdir -p /home/gaseous/.gaseous-server /var/lib/mysql
chown -R ${PUID}:${PGID} /App /home/gaseous/.gaseous-server /var/lib/mysql /run/mysqld
# Start supervisord and services
/usr/bin/supervisord -c /etc/supervisor/conf.d/supervisord.conf

17
build/mariadb.sh Normal file
View File

@@ -0,0 +1,17 @@
#!/bin/sh
# 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'; FLUSH PRIVILEGES;"
# stop the server
sleep 5
killall mariadbd
# start the server normally
/usr/sbin/mariadbd --datadir=/var/lib/mysql

35
build/supervisord.conf Normal file
View File

@@ -0,0 +1,35 @@
[supervisord]
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
autostart=true
autorestart=true
redirect_stderr=true
stdout_logfile=/dev/fd/1
stdout_logfile_maxbytes=0

View File

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

View File

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

View File

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

View File

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

Binary file not shown.

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -1,5 +1,7 @@
using System.Collections.Concurrent;
using System.Collections.Concurrent;
using System.ComponentModel;
using System.Data;
using System.IO.Compression;
using System.Reflection;
using System.Security.Cryptography;
@@ -18,7 +20,8 @@ namespace gaseous_server.Classes
if (ObjectToCheck == null || ObjectToCheck == System.DBNull.Value)
{
return IfNullValue;
} else
}
else
{
return ObjectToCheck;
}
@@ -26,10 +29,10 @@ namespace gaseous_server.Classes
static public DateTime ConvertUnixToDateTime(double UnixTimeStamp)
{
DateTime dateTime = new DateTime(1970, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc);
dateTime = dateTime.AddSeconds(UnixTimeStamp).ToLocalTime();
return dateTime;
}
DateTime dateTime = new DateTime(1970, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc);
dateTime = dateTime.AddSeconds(UnixTimeStamp).ToLocalTime();
return dateTime;
}
public class hashObject
{
@@ -40,21 +43,23 @@ namespace gaseous_server.Classes
public hashObject(string FileName)
{
var xmlStream = File.OpenRead(FileName);
var xmlStream = File.OpenRead(FileName);
var md5 = MD5.Create();
byte[] md5HashByte = md5.ComputeHash(xmlStream);
string md5Hash = BitConverter.ToString(md5HashByte).Replace("-", "").ToLowerInvariant();
Logging.Log(Logging.LogType.Information, "Hash File", "Generating MD5 hash for file: " + FileName);
var md5 = MD5.Create();
byte[] md5HashByte = md5.ComputeHash(xmlStream);
string md5Hash = BitConverter.ToString(md5HashByte).Replace("-", "").ToLowerInvariant();
_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;
byte[] sha1HashByte = sha1.ComputeHash(xmlStream);
string sha1Hash = BitConverter.ToString(sha1HashByte).Replace("-", "").ToLowerInvariant();
byte[] sha1HashByte = sha1.ComputeHash(xmlStream);
string sha1Hash = BitConverter.ToString(sha1HashByte).Replace("-", "").ToLowerInvariant();
_sha1hash = sha1Hash;
xmlStream.Close();
}
}
string _md5hash = "";
string _sha1hash = "";
@@ -84,29 +89,29 @@ namespace gaseous_server.Classes
}
}
public static long DirSize(DirectoryInfo d)
{
long size = 0;
// Add file sizes.
FileInfo[] fis = d.GetFiles();
foreach (FileInfo fi in fis)
{
size += fi.Length;
}
// Add subdirectory sizes.
DirectoryInfo[] dis = d.GetDirectories();
foreach (DirectoryInfo di in dis)
{
size += DirSize(di);
}
return size;
}
public static long DirSize(DirectoryInfo d)
{
long size = 0;
// Add file sizes.
FileInfo[] fis = d.GetFiles();
foreach (FileInfo fi in fis)
{
size += fi.Length;
}
// Add subdirectory sizes.
DirectoryInfo[] dis = d.GetDirectories();
foreach (DirectoryInfo di in dis)
{
size += DirSize(di);
}
return size;
}
public static string[] SkippableFiles = {
".DS_STORE",
"desktop.ini"
};
public static string NormalizePath(string path)
{
return Path.GetFullPath(new Uri(path).LocalPath)
@@ -120,30 +125,64 @@ namespace gaseous_server.Classes
.Single(x => x.GetValue(null).Equals(value)),
typeof(DescriptionAttribute)))?.Description ?? value.ToString();
}
}
// compression
public static byte[] Compress(byte[] data)
{
MemoryStream output = new MemoryStream();
using (DeflateStream dstream = new DeflateStream(output, CompressionLevel.Optimal))
{
dstream.Write(data, 0, data.Length);
}
return output.ToArray();
}
public static byte[] Decompress(byte[] data)
{
MemoryStream input = new MemoryStream(data);
MemoryStream output = new MemoryStream();
using (DeflateStream dstream = new DeflateStream(input, CompressionMode.Decompress))
{
dstream.CopyTo(output);
}
return output.ToArray();
}
public static object GetEnvVar(string envName, string defaultValue)
{
if (!String.IsNullOrEmpty(Environment.GetEnvironmentVariable(envName)))
{
return Environment.GetEnvironmentVariable(envName);
}
else
{
return defaultValue;
}
}
}
/// <summary>
/// Provides a way to set contextual data that flows with the call and
/// async context of a test or invocation.
/// </summary>
public static class CallContext
{
static ConcurrentDictionary<string, AsyncLocal<object>> state = new ConcurrentDictionary<string, AsyncLocal<object>>();
/// Provides a way to set contextual data that flows with the call and
/// async context of a test or invocation.
/// </summary>
public static class CallContext
{
static ConcurrentDictionary<string, AsyncLocal<object>> state = new ConcurrentDictionary<string, AsyncLocal<object>>();
/// <summary>
/// Stores a given object and associates it with the specified name.
/// </summary>
/// <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>
public static void SetData(string name, object data) =>
state.GetOrAdd(name, _ => new AsyncLocal<object>()).Value = data;
/// <summary>
/// Stores a given object and associates it with the specified name.
/// </summary>
/// <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>
public static void SetData(string name, object data) =>
state.GetOrAdd(name, _ => new AsyncLocal<object>()).Value = data;
/// <summary>
/// Retrieves an object with the specified name from the <see cref="CallContext"/>.
/// </summary>
/// <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>
public static object GetData(string name) =>
state.TryGetValue(name, out AsyncLocal<object> data) ? data.Value : null;
}
/// <summary>
/// Retrieves an object with the specified name from the <see cref="CallContext"/>.
/// </summary>
/// <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>
public static object GetData(string name) =>
state.TryGetValue(name, out AsyncLocal<object> data) ? data.Value : null;
}
}

View File

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

View File

@@ -9,6 +9,21 @@ namespace gaseous_server.Classes
{
public class Database
{
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()
{
@@ -78,7 +93,16 @@ namespace gaseous_server.Classes
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 dbScript = "";
@@ -94,7 +118,7 @@ namespace gaseous_server.Classes
// apply script
sql = "SELECT schema_version FROM schema_version;";
dbDict = new Dictionary<string, object>();
DataTable SchemaVersion = ExecuteCMD(sql, dbDict);
SchemaVersion = ExecuteCMD(sql, dbDict);
if (SchemaVersion.Rows.Count == 0)
{
// something is broken here... where's the table?
@@ -105,6 +129,8 @@ namespace gaseous_server.Classes
{
int SchemaVer = (int)SchemaVersion.Rows[0][0];
Logging.Log(Logging.LogType.Information, "Database", "Schema version is " + SchemaVer);
// update schema version variable
Database.schema_version = SchemaVer;
if (SchemaVer < i)
{
try
@@ -123,6 +149,9 @@ namespace gaseous_server.Classes
// run post-upgrade code
DatabaseMigration.PostUpgradeScript(i, _ConnectorType);
// update schema version variable
Database.schema_version = i;
}
catch (Exception ex)
{

View File

@@ -1,23 +1,75 @@
using System;
using System.Data;
using System.Reflection;
namespace gaseous_server.Classes
{
public static class DatabaseMigration
{
public static class DatabaseMigration
{
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
var assembly = Assembly.GetExecutingAssembly();
Database db = new Database(Database.databaseType.MySql, Config.DatabaseConfiguration.ConnectionString);
string sql = "";
Dictionary<string, object> dbDict = new Dictionary<string, object>();
DataTable data;
Logging.Log(Logging.LogType.Information, "Database", "Checking for pre-upgrade for schema version " + TargetSchemaVersion);
switch (DatabaseType)
{
case Database.databaseType.MySql:
switch (TargetSchemaVersion)
{
case 1005:
Logging.Log(Logging.LogType.Information, "Database", "Running pre-upgrade for schema version " + TargetSchemaVersion);
// there was a mistake at dbschema version 1004-1005
// the first preview release of v1.7 reused dbschema version 1004
// if table "Relation_Game_AgeRatings" exists - then we need to apply the gaseous-fix-1005.sql script before applying the standard 1005 script
sql = "SELECT table_name FROM information_schema.tables WHERE table_schema = @dbname AND table_name = @tablename;";
dbDict.Add("dbname", Config.DatabaseConfiguration.DatabaseName);
dbDict.Add("tablename", "Relation_Game_AgeRatings");
data = db.ExecuteCMD(sql, dbDict);
if (data.Rows.Count == 0)
{
Logging.Log(Logging.LogType.Information, "Database", "Schema version " + TargetSchemaVersion + " requires a table which is missing.");
string resourceName = "gaseous_server.Support.Database.MySQL.gaseous-fix-1005.sql";
string dbScript = "";
string[] resources = Assembly.GetExecutingAssembly().GetManifestResourceNames();
if (resources.Contains(resourceName))
{
using (Stream stream = assembly.GetManifestResourceStream(resourceName))
using (StreamReader reader = new StreamReader(stream))
{
dbScript = reader.ReadToEnd();
// apply schema!
Logging.Log(Logging.LogType.Information, "Database", "Applying schema version fix prior to version 1005");
db.ExecuteCMD(dbScript, dbDict, 180);
}
}
}
break;
}
break;
}
}
public static void PostUpgradeScript(int TargetSchemaVersion, Database.databaseType? DatabaseType)
public static void PostUpgradeScript(int TargetSchemaVersion, Database.databaseType? DatabaseType)
{
Database db = new Database(Database.databaseType.MySql, Config.DatabaseConfiguration.ConnectionString);
string sql = "";
Dictionary<string, object> dbDict = new Dictionary<string, object>();
DataTable data;
switch(DatabaseType)
switch (DatabaseType)
{
case Database.databaseType.MySql:
switch (TargetSchemaVersion)
@@ -26,18 +78,18 @@ namespace gaseous_server.Classes
// this is a safe background task
BackgroundUpgradeTargetSchemaVersions.Add(1002);
break;
case 1004:
// needs to run on start up
// copy root path to new libraries format
string oldRoot = Path.Combine(Config.LibraryConfiguration.LibraryRootDirectory, "Library");
string sql = "INSERT INTO GameLibraries (Name, Path, DefaultLibrary, DefaultPlatform) VALUES (@name, @path, @defaultlibrary, @defaultplatform); SELECT CAST(LAST_INSERT_ID() AS SIGNED);";
sql = "INSERT INTO GameLibraries (Name, Path, DefaultLibrary, DefaultPlatform) VALUES (@name, @path, @defaultlibrary, @defaultplatform); SELECT CAST(LAST_INSERT_ID() AS SIGNED);";
dbDict.Add("name", "Default");
dbDict.Add("path", oldRoot);
dbDict.Add("defaultlibrary", 1);
dbDict.Add("defaultplatform", 0);
DataTable data = db.ExecuteCMD(sql, dbDict);
data = db.ExecuteCMD(sql, dbDict);
// apply the new library id to the existing roms
sql = "UPDATE Games_Roms SET LibraryId=@libraryid;";
@@ -46,6 +98,11 @@ namespace gaseous_server.Classes
db.ExecuteCMD(sql, dbDict);
break;
case 1016:
// delete old format LastRun_* settings from settings table
sql = "DELETE FROM Settings WHERE Setting LIKE 'LastRun_%';";
db.ExecuteNonQuery(sql);
break;
}
break;
}
@@ -64,7 +121,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);
string sql = "";
Dictionary<string, object> dbDict = new Dictionary<string, object>();
@@ -77,7 +135,7 @@ namespace gaseous_server.Classes
Logging.Log(Logging.LogType.Information, "Signature Ingestor - Database Update", "Updating " + data.Rows.Count + " database entries");
int Counter = 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<KeyValuePair<string, object>> Attributes = new List<KeyValuePair<string, object>>();
@@ -150,7 +208,7 @@ namespace gaseous_server.Classes
dbDict.Add("id", (int)row["Id"]);
db.ExecuteCMD(updateSQL, dbDict);
if ((Counter - LastCounterCheck) > 10)
if ((Counter - LastCounterCheck) > 10)
{
LastCounterCheck = Counter;
Logging.Log(Logging.LogType.Information, "Signature Ingestor - Database Update", "Updating " + Counter + " / " + data.Rows.Count + " database entries");

View File

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

View File

@@ -31,7 +31,7 @@ namespace gaseous_server.Classes
if (!Directory.Exists(ExtractPath)) { Directory.CreateDirectory(ExtractPath); }
try
{
switch(ImportedFileExtension)
switch (ImportedFileExtension)
{
case ".zip":
Logging.Log(Logging.LogType.Information, "Get Signature", "Decompressing using zip");
@@ -105,7 +105,7 @@ namespace gaseous_server.Classes
}
break;
}
Logging.Log(Logging.LogType.Information, "Get Signature", "Processing decompressed files for signature matches");
// loop through contents until we find the first signature match
List<ArchiveData> archiveFiles = new List<ArchiveData>();
@@ -116,12 +116,13 @@ namespace gaseous_server.Classes
{
FileInfo zfi = new FileInfo(file);
Common.hashObject zhash = new Common.hashObject(file);
Logging.Log(Logging.LogType.Information, "Get Signature", "Checking signature of decompressed file " + file);
if (zfi != null)
{
ArchiveData archiveData = new ArchiveData{
ArchiveData archiveData = new ArchiveData
{
FileName = Path.GetFileName(file),
FilePath = zfi.Directory.FullName.Replace(ExtractPath, ""),
Size = zfi.Length,
@@ -138,7 +139,7 @@ namespace gaseous_server.Classes
if (zDiscoveredSignature.Score > discoveredSignature.Score)
{
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
)
{
@@ -195,7 +196,7 @@ namespace gaseous_server.Classes
{
// signature retrieved from Hasheous
Logging.Log(Logging.LogType.Information, "Import Game", "Signature retrieved from Hasheous for game: " + dbSignature.Game.Name);
discoveredSignature = dbSignature;
}
else
@@ -203,7 +204,7 @@ namespace gaseous_server.Classes
// construct a signature from file data
dbSignature = _GetFileSignatureFromFileData(hash, ImageName, ImageExtension, ImageSize, GameFileImportPath);
Logging.Log(Logging.LogType.Information, "Import Game", "Signature generated from provided file for game: " + dbSignature.Game.Name);
discoveredSignature = dbSignature;
}
}
@@ -216,8 +217,8 @@ namespace gaseous_server.Classes
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);
// 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)
{
HasheousClient.Hasheous hasheous = new HasheousClient.Hasheous();
SignatureLookupItem? HasheousResult = hasheous.RetrieveFromHasheousAsync(new HashLookupModel{
MD5 = hash.md5hash,
SHA1 = hash.sha1hash
});
SignatureLookupItem? HasheousResult = null;
if (HasheousResult != null)
try
{
if (HasheousResult.Signature != null)
HasheousResult = hasheous.RetrieveFromHasheousAsync(new HashLookupModel
{
gaseous_server.Models.Signatures_Games signature = new Models.Signatures_Games();
signature.Game = HasheousResult.Signature.Game;
signature.Rom = HasheousResult.Signature.Rom;
if (HasheousResult.MetadataResults != null)
MD5 = hash.md5hash,
SHA1 = hash.sha1hash
});
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;
signature.Flags.IGDBGameId = (long)metadataResult.GameId;
if (metadataResult.Source == MetadataModel.MetadataSources.IGDB)
{
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;

View File

@@ -16,48 +16,49 @@ using HasheousClient.Models;
namespace gaseous_server.Classes
{
public class ImportGame : QueueItemStatus
{
public class ImportGame : QueueItemStatus
{
public void ProcessDirectory(string 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);
// import files first
// import files first
int importCount = 1;
foreach (string importContent in importContents) {
foreach (string importContent in importContents)
{
SetStatus(importCount, importContents.Length, "Importing file: " + importContent);
ImportGameFile(importContent, null);
ImportGameFile(importContent, null);
importCount += 1;
}
}
ClearStatus();
DeleteOrphanedDirectories(ImportPath);
}
else
{
Logging.Log(Logging.LogType.Critical, "Import Games", "The import directory " + ImportPath + " does not exist.");
throw new DirectoryNotFoundException("Invalid path: " + ImportPath);
}
else
{
Logging.Log(Logging.LogType.Critical, "Import Games", "The import directory " + ImportPath + " does not exist.");
throw new DirectoryNotFoundException("Invalid path: " + ImportPath);
}
}
public void ImportGameFile(string GameFileImportPath, IGDB.Models.Platform? OverridePlatform)
{
{
Database db = new Database(Database.databaseType.MySql, Config.DatabaseConfiguration.ConnectionString);
string sql = "";
Dictionary<string, object> dbDict = new Dictionary<string, object>();
string sql = "";
Dictionary<string, object> dbDict = new Dictionary<string, object>();
if (Common.SkippableFiles.Contains<string>(Path.GetFileName(GameFileImportPath), StringComparer.OrdinalIgnoreCase))
{
{
Logging.Log(Logging.LogType.Debug, "Import Game", "Skipping item " + GameFileImportPath);
}
else
{
else
{
FileInfo fi = new FileInfo(GameFileImportPath);
Common.hashObject hash = new Common.hashObject(GameFileImportPath);
@@ -87,7 +88,7 @@ namespace gaseous_server.Classes
}
File.Move(GameFileImportPath, targetPathWithFileName, true);
}
// import source was the upload directory
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)
{
@@ -173,7 +174,7 @@ namespace gaseous_server.Classes
string GameName = Signature.Game.Name;
List<string> SearchCandidates = GetSearchCandidates(GameName);
foreach (string SearchCandidate in SearchCandidates)
{
bool GameFound = false;
@@ -197,8 +198,10 @@ namespace gaseous_server.Classes
Logging.Log(Logging.LogType.Information, "Import Game", " " + games.Length + " search results found");
// quite likely we've found sequels and alternate types
foreach (Game game in games) {
if (game.Name == SearchCandidate) {
foreach (Game game in games)
{
if (game.Name == SearchCandidate)
{
// found game title matches the search candidate
determinedGame = Metadata.Games.GetGame((long)games[0].Id, false, false, false);
Logging.Log(Logging.LogType.Information, "Import Game", "Found exact match!");
@@ -273,7 +276,8 @@ namespace gaseous_server.Classes
// assumption: no games have () in their titles so we'll remove them
int idx = GameName.IndexOf('(');
if (idx >= 0) {
if (idx >= 0)
{
GameName = GameName.Substring(0, idx);
}
@@ -305,7 +309,8 @@ namespace gaseous_server.Classes
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);";
} 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;";
dbDict.Add("id", UpdateId);
@@ -348,7 +353,8 @@ namespace gaseous_server.Classes
if (UpdateId == 0)
{
romId = (long)romInsert.Rows[0][0];
} else
}
else
{
romId = UpdateId;
}
@@ -362,73 +368,73 @@ namespace gaseous_server.Classes
return 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)
{
public static string ComputeROMPath(long 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))
{
string DestinationPath = ComputeROMPath(RomId);
if (romPath == DestinationPath)
{
Logging.Log(Logging.LogType.Debug, "Move Game ROM", "Destination path is the same as the current path - aborting");
if (romPath == DestinationPath)
{
Logging.Log(Logging.LogType.Debug, "Move Game ROM", "Destination path is the same as the current path - aborting");
return true;
}
else
{
Logging.Log(Logging.LogType.Information, "Move Game ROM", "Moving " + romPath + " to " + DestinationPath);
if (File.Exists(DestinationPath))
{
Logging.Log(Logging.LogType.Information, "Move Game ROM", "A file with the same name exists at the destination - aborting");
}
else
{
Logging.Log(Logging.LogType.Information, "Move Game ROM", "Moving " + romPath + " to " + DestinationPath);
if (File.Exists(DestinationPath))
{
Logging.Log(Logging.LogType.Information, "Move Game ROM", "A file with the same name exists at the destination - aborting");
return false;
}
else
{
File.Move(romPath, DestinationPath);
}
else
{
File.Move(romPath, DestinationPath);
// 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);
// 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
{
@@ -437,8 +443,8 @@ namespace gaseous_server.Classes
}
}
public void OrganiseLibrary()
{
public void OrganiseLibrary()
{
Logging.Log(Logging.LogType.Information, "Organise Library", "Starting default library organisation");
GameLibrary.LibraryItem library = GameLibrary.GetDefaultLibrary;
@@ -451,17 +457,19 @@ namespace gaseous_server.Classes
DataTable romDT = db.ExecuteCMD(sql, dbDict);
if (romDT.Rows.Count > 0)
{
foreach (DataRow dr in romDT.Rows)
{
Logging.Log(Logging.LogType.Information, "Organise Library", "Processing ROM " + dr["name"]);
long RomId = (long)dr["id"];
MoveGameFile(RomId);
}
}
{
for (int i = 0; i < romDT.Rows.Count; i++)
{
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"]);
long RomId = (long)romDT.Rows[i]["id"];
MoveGameFile(RomId);
}
}
ClearStatus();
// clean up empty directories
DeleteOrphanedDirectories(GameLibrary.GetDefaultLibrary.Path);
// clean up empty directories
DeleteOrphanedDirectories(GameLibrary.GetDefaultLibrary.Path);
Logging.Log(Logging.LogType.Information, "Organise Library", "Finsihed default library organisation");
}
@@ -474,7 +482,7 @@ namespace gaseous_server.Classes
string[] files = Directory.GetFiles(directory);
string[] directories = Directory.GetDirectories(directory);
if (files.Length == 0 &&
directories.Length == 0)
{
@@ -561,7 +569,7 @@ namespace gaseous_server.Classes
public void LibrarySpecificScan(GameLibrary.LibraryItem library)
{
Logging.Log(Logging.LogType.Information, "Library Scan", "Starting scan of library: " + library.Name);
Database db = new Database(Database.databaseType.MySql, Config.DatabaseConfiguration.ConnectionString);
@@ -630,7 +638,7 @@ namespace gaseous_server.Classes
{
// file is not in database - process it
Logging.Log(Logging.LogType.Information, "Library Scan", "Orphaned file found in library: " + LibraryFile);
Common.hashObject hash = new Common.hashObject(LibraryFile);
FileInfo fi = new FileInfo(LibraryFile);
@@ -642,8 +650,8 @@ namespace gaseous_server.Classes
// get discovered platform
long PlatformId;
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
PlatformId = library.DefaultPlatformId;
@@ -768,8 +776,8 @@ namespace gaseous_server.Classes
// get discovered platform
long PlatformId;
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
PlatformId = library.DefaultPlatformId;

View File

@@ -5,17 +5,18 @@ using Microsoft.VisualStudio.Web.CodeGeneration;
namespace gaseous_server.Classes
{
public class Maintenance : QueueItemStatus
public class Maintenance : QueueItemStatus
{
const int MaxFileAge = 30;
public void RunMaintenance()
public void RunDailyMaintenance()
{
Database db = new Database(Database.databaseType.MySql, Config.DatabaseConfiguration.ConnectionString);
string sql = "";
Dictionary<string, object> dbDict = new Dictionary<string, object>();
// 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 = "";
foreach (GameLibrary.LibraryItem library in GameLibrary.GetLibraries)
{
@@ -33,9 +34,27 @@ namespace gaseous_server.Classes
}
// delete old logs
sql = "DELETE FROM ServerLogs WHERE EventTime < @EventRententionDate;";
dbDict.Add("EventRententionDate", DateTime.UtcNow.AddDays(Config.LoggingConfiguration.LogRetention * -1));
db.ExecuteCMD(sql, dbDict);
Logging.Log(Logging.LogType.Information, "Maintenance", "Removing logs older than " + Config.LoggingConfiguration.LogRetention + " days");
long deletedCount = 1;
long deletedEventCount = 0;
long maxLoops = 1000;
while (deletedCount > 0)
{
sql = "DELETE FROM ServerLogs WHERE EventTime < @EventRetentionDate LIMIT 1000; SELECT ROW_COUNT() AS Count;";
dbDict.Add("EventRetentionDate", DateTime.UtcNow.AddDays(Config.LoggingConfiguration.LogRetention * -1));
DataTable deletedCountTable = db.ExecuteCMD(sql, dbDict);
deletedCount = (long)deletedCountTable.Rows[0][0];
deletedEventCount += deletedCount;
// check if we've hit the limit
maxLoops -= 1;
if (maxLoops <= 0)
{
Logging.Log(Logging.LogType.Warning, "Maintenance", "Hit the maximum number of loops for deleting logs. Stopping.");
break;
}
}
Logging.Log(Logging.LogType.Information, "Maintenance", "Deleted " + deletedEventCount + " log entries");
// delete files and directories older than 7 days in PathsToClean
List<string> PathsToClean = new List<string>();
@@ -69,6 +88,13 @@ namespace gaseous_server.Classes
}
}
}
}
public void RunWeeklyMaintenance()
{
Database db = new Database(Database.databaseType.MySql, Config.DatabaseConfiguration.ConnectionString);
string sql = "";
Dictionary<string, object> dbDict = new Dictionary<string, object>();
Logging.Log(Logging.LogType.Information, "Maintenance", "Optimising database tables");
sql = "SHOW FULL TABLES WHERE Table_Type = 'BASE TABLE';";
@@ -80,7 +106,7 @@ namespace gaseous_server.Classes
SetStatus(StatusCounter, tables.Rows.Count, "Optimising 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)
{
string retVal = "";

View File

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

View File

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

View File

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

View File

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

View File

@@ -5,9 +5,9 @@ using IGDB.Models;
namespace gaseous_server.Classes.Metadata
{
public class Games
{
const string fieldList = "fields age_ratings,aggregated_rating,aggregated_rating_count,alternative_names,artworks,bundles,category,checksum,collection,cover,created_at,dlcs,expanded_games,expansions,external_games,first_release_date,follows,forks,franchise,franchises,game_engines,game_localizations,game_modes,genres,hypes,involved_companies,keywords,language_supports,multiplayer_modes,name,parent_game,platforms,player_perspectives,ports,rating,rating_count,release_dates,remakes,remasters,screenshots,similar_games,slug,standalone_expansions,status,storyline,summary,tags,themes,total_rating,total_rating_count,updated_at,url,version_parent,version_title,videos,websites;";
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;";
public Games()
{
@@ -15,9 +15,9 @@ namespace gaseous_server.Classes.Metadata
}
public class InvalidGameId : Exception
{
{
public InvalidGameId(long Id) : base("Unable to find Game by id " + Id)
{}
{ }
}
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; }
}
// set up where clause
string WhereClause = "";
string searchField = "";
switch (searchUsing)
{
case SearchUsing.id:
WhereClause = "where id = " + searchValue;
searchField = "id";
break;
case SearchUsing.slug:
WhereClause = "where slug = " + searchValue;
WhereClause = "where slug = \"" + searchValue + "\"";
searchField = "slug";
break;
default:
throw new Exception("Invalid search type");
@@ -110,11 +112,11 @@ namespace gaseous_server.Classes.Metadata
catch (Exception 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;
case Storage.CacheStatus.Current:
returnValue = Storage.GetCacheValue<Game>(returnValue, "id", (long)searchValue);
returnValue = Storage.GetCacheValue<Game>(returnValue, searchField, searchValue);
UpdateSubClasses(returnValue, false, false, false);
return returnValue;
default:
@@ -125,17 +127,17 @@ namespace gaseous_server.Classes.Metadata
private static void UpdateSubClasses(Game Game, bool getAllMetadata, bool followSubGames, bool forceRefresh)
{
// required metadata
if (Game.Cover != null)
{
try
{
Cover GameCover = Covers.GetCover(Game.Cover.Id, Config.LibraryConfiguration.LibraryMetadataDirectory_Game(Game), forceRefresh);
}
catch (Exception ex)
{
Logging.Log(Logging.LogType.Critical, "Game Metadata", "Unable to fetch cover artwork.", ex);
}
}
// if (Game.Cover != null)
// {
// try
// {
// Cover GameCover = Covers.GetCover(Game.Cover.Id, Config.LibraryConfiguration.LibraryMetadataDirectory_Game(Game), forceRefresh);
// }
// catch (Exception ex)
// {
// Logging.Log(Logging.LogType.Critical, "Game Metadata", "Unable to fetch cover artwork.", ex);
// }
// }
if (Game.Genres != null)
{
@@ -285,7 +287,7 @@ namespace gaseous_server.Classes.Metadata
{
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)
{
@@ -347,7 +349,7 @@ namespace gaseous_server.Classes.Metadata
// get missing metadata from parent if this is a port
if (result.Category == Category.Port)
{
{
if (result.Summary == null)
{
if (result.ParentGame != null)
@@ -364,7 +366,7 @@ namespace gaseous_server.Classes.Metadata
return result;
}
public static void AssignAllGamesToPlatformIdZero()
{
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 + ";";
// get Game metadata
Game[]? results = new Game[0];
@@ -439,7 +441,8 @@ namespace gaseous_server.Classes.Metadata
DataTable data = db.ExecuteCMD(sql, dbDict);
foreach (DataRow row in data.Rows)
{
Game game = new Game{
Game game = new Game
{
Id = (long)row["Id"],
Name = (string)Common.ReturnValueIfNull(row["Name"], ""),
Slug = (string)Common.ReturnValueIfNull(row["Slug"], ""),
@@ -476,12 +479,12 @@ namespace gaseous_server.Classes.Metadata
searchBody = "where platforms = (" + PlatformId + ") & name ~ \"" + SearchString + "\";";
break;
}
// check search cache
Game[]? games = Communications.GetSearchCache<Game[]?>(searchFields, searchBody);
if (games == null)
{
{
// cache miss
// get Game metadata
Communications comms = new Communications();
@@ -513,7 +516,7 @@ namespace gaseous_server.Classes.Metadata
foreach (DataRow row in data.Rows)
{
KeyValuePair<long, string> valuePair = new KeyValuePair<long, string>((long)row["PlatformId"], (string)row["Name"]);
platforms.Add(valuePair);
platforms.Add(valuePair);
}
return platforms;
@@ -529,10 +532,16 @@ namespace gaseous_server.Classes.Metadata
public class MinimalGameItem
{
public MinimalGameItem()
{
}
public MinimalGameItem(Game gameObject)
{
this.Id = gameObject.Id;
this.Name = gameObject.Name;
this.Slug = gameObject.Slug;
this.TotalRating = gameObject.TotalRating;
this.TotalRatingCount = gameObject.TotalRatingCount;
this.Cover = gameObject.Cover;
@@ -555,9 +564,13 @@ namespace gaseous_server.Classes.Metadata
}
public long? Id { get; set; }
public long Index { get; set; }
public string Name { get; set; }
public string Slug { get; set; }
public double? TotalRating { get; set; }
public int? TotalRatingCount { get; set; }
public bool HasSavedGame { get; set; } = false;
public bool IsFavourite { get; set; } = false;
public DateTimeOffset? FirstReleaseDate { get; set; }
public IGDB.IdentityOrValue<IGDB.Models.Cover> Cover { get; set; }
public IGDB.IdentitiesOrValues<IGDB.Models.Artwork> Artworks { get; set; }

View File

@@ -5,15 +5,15 @@ using IGDB.Models;
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;";
public PlatformVersions()
{
}
{
}
public static PlatformVersion? GetPlatformVersion(long Id, Platform ParentPlatform)
public static PlatformVersion? GetPlatformVersion(long Id, Platform ParentPlatform, bool GetImages = false)
{
if (Id == 0)
{
@@ -21,18 +21,18 @@ namespace gaseous_server.Classes.Metadata
}
else
{
Task<PlatformVersion> RetVal = _GetPlatformVersion(SearchUsing.id, Id, ParentPlatform);
Task<PlatformVersion> RetVal = _GetPlatformVersion(SearchUsing.id, Id, ParentPlatform, GetImages);
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;
}
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
Storage.CacheStatus? cacheStatus = new Storage.CacheStatus();
@@ -67,7 +67,7 @@ namespace gaseous_server.Classes.Metadata
if (returnValue != null)
{
Storage.NewCacheValue(returnValue);
UpdateSubClasses(ParentPlatform, returnValue);
UpdateSubClasses(ParentPlatform, returnValue, GetImages);
}
return returnValue;
case Storage.CacheStatus.Expired:
@@ -75,7 +75,7 @@ namespace gaseous_server.Classes.Metadata
{
returnValue = await GetObjectFromServer(WhereClause);
Storage.NewCacheValue(returnValue, true);
UpdateSubClasses(ParentPlatform, returnValue);
UpdateSubClasses(ParentPlatform, returnValue, GetImages);
}
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));
}
catch (Exception ex)
{
Logging.Log(Logging.LogType.Warning, "Platform Update", "Unable to fetch platform logo", ex);
try
{
PlatformLogo platformLogo = PlatformLogos.GetPlatformLogo(platformVersion.PlatformLogo.Id, Path.Combine(Config.LibraryConfiguration.LibraryMetadataDirectory_Platform(ParentPlatform), "Versions", platformVersion.Slug));
}
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
{
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;";
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)
{
Platform returnValue = new Platform();
@@ -41,10 +41,10 @@ namespace gaseous_server.Classes.Metadata
{
try
{
Task<Platform> RetVal = _GetPlatform(SearchUsing.id, Id, forceRefresh);
Task<Platform> RetVal = _GetPlatform(SearchUsing.id, Id, forceRefresh, GetImages);
return RetVal.Result;
}
catch(Exception ex)
catch (Exception ex)
{
Logging.Log(Logging.LogType.Warning, "Metadata", "An error occurred fetching Platform Id " + Id, ex);
return null;
@@ -52,14 +52,14 @@ namespace gaseous_server.Classes.Metadata
}
}
public static Platform GetPlatform(string Slug, bool forceRefresh = false)
{
Task<Platform> RetVal = _GetPlatform(SearchUsing.slug, Slug, forceRefresh);
return RetVal.Result;
}
public static Platform GetPlatform(string Slug, bool forceRefresh = false, bool GetImages = false)
{
Task<Platform> RetVal = _GetPlatform(SearchUsing.slug, Slug, forceRefresh, GetImages);
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
Storage.CacheStatus? cacheStatus = new Storage.CacheStatus();
if (searchUsing == SearchUsing.id)
@@ -78,13 +78,16 @@ namespace gaseous_server.Classes.Metadata
// set up where clause
string WhereClause = "";
string searchField = "";
switch (searchUsing)
{
case SearchUsing.id:
WhereClause = "where id = " + searchValue;
searchField = "id";
break;
case SearchUsing.slug:
WhereClause = "where slug = " + searchValue;
WhereClause = "where slug = \"" + searchValue + "\"";
searchField = "slug";
break;
default:
throw new Exception("Invalid search type");
@@ -96,7 +99,7 @@ namespace gaseous_server.Classes.Metadata
case Storage.CacheStatus.NotPresent:
returnValue = await GetObjectFromServer(WhereClause);
Storage.NewCacheValue(returnValue);
UpdateSubClasses(returnValue);
UpdateSubClasses(returnValue, GetImages);
AddPlatformMapping(returnValue);
return returnValue;
case Storage.CacheStatus.Expired:
@@ -104,23 +107,23 @@ namespace gaseous_server.Classes.Metadata
{
returnValue = await GetObjectFromServer(WhereClause);
Storage.NewCacheValue(returnValue, true);
UpdateSubClasses(returnValue);
UpdateSubClasses(returnValue, GetImages);
AddPlatformMapping(returnValue);
return returnValue;
}
catch (Exception 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:
return Storage.GetCacheValue<Platform>(returnValue, "id", (long)searchValue);
return Storage.GetCacheValue<Platform>(returnValue, searchField, searchValue);
default:
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)
{
@@ -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));
}
catch (Exception ex)
{
Logging.Log(Logging.LogType.Warning, "Platform Update", "Unable to fetch platform logo", ex);
try
{
PlatformLogo platformLogo = PlatformLogos.GetPlatformLogo(platform.PlatformLogo.Id, Config.LibraryConfiguration.LibraryMetadataDirectory_Platform(platform));
}
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.");
// doesn't exist - add it
item = new Models.PlatformMapping.PlatformMapItem{
item = new Models.PlatformMapping.PlatformMapItem
{
IGDBId = (long)platform.Id,
IGDBName = platform.Name,
IGDBSlug = platform.Slug,
AlternateNames = new List<string>{ platform.AlternativeName }
AlternateNames = new List<string> { platform.AlternativeName }
};
Models.PlatformMapping.WritePlatformMap(item, false, true);
}

View File

@@ -7,19 +7,19 @@ using Microsoft.Extensions.Caching.Memory;
namespace gaseous_server.Classes.Metadata
{
public class Storage
{
public enum CacheStatus
{
NotPresent,
Current,
Expired
}
public class Storage
{
public enum CacheStatus
{
NotPresent,
Current,
Expired
}
public static CacheStatus GetCacheStatus(string Endpoint, string Slug)
{
public static CacheStatus GetCacheStatus(string Endpoint, string Slug)
{
return _GetCacheStatus(Endpoint, "slug", Slug);
}
}
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)
{
{
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>();
dbDict.Add("Endpoint", Endpoint);
dbDict.Add(SearchField, SearchValue);
DataTable dt = db.ExecuteCMD(sql, dbDict);
if (dt.Rows.Count == 0)
{
// no data stored for this item, or lastUpdated
return CacheStatus.NotPresent;
}
else
{
DateTime CacheExpiryTime = DateTime.UtcNow.AddHours(-168);
if ((DateTime)dt.Rows[0]["lastUpdated"] < CacheExpiryTime)
{
return CacheStatus.Expired;
}
else
{
return CacheStatus.Current;
}
}
Dictionary<string, object> dbDict = new Dictionary<string, object>();
dbDict.Add("Endpoint", Endpoint);
dbDict.Add(SearchField, SearchValue);
DataTable dt = db.ExecuteCMD(sql, dbDict);
if (dt.Rows.Count == 0)
{
// no data stored for this item, or lastUpdated
return CacheStatus.NotPresent;
}
else
{
DateTime CacheExpiryTime = DateTime.UtcNow.AddHours(-168);
if ((DateTime)dt.Rows[0]["lastUpdated"] < CacheExpiryTime)
{
return CacheStatus.Expired;
}
else
{
return CacheStatus.Current;
}
}
}
public static void NewCacheValue(object ObjectToCache, bool UpdateRecord = false)
{
// get the object type name
string ObjectTypeName = ObjectToCache.GetType().Name;
public static void NewCacheValue(object ObjectToCache, bool UpdateRecord = false)
{
// get the object type name
string ObjectTypeName = ObjectToCache.GetType().Name;
// build dictionary
string objectJson = Newtonsoft.Json.JsonConvert.SerializeObject(ObjectToCache);
Dictionary<string, object?> objectDict = Newtonsoft.Json.JsonConvert.DeserializeObject<Dictionary<string, object?>>(objectJson);
// build dictionary
string objectJson = Newtonsoft.Json.JsonConvert.SerializeObject(ObjectToCache);
Dictionary<string, object?> objectDict = Newtonsoft.Json.JsonConvert.DeserializeObject<Dictionary<string, object?>>(objectJson);
objectDict.Add("dateAdded", DateTime.UtcNow);
objectDict.Add("lastUpdated", DateTime.UtcNow);
// generate sql
string fieldList = "";
string valueList = "";
string updateFieldValueList = "";
foreach (KeyValuePair<string, object?> key in objectDict)
{
if (fieldList.Length > 0)
{
fieldList = fieldList + ", ";
valueList = valueList + ", ";
}
fieldList = fieldList + key.Key;
valueList = valueList + "@" + key.Key;
if ((key.Key != "id") && (key.Key != "dateAdded"))
// generate sql
string fieldList = "";
string valueList = "";
string updateFieldValueList = "";
foreach (KeyValuePair<string, object?> key in objectDict)
{
if (fieldList.Length > 0)
{
fieldList = fieldList + ", ";
valueList = valueList + ", ";
}
fieldList = fieldList + key.Key;
valueList = valueList + "@" + key.Key;
if ((key.Key != "id") && (key.Key != "dateAdded"))
{
if (updateFieldValueList.Length > 0)
{
updateFieldValueList = updateFieldValueList + ", ";
}
updateFieldValueList += key.Key + " = @" + key.Key;
}
}
// check property type
Type objectType = ObjectToCache.GetType();
if (objectType != null)
{
PropertyInfo objectProperty = objectType.GetProperty(key.Key);
if (objectProperty != null)
{
string compareName = objectProperty.PropertyType.Name.ToLower().Split("`")[0];
var objectValue = objectProperty.GetValue(ObjectToCache);
if (objectValue != null)
{
string newObjectValue;
Dictionary<string, object> newDict;
// check property type
Type objectType = ObjectToCache.GetType();
if (objectType != null)
{
PropertyInfo objectProperty = objectType.GetProperty(key.Key);
if (objectProperty != null)
{
string compareName = objectProperty.PropertyType.Name.ToLower().Split("`")[0];
var objectValue = objectProperty.GetValue(ObjectToCache);
if (objectValue != null)
{
string newObjectValue;
Dictionary<string, object> newDict;
switch (compareName)
{
case "identityorvalue":
{
case "identityorvalue":
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"];
break;
case "identitiesorvalues":
newObjectValue = Newtonsoft.Json.JsonConvert.SerializeObject(objectValue);
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;
StoreRelations(ObjectTypeName, key.Key, (long)objectDict["Id"], newObjectValue);
break;
case "int32[]":
case "int32[]":
newObjectValue = Newtonsoft.Json.JsonConvert.SerializeObject(objectValue);
objectDict[key.Key] = newObjectValue;
break;
}
}
}
}
}
string sql = "";
if (UpdateRecord == false)
{
sql = "INSERT INTO " + ObjectTypeName + " (" + fieldList + ") VALUES (" + valueList + ")";
}
}
}
}
string sql = "";
if (UpdateRecord == false)
{
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
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;
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);
if (dt.Rows.Count == 0)
{
// no data stored for this item
throw new Exception("No record found that matches endpoint " + Endpoint + " with search value " + SearchValue);
// no data stored for this item
throw new Exception("No record found that matches endpoint " + Endpoint + " with search value " + SearchValue);
}
else
{
DataRow dataRow = dt.Rows[0];
DataRow dataRow = dt.Rows[0];
object returnObject = BuildCacheObject<T>(EndpointType, dataRow);
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())
{
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
{
public object Object { get; set; }
public DateTime CreationTime { get; } = DateTime.UtcNow;
public DateTime ExpiryTime
public DateTime ExpiryTime
{
get
{

View File

@@ -5,6 +5,9 @@ using Microsoft.VisualBasic;
using IGDB.Models;
using gaseous_server.Classes.Metadata;
using System.IO.Compression;
using SharpCompress.Archives;
using SharpCompress.Common;
using gaseous_server.Models;
namespace gaseous_server.Classes
{
@@ -55,12 +58,15 @@ namespace gaseous_server.Classes
return GetMediaGroup(mgId);
}
public static GameRomMediaGroupItem GetMediaGroup(long Id)
public static GameRomMediaGroupItem GetMediaGroup(long Id, string userid = "")
{
Database db = new Database(Database.databaseType.MySql, Config.DatabaseConfiguration.ConnectionString);
string sql = "SELECT * FROM RomMediaGroup WHERE Id=@id;";
Dictionary<string, object> dbDict = new Dictionary<string, object>();
dbDict.Add("id", Id);
string sql = "SELECT DISTINCT RomMediaGroup.*, GameState.RomId AS GameStateRomId FROM gaseous.RomMediaGroup LEFT JOIN GameState ON RomMediaGroup.Id = GameState.RomId AND GameState.IsMediaGroup = 1 AND GameState.UserId = @userid WHERE RomMediaGroup.Id=@id;";
Dictionary<string, object> dbDict = new Dictionary<string, object>
{
{ "id", Id },
{ "userid", userid }
};
DataTable dataTable = db.ExecuteCMD(sql, dbDict);
@@ -75,12 +81,15 @@ namespace gaseous_server.Classes
}
}
public static List<GameRomMediaGroupItem> GetMediaGroupsFromGameId(long GameId)
public static List<GameRomMediaGroupItem> GetMediaGroupsFromGameId(long GameId, string userid = "")
{
Database db = new Database(Database.databaseType.MySql, Config.DatabaseConfiguration.ConnectionString);
string sql = "SELECT * FROM RomMediaGroup WHERE GameId=@gameid;";
Dictionary<string, object> dbDict = new Dictionary<string, object>();
dbDict.Add("gameid", GameId);
string sql = "SELECT DISTINCT RomMediaGroup.*, GameState.RomId AS GameStateRomId FROM gaseous.RomMediaGroup LEFT JOIN GameState ON RomMediaGroup.Id = GameState.RomId AND GameState.IsMediaGroup = 1 AND GameState.UserId = @userid WHERE RomMediaGroup.GameId=@gameid;";
Dictionary<string, object> dbDict = new Dictionary<string, object>
{
{ "gameid", GameId },
{ "userid", userid }
};
DataTable dataTable = db.ExecuteCMD(sql, dbDict);
@@ -156,7 +165,7 @@ namespace gaseous_server.Classes
public static void DeleteMediaGroup(long Id)
{
Database db = new Database(Database.databaseType.MySql, Config.DatabaseConfiguration.ConnectionString);
string sql = "DELETE FROM RomMediaGroup WHERE Id=@id;";
string sql = "DELETE FROM RomMediaGroup WHERE Id=@id; DELETE FROM GameState WHERE RomId=@id AND IsMediaGroup=1;";
Dictionary<string, object> dbDict = new Dictionary<string, object>();
dbDict.Add("id", Id);
db.ExecuteCMD(sql, dbDict);
@@ -170,6 +179,15 @@ namespace gaseous_server.Classes
internal static GameRomMediaGroupItem BuildMediaGroupFromRow(DataRow row)
{
bool hasSaveStates = false;
if (row.Table.Columns.Contains("GameStateRomId"))
{
if (row["GameStateRomId"] != DBNull.Value)
{
hasSaveStates = true;
}
}
GameRomMediaGroupItem mediaGroupItem = new GameRomMediaGroupItem();
mediaGroupItem.Id = (long)row["Id"];
mediaGroupItem.Status = (GameRomMediaGroupItem.GroupBuildStatus)row["Status"];
@@ -177,6 +195,7 @@ namespace gaseous_server.Classes
mediaGroupItem.GameId = (long)row["GameId"];
mediaGroupItem.RomIds = new List<long>();
mediaGroupItem.Roms = new List<Roms.GameRomItem>();
mediaGroupItem.HasSaveStates = hasSaveStates;
// get members
Database db = new Database(Database.databaseType.MySql, Config.DatabaseConfiguration.ConnectionString);
@@ -243,6 +262,7 @@ namespace gaseous_server.Classes
{
Game GameObject = Games.GetGame(mediaGroupItem.GameId, false, false, 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);
@@ -277,10 +297,124 @@ namespace gaseous_server.Classes
foreach (long RomId in mediaGroupItem.RomIds)
{
Roms.GameRomItem rom = Roms.GetRom(RomId);
bool fileNameFound = false;
if (File.Exists(rom.Path))
{
Logging.Log(Logging.LogType.Information, "Media Group", "Copying ROM: " + rom.Name);
File.Copy(rom.Path, Path.Combine(ZipFileTempPath, Path.GetFileName(rom.Path)));
string romExt = Path.GetExtension(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);
}
@@ -397,6 +531,7 @@ namespace gaseous_server.Classes
public Models.PlatformMapping.PlatformMapItem.WebEmulatorItem? Emulator { get; set; }
public List<long> RomIds { get; set; }
public List<Roms.GameRomItem> Roms { get; set; }
public bool HasSaveStates { get; set; } = false;
private GroupBuildStatus _Status { get; set; }
public GroupBuildStatus Status {
get

View File

@@ -10,22 +10,29 @@ namespace gaseous_server.Classes
public class Roms
{
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 static GameRomObject GetRoms(long GameId, long PlatformId = -1, string NameSearch = "", int pageNumber = 0, int pageSize = 0)
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 = "")
{
GameRomObject GameRoms = new GameRomObject();
Database db = new Database(Database.databaseType.MySql, Config.DatabaseConfiguration.ConnectionString);
string sql = "";
Database db = new Database(Database.databaseType.MySql, Config.DatabaseConfiguration.ConnectionString);
string sql = "";
string sqlCount = "";
string sqlPlatform = "";
Dictionary<string, object> dbDict = new Dictionary<string, object>();
dbDict.Add("id", GameId);
dbDict.Add("id", GameId);
dbDict.Add("userid", userid);
string NameSearchWhere = "";
if (NameSearch.Length > 0)
{
@@ -36,27 +43,30 @@ namespace gaseous_server.Classes
// 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`;";
if (PlatformId == -1) {
if (PlatformId == -1)
{
// data query
sql = "SELECT Games_Roms.*, Platform.`Name` AS platformname FROM Games_Roms LEFT JOIN Platform ON Games_Roms.PlatformId = Platform.Id 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
sqlCount = "SELECT COUNT(Games_Roms.Id) AS RomCount FROM Games_Roms WHERE Games_Roms.GameId = @id" + NameSearchWhere + ";";
} else {
}
else
{
// data query
sql = "SELECT Games_Roms.*, Platform.`Name` AS platformname FROM Games_Roms LEFT JOIN Platform ON Games_Roms.PlatformId = Platform.Id 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;";
// count query
sqlCount = "SELECT COUNT(Games_Roms.Id) AS RomCount FROM Games_Roms WHERE Games_Roms.GameId = @id AND Games_Roms.PlatformId = @platformid" + NameSearchWhere + ";";
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];
DataTable platformDT = db.ExecuteCMD(sqlPlatform, dbDict);
if (romDT.Rows.Count > 0)
{
if (romDT.Rows.Count > 0)
{
// set count of roms
GameRoms.Count = int.Parse((string)rowCount["RomCount"]);
@@ -72,12 +82,12 @@ namespace gaseous_server.Classes
}
return GameRoms;
}
else
{
throw new Games.InvalidGameId(GameId);
}
}
}
else
{
throw new Games.InvalidGameId(GameId);
}
}
public static GameRomItem GetRom(long RomId)
{
@@ -99,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)
{
// ensure metadata for platformid is present
@@ -107,10 +137,10 @@ namespace gaseous_server.Classes
// ensure metadata for gameid is present
IGDB.Models.Game game = Classes.Metadata.Games.GetGame(GameId, false, false, false);
Database db = new Database(Database.databaseType.MySql, Config.DatabaseConfiguration.ConnectionString);
string sql = "UPDATE Games_Roms SET PlatformId=@platformid, GameId=@gameid WHERE Id = @id";
Dictionary<string, object> dbDict = new Dictionary<string, object>();
dbDict.Add("id", RomId);
Database db = new Database(Database.databaseType.MySql, Config.DatabaseConfiguration.ConnectionString);
string sql = "UPDATE Games_Roms SET PlatformId=@platformid, GameId=@gameid WHERE Id = @id";
Dictionary<string, object> dbDict = new Dictionary<string, object>();
dbDict.Add("id", RomId);
dbDict.Add("platformid", PlatformId);
dbDict.Add("gameid", GameId);
db.ExecuteCMD(sql, dbDict);
@@ -118,7 +148,7 @@ namespace gaseous_server.Classes
GameRomItem rom = GetRom(RomId);
return rom;
}
}
public static void DeleteRom(long RomId)
{
@@ -131,36 +161,46 @@ namespace gaseous_server.Classes
}
Database db = new Database(Database.databaseType.MySql, Config.DatabaseConfiguration.ConnectionString);
string sql = "DELETE FROM Games_Roms WHERE Id = @id";
string sql = "DELETE FROM Games_Roms WHERE Id = @id; DELETE FROM GameState WHERE RomId = @id;";
Dictionary<string, object> dbDict = new Dictionary<string, object>();
dbDict.Add("id", RomId);
db.ExecuteCMD(sql, dbDict);
}
}
}
private static GameRomItem BuildRom(DataRow romDR)
{
bool hasSaveStates = false;
if (romDR.Table.Columns.Contains("SavedStateRomId"))
{
if (romDR["SavedStateRomId"] != DBNull.Value)
{
hasSaveStates = true;
}
}
GameRomItem romItem = new GameRomItem
{
Id = (long)romDR["id"],
PlatformId = (long)romDR["platformid"],
{
Id = (long)romDR["id"],
PlatformId = (long)romDR["platformid"],
Platform = (string)romDR["platformname"],
GameId = (long)romDR["gameid"],
Name = (string)romDR["name"],
Size = (long)romDR["size"],
Crc = ((string)romDR["crc"]).ToLower(),
Md5 = ((string)romDR["md5"]).ToLower(),
Sha1 = ((string)romDR["sha1"]).ToLower(),
DevelopmentStatus = (string)romDR["developmentstatus"],
Attributes = Newtonsoft.Json.JsonConvert.DeserializeObject<List<KeyValuePair<string, object>>>((string)Common.ReturnValueIfNull(romDR["attributes"], "[ ]")),
RomType = (HasheousClient.Models.LookupResponseModel.RomItem.RomTypes)(int)romDR["romtype"],
RomTypeMedia = (string)romDR["romtypemedia"],
MediaLabel = (string)romDR["medialabel"],
Path = (string)romDR["path"],
GameId = (long)romDR["gameid"],
Name = (string)romDR["name"],
Size = (long)romDR["size"],
Crc = ((string)romDR["crc"]).ToLower(),
Md5 = ((string)romDR["md5"]).ToLower(),
Sha1 = ((string)romDR["sha1"]).ToLower(),
DevelopmentStatus = (string)romDR["developmentstatus"],
Attributes = Newtonsoft.Json.JsonConvert.DeserializeObject<List<KeyValuePair<string, object>>>((string)Common.ReturnValueIfNull(romDR["attributes"], "[ ]")),
RomType = (HasheousClient.Models.LookupResponseModel.RomItem.RomTypes)(int)romDR["romtype"],
RomTypeMedia = (string)romDR["romtypemedia"],
MediaLabel = (string)romDR["medialabel"],
Path = (string)romDR["path"],
SignatureSource = (gaseous_server.Models.Signatures_Games.RomItem.SignatureSourceType)(Int32)romDR["metadatasource"],
SignatureSourceGameTitle = (string)Common.ReturnValueIfNull(romDR["MetadataGameName"], ""),
HasSaveStates = hasSaveStates,
Library = GameLibrary.GetLibrary((int)romDR["LibraryId"])
};
};
// check for a web emulator and update the romItem
foreach (Models.PlatformMapping.PlatformMapItem platformMapping in Models.PlatformMapping.PlatformMap)
@@ -174,8 +214,8 @@ namespace gaseous_server.Classes
}
}
return romItem;
}
return romItem;
}
public class GameRomObject
{
@@ -187,12 +227,12 @@ namespace gaseous_server.Classes
{
public long PlatformId { get; set; }
public string Platform { get; set; }
public Models.PlatformMapping.PlatformMapItem.WebEmulatorItem? Emulator { get; set; }
public long GameId { get; set; }
public Models.PlatformMapping.PlatformMapItem.WebEmulatorItem? Emulator { get; set; }
public long GameId { get; set; }
public string? Path { get; set; }
public string? SignatureSourceGameTitle { get; set;}
public string? SignatureSourceGameTitle { get; set; }
public bool HasSaveStates { get; set; } = false;
public GameLibrary.LibraryItem Library { get; set; }
}
}
}
}
}
}

View File

@@ -104,7 +104,7 @@ namespace gaseous_server.SignatureIngestors.XML
// set up game dictionary
dbDict = new Dictionary<string, object>();
if (flipNameAndDescription.Contains(Object.SourceType))
if (flipNameAndDescription.Contains(Object.SourceType))
{
dbDict.Add("name", Common.ReturnValueIfNull(gameObject.Description, ""));
dbDict.Add("description", Common.ReturnValueIfNull(gameObject.Name, ""));

View File

@@ -10,7 +10,8 @@ namespace gaseous_server.Classes
if (md5.Length > 0)
{
return _GetSignature("Signatures_Roms.md5 = @searchstring", md5);
} else
}
else
{
return _GetSignature("Signatures_Roms.sha1 = @searchstring", sha1);
}
@@ -21,7 +22,8 @@ namespace gaseous_server.Classes
if (TosecName.Length > 0)
{
return _GetSignature("Signatures_Roms.name = @searchstring", TosecName);
} else
}
else
{
return null;
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -6,15 +6,18 @@ using System.IO;
using System.Linq;
using System.Reflection;
using System.Threading.Tasks;
using Authentication;
using gaseous_server.Classes;
using gaseous_server.Classes.Metadata;
using gaseous_server.Models;
using IGDB.Models;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Identity;
using Microsoft.AspNetCore.Mvc;
using Microsoft.CodeAnalysis.Scripting;
using static gaseous_server.Classes.Metadata.AgeRatings;
using Asp.Versioning;
namespace gaseous_server.Controllers
{
@@ -25,10 +28,22 @@ namespace gaseous_server.Controllers
[ApiController]
public class GamesController : Controller
{
private readonly UserManager<ApplicationUser> _userManager;
private readonly SignInManager<ApplicationUser> _signInManager;
public GamesController(
UserManager<ApplicationUser> userManager,
SignInManager<ApplicationUser> signInManager
)
{
_userManager = userManager;
_signInManager = signInManager;
}
[MapToApiVersion("1.0")]
[HttpGet]
[ProducesResponseType(typeof(List<Game>), StatusCodes.Status200OK)]
public ActionResult Game(
public async Task<ActionResult> Game(
string name = "",
string platform = "",
string genre = "",
@@ -288,7 +303,7 @@ namespace gaseous_server.Controllers
[ProducesResponseType(typeof(Game), StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status404NotFound)]
[ResponseCache(CacheProfileName = "5Minute")]
public ActionResult Game(long GameId)
public async Task<ActionResult> Game(long GameId)
{
try
{
@@ -316,7 +331,7 @@ namespace gaseous_server.Controllers
[ProducesResponseType(typeof(List<AlternativeName>), StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status404NotFound)]
[ResponseCache(CacheProfileName = "7Days")]
public ActionResult GameAlternativeNames(long GameId)
public async Task<ActionResult> GameAlternativeNames(long GameId)
{
try
{
@@ -349,7 +364,7 @@ namespace gaseous_server.Controllers
[ProducesResponseType(typeof(List<AgeRatings.GameAgeRating>), StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status404NotFound)]
[ResponseCache(CacheProfileName = "7Days")]
public ActionResult GameAgeClassification(long GameId)
public async Task<ActionResult> GameAgeClassification(long GameId)
{
try
{
@@ -382,7 +397,7 @@ namespace gaseous_server.Controllers
[ProducesResponseType(typeof(List<Artwork>), StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status404NotFound)]
[ResponseCache(CacheProfileName = "7Days")]
public ActionResult GameArtwork(long GameId)
public async Task<ActionResult> GameArtwork(long GameId)
{
try
{
@@ -413,7 +428,7 @@ namespace gaseous_server.Controllers
[ProducesResponseType(typeof(Artwork), StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status404NotFound)]
[ResponseCache(CacheProfileName = "7Days")]
public ActionResult GameArtwork(long GameId, long ArtworkId)
public async Task<ActionResult> GameArtwork(long GameId, long ArtworkId)
{
try
{
@@ -449,7 +464,7 @@ namespace gaseous_server.Controllers
[Route("{GameId}/artwork/{ArtworkId}/image/{size}/{ImageName}")]
[ProducesResponseType(typeof(FileStreamResult), StatusCodes.Status200OK)]
[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
{
@@ -516,7 +531,7 @@ namespace gaseous_server.Controllers
[ProducesResponseType(typeof(Cover), StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status404NotFound)]
[ResponseCache(CacheProfileName = "7Days")]
public ActionResult GameCover(long GameId)
public async Task<ActionResult> GameCover(long GameId)
{
try
{
@@ -551,7 +566,7 @@ namespace gaseous_server.Controllers
[Route("{GameId}/cover/image/{size}/{imagename}")]
[ProducesResponseType(typeof(FileStreamResult), StatusCodes.Status200OK)]
[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
{
@@ -596,6 +611,81 @@ namespace gaseous_server.Controllers
}
}
[MapToApiVersion("1.0")]
[MapToApiVersion("1.1")]
[HttpGet]
[Route("{GameId}/favourite")]
[ProducesResponseType(typeof(bool), StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status404NotFound)]
public async Task<ActionResult> GameGetFavouriteAsync(long GameId)
{
try
{
IGDB.Models.Game gameObject = Classes.Metadata.Games.GetGame(GameId, false, false, false);
if (gameObject != null)
{
var user = await _userManager.GetUserAsync(User);
if (user != null)
{
Favourites favourites = new Favourites();
return Ok(favourites.GetFavourite(user.Id, GameId));
}
else
{
return NotFound();
}
}
else
{
return NotFound();
}
}
catch
{
return NotFound();
}
}
[MapToApiVersion("1.0")]
[MapToApiVersion("1.1")]
[HttpPost]
[Route("{GameId}/favourite")]
[ProducesResponseType(typeof(bool), StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status404NotFound)]
public async Task<ActionResult> GameSetFavouriteAsync(long GameId, bool favourite)
{
try
{
IGDB.Models.Game gameObject = Classes.Metadata.Games.GetGame(GameId, false, false, false);
if (gameObject != null)
{
var user = await _userManager.GetUserAsync(User);
if (user != null)
{
Favourites favourites = new Favourites();
return Ok(favourites.SetFavourite(user.Id, GameId, favourite));
}
else
{
return NotFound();
}
}
else
{
return NotFound();
}
}
catch
{
return NotFound();
}
}
[MapToApiVersion("1.0")]
[MapToApiVersion("1.1")]
[HttpGet]
@@ -603,7 +693,7 @@ namespace gaseous_server.Controllers
[ProducesResponseType(typeof(List<Genre>), StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status404NotFound)]
[ResponseCache(CacheProfileName = "7Days")]
public ActionResult GameGenre(long GameId)
public async Task<ActionResult> GameGenre(long GameId)
{
try
{
@@ -641,7 +731,7 @@ namespace gaseous_server.Controllers
[ProducesResponseType(typeof(List<Dictionary<string, object>>), StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status404NotFound)]
[ResponseCache(CacheProfileName = "7Days")]
public ActionResult GameInvolvedCompanies(long GameId)
public async Task<ActionResult> GameInvolvedCompanies(long GameId)
{
try
{
@@ -686,7 +776,7 @@ namespace gaseous_server.Controllers
[ProducesResponseType(typeof(Dictionary<string, object>), StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status404NotFound)]
[ResponseCache(CacheProfileName = "7Days")]
public ActionResult GameInvolvedCompanies(long GameId, long CompanyId)
public async Task<ActionResult> GameInvolvedCompanies(long GameId, long CompanyId)
{
try
{
@@ -728,7 +818,7 @@ namespace gaseous_server.Controllers
[Route("{GameId}/companies/{CompanyId}/image")]
[ProducesResponseType(typeof(FileStreamResult), StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status404NotFound)]
public ActionResult GameCompanyImage(long GameId, long CompanyId)
public async Task<ActionResult> GameCompanyImage(long GameId, long CompanyId)
{
try
{
@@ -773,7 +863,7 @@ namespace gaseous_server.Controllers
[Route("{GameId}/platforms")]
[ProducesResponseType(typeof(List<KeyValuePair<long, string>>), StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status404NotFound)]
public ActionResult GamePlatforms(long GameId)
public async Task<ActionResult> GamePlatforms(long GameId)
{
try
{
@@ -792,7 +882,7 @@ namespace gaseous_server.Controllers
[ProducesResponseType(typeof(List<ReleaseDate>), StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status404NotFound)]
[ResponseCache(CacheProfileName = "7Days")]
public ActionResult GameReleaseDates(long GameId)
public async Task<ActionResult> GameReleaseDates(long GameId)
{
try
{
@@ -830,13 +920,15 @@ namespace gaseous_server.Controllers
[ProducesResponseType(typeof(Classes.Roms.GameRomObject), StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status404NotFound)]
//[ResponseCache(CacheProfileName = "5Minute")]
public ActionResult GameRom(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);
try
{
Game gameObject = Classes.Metadata.Games.GetGame(GameId, false, false, false);
return Ok(Classes.Roms.GetRoms(GameId, PlatformId, NameSearch, pageNumber, pageSize));
return Ok(Classes.Roms.GetRoms(GameId, PlatformId, NameSearch, pageNumber, pageSize, user.Id));
}
catch
{
@@ -851,7 +943,7 @@ namespace gaseous_server.Controllers
[ProducesResponseType(typeof(Classes.Roms.GameRomItem), StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status404NotFound)]
//[ResponseCache(CacheProfileName = "5Minute")]
public ActionResult GameRom(long GameId, long RomId)
public async Task<ActionResult> GameRom(long GameId, long RomId)
{
try
{
@@ -880,7 +972,7 @@ namespace gaseous_server.Controllers
[Route("{GameId}/roms/{RomId}")]
[ProducesResponseType(typeof(Classes.Roms.GameRomItem), StatusCodes.Status200OK)]
[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
{
@@ -910,7 +1002,7 @@ namespace gaseous_server.Controllers
[Route("{GameId}/roms/{RomId}")]
[ProducesResponseType(typeof(Classes.Roms.GameRomItem), StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status404NotFound)]
public ActionResult GameRomDelete(long GameId, long RomId)
public async Task<ActionResult> GameRomDelete(long GameId, long RomId)
{
try
{
@@ -942,7 +1034,7 @@ namespace gaseous_server.Controllers
[Route("{GameId}/roms/{RomId}/file")]
[ProducesResponseType(typeof(FileStreamResult), StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status404NotFound)]
public ActionResult GameRomFile(long GameId, long RomId)
public async Task<ActionResult> GameRomFile(long GameId, long RomId)
{
try
{
@@ -981,7 +1073,7 @@ namespace gaseous_server.Controllers
[Route("{GameId}/roms/{RomId}/{FileName}")]
[ProducesResponseType(typeof(FileStreamResult), StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status404NotFound)]
public ActionResult GameRomFile(long GameId, long RomId, string FileName)
public async Task<ActionResult> GameRomFile(long GameId, long RomId, string FileName)
{
try
{
@@ -1018,13 +1110,15 @@ namespace gaseous_server.Controllers
[ProducesResponseType(typeof(Classes.RomMediaGroup.GameRomMediaGroupItem), StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status404NotFound)]
//[ResponseCache(CacheProfileName = "5Minute")]
public ActionResult GameRomGroup(long GameId, long RomGroupId)
public async Task<ActionResult> GameRomGroupAsync(long GameId, long RomGroupId)
{
var user = await _userManager.GetUserAsync(User);
try
{
Game gameObject = Classes.Metadata.Games.GetGame(GameId, false, false, false);
Classes.RomMediaGroup.GameRomMediaGroupItem rom = Classes.RomMediaGroup.GetMediaGroup(RomGroupId);
Classes.RomMediaGroup.GameRomMediaGroupItem rom = Classes.RomMediaGroup.GetMediaGroup(RomGroupId, user.Id);
if (rom.GameId == GameId)
{
return Ok(rom);
@@ -1047,15 +1141,17 @@ namespace gaseous_server.Controllers
[Route("{GameId}/romgroup")]
[ProducesResponseType(typeof(List<RomMediaGroup.GameRomMediaGroupItem>), StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status404NotFound)]
public ActionResult GetGameRomGroup(long GameId)
public async Task<ActionResult> GetGameRomGroupAsync(long GameId)
{
var user = await _userManager.GetUserAsync(User);
try
{
Game gameObject = Classes.Metadata.Games.GetGame(GameId, false, false, false);
try
{
return Ok(RomMediaGroup.GetMediaGroupsFromGameId(GameId));
return Ok(RomMediaGroup.GetMediaGroupsFromGameId(GameId, user.Id));
}
catch (Exception ex)
{
@@ -1076,7 +1172,7 @@ namespace gaseous_server.Controllers
[Route("{GameId}/romgroup")]
[ProducesResponseType(typeof(Classes.RomMediaGroup.GameRomMediaGroupItem), StatusCodes.Status200OK)]
[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
{
@@ -1105,13 +1201,15 @@ namespace gaseous_server.Controllers
[Route("{GameId}/romgroup/{RomId}")]
[ProducesResponseType(typeof(Classes.RomMediaGroup.GameRomMediaGroupItem), StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status404NotFound)]
public ActionResult GameRomGroupMembers(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);
try
{
Game gameObject = Classes.Metadata.Games.GetGame(GameId, false, false, false);
Classes.RomMediaGroup.GameRomMediaGroupItem rom = Classes.RomMediaGroup.GetMediaGroup(RomGroupId);
Classes.RomMediaGroup.GameRomMediaGroupItem rom = Classes.RomMediaGroup.GetMediaGroup(RomGroupId, user.Id);
if (rom.GameId == GameId)
{
rom = Classes.RomMediaGroup.EditMediaGroup(RomGroupId, RomIds);
@@ -1135,7 +1233,7 @@ namespace gaseous_server.Controllers
[Route("{GameId}/romgroup/{RomGroupId}")]
[ProducesResponseType(typeof(Classes.RomMediaGroup.GameRomMediaGroupItem), StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status404NotFound)]
public ActionResult GameRomGroupDelete(long GameId, long RomGroupId)
public async Task<ActionResult> GameRomGroupDelete(long GameId, long RomGroupId)
{
try
{
@@ -1168,7 +1266,7 @@ namespace gaseous_server.Controllers
[Route("{GameId}/romgroup/{RomGroupId}/{filename}")]
[ProducesResponseType(typeof(FileStreamResult), StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status404NotFound)]
public ActionResult GameRomGroupFile(long GameId, long RomGroupId, string filename = "")
public async Task<ActionResult> GameRomGroupFile(long GameId, long RomGroupId, string filename = "")
{
try
{
@@ -1213,7 +1311,7 @@ namespace gaseous_server.Controllers
[Route("search")]
[ProducesResponseType(typeof(List<Game>), StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status404NotFound)]
public ActionResult GameSearch(long RomId = 0, string SearchString = "")
public async Task<ActionResult> GameSearch(long RomId = 0, string SearchString = "")
{
try
{
@@ -1254,7 +1352,7 @@ namespace gaseous_server.Controllers
[ProducesResponseType(typeof(List<Screenshot>), StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status404NotFound)]
[ResponseCache(CacheProfileName = "7Days")]
public ActionResult GameScreenshot(long GameId)
public async Task<ActionResult> GameScreenshot(long GameId)
{
try
{
@@ -1285,7 +1383,7 @@ namespace gaseous_server.Controllers
[ProducesResponseType(typeof(Screenshot), StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status404NotFound)]
[ResponseCache(CacheProfileName = "7Days")]
public ActionResult GameScreenshot(long GameId, long ScreenshotId)
public async Task<ActionResult> GameScreenshot(long GameId, long ScreenshotId)
{
try
{
@@ -1319,7 +1417,7 @@ namespace gaseous_server.Controllers
[Route("{GameId}/screenshots/{ScreenshotId}/image/{size}/{ImageName}")]
[ProducesResponseType(typeof(FileStreamResult), StatusCodes.Status200OK)]
[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
{
@@ -1370,7 +1468,7 @@ namespace gaseous_server.Controllers
[ProducesResponseType(typeof(List<GameVideo>), StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status404NotFound)]
[ResponseCache(CacheProfileName = "7Days")]
public ActionResult GameVideo(long GameId)
public async Task<ActionResult> GameVideo(long GameId)
{
try
{

View File

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

View File

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

View File

@@ -13,6 +13,8 @@ using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Microsoft.CodeAnalysis.Scripting;
using Microsoft.AspNetCore.Authorization;
using System.Text;
using Asp.Versioning;
namespace gaseous_server.Controllers
{
@@ -37,6 +39,32 @@ namespace gaseous_server.Controllers
return Ok(PlatformMapping.PlatformMap);
}
[MapToApiVersion("1.0")]
[MapToApiVersion("1.1")]
[HttpGet]
[Route("PlatformMap.json")]
[ProducesResponseType(typeof(FileStreamResult), StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status404NotFound)]
public ActionResult DownloadPlatformMap()
{
string srcJson = Newtonsoft.Json.JsonConvert.SerializeObject(PlatformMapping.PlatformMap, Newtonsoft.Json.Formatting.Indented);
string filename = "PlatformMap.json";
byte[] bytes = Encoding.UTF8.GetBytes(srcJson);
string contentType = "application/json";
var cd = new System.Net.Mime.ContentDisposition
{
FileName = filename,
Inline = true,
DispositionType = "attachment"
};
Response.Headers.Add("Content-Disposition", cd.ToString());
return File(bytes, contentType);
}
[MapToApiVersion("1.0")]
[MapToApiVersion("1.1")]
[HttpGet]

View File

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

View File

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

View File

@@ -12,7 +12,7 @@ using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using NuGet.Common;
using static gaseous_server.Classes.Metadata.Games;
using Asp.Versioning;
namespace gaseous_server.Controllers
{
@@ -73,9 +73,11 @@ namespace gaseous_server.Controllers
private static async Task<List<GaseousGame>> _SearchForGame(long PlatformId, string SearchString)
{
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 += "where platforms = (" + PlatformId + ");";
searchBody += "limit 100;";
List<GaseousGame>? searchCache = Communications.GetSearchCache<List<GaseousGame>>(searchFields, searchBody);
@@ -85,12 +87,12 @@ namespace gaseous_server.Controllers
// get Game metadata from data source
Communications comms = new Communications();
var results = await comms.APIComm<Game>(IGDBClient.Endpoints.Games, searchFields, searchBody);
List<GaseousGame> games = new List<GaseousGame>();
foreach (Game game in results.ToList())
{
Storage.CacheStatus cacheStatus = Storage.GetCacheStatus("Game", (long)game.Id);
switch(cacheStatus)
switch (cacheStatus)
{
case Storage.CacheStatus.NotPresent:
Storage.NewCacheValue(game, false);

View File

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

View File

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

View File

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

View File

@@ -17,6 +17,8 @@ using Microsoft.AspNetCore.Mvc;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using Microsoft.CodeAnalysis.Scripting;
using static gaseous_server.Classes.Metadata.AgeRatings;
using Asp.Versioning;
using Humanizer;
namespace gaseous_server.Controllers.v1_1
{
@@ -85,7 +87,7 @@ namespace gaseous_server.Controllers.v1_1
model.GameAgeRating.IncludeUnrated = false;
}
return Ok(GetGames(model, pageNumber, pageSize));
return Ok(GetGames(model, user.Id, pageNumber, pageSize));
}
else
{
@@ -141,9 +143,13 @@ namespace gaseous_server.Controllers.v1_1
public List<string> GameMode { get; set; }
public List<string> PlayerPerspective { get; set; }
public List<string> Theme { get; set; }
public int MinimumReleaseYear { get; set; } = -1;
public int MaximumReleaseYear { get; set; } = -1;
public GameRatingItem GameRating { get; set; } = new GameRatingItem();
public GameAgeRatingItem GameAgeRating { get; set; } = new GameAgeRatingItem();
public GameSortingItem Sorting { get; set; } = new GameSortingItem();
public bool HasSavedGame { get; set; }
public bool IsFavourite { get; set; }
public class GameRatingItem
@@ -181,11 +187,12 @@ namespace gaseous_server.Controllers.v1_1
}
}
public static GameReturnPackage GetGames(GameSearchModel model, int pageNumber = 0, int pageSize = 0)
public static GameReturnPackage GetGames(GameSearchModel model, string userid, int pageNumber = 0, int pageSize = 0)
{
string whereClause = "";
string havingClause = "";
Dictionary<string, object> whereParams = new Dictionary<string, object>();
whereParams.Add("userid", userid);
List<string> whereClauses = new List<string>();
List<string> havingClauses = new List<string>();
@@ -202,6 +209,32 @@ namespace gaseous_server.Controllers.v1_1
whereParams.Add("@Name", "(*" + model.Name + "*) (" + model.Name + ") ");
}
if (model.HasSavedGame == true)
{
string hasSavesTemp = "(RomSavedStates.RomSaveCount IS NOT NULL OR RomGroupSavedStates.MediaGroupSaveCount IS NOT NULL)";
whereClauses.Add(hasSavesTemp);
}
if (model.IsFavourite == true)
{
string isFavTemp = "Favourite = 1";
havingClauses.Add(isFavTemp);
}
if (model.MinimumReleaseYear != -1)
{
string releaseTempMinVal = "FirstReleaseDate >= @minreleasedate";
whereParams.Add("minreleasedate", new DateTime(model.MinimumReleaseYear, 1, 1));
havingClauses.Add(releaseTempMinVal);
}
if (model.MaximumReleaseYear != -1)
{
string releaseTempMaxVal = "FirstReleaseDate <= @maxreleasedate";
whereParams.Add("maxreleasedate", new DateTime(model.MaximumReleaseYear, 12, 31, 23, 59, 59));
havingClauses.Add(releaseTempMaxVal);
}
if (model.GameRating != null)
{
List<string> ratingClauses = new List<string>();
@@ -444,31 +477,154 @@ namespace gaseous_server.Controllers.v1_1
string orderByClause = "ORDER BY `" + orderByField + "` " + orderByOrder;
Database db = new Database(Database.databaseType.MySql, Config.DatabaseConfiguration.ConnectionString);
//string sql = "SELECT DISTINCT view_Games.* FROM view_Games LEFT JOIN Relation_Game_Platforms ON view_Games.Id = Relation_Game_Platforms.GameId AND (Relation_Game_Platforms.PlatformsId IN (SELECT DISTINCT PlatformId FROM Games_Roms WHERE Games_Roms.GameId = view_Games.Id)) LEFT JOIN Relation_Game_Genres ON view_Games.Id = Relation_Game_Genres.GameId LEFT JOIN Relation_Game_GameModes ON view_Games.Id = Relation_Game_GameModes.GameId LEFT JOIN Relation_Game_PlayerPerspectives ON view_Games.Id = Relation_Game_PlayerPerspectives.GameId LEFT JOIN Relation_Game_Themes ON view_Games.Id = Relation_Game_Themes.GameId " + whereClause + " " + havingClause + " " + orderByClause;
string sql = "SELECT DISTINCT Game.Id, Game.`Name`, Game.NameThe, Game.PlatformId, Game.TotalRating, Game.TotalRatingCount, Game.Cover, Game.Artworks, Game.FirstReleaseDate, Game.Category, Game.ParentGame, Game.AgeRatings, Game.AgeGroupId, Game.RomCount FROM (SELECT DISTINCT Game.*, CASE WHEN Game.`Name` LIKE 'The %' THEN CONCAT(TRIM(SUBSTR(Game.`Name` FROM 4)), ', The') ELSE Game.`Name` END AS NameThe, Games_Roms.PlatformId, AgeGroup.AgeGroupId, COUNT(Games_Roms.Id) AS RomCount FROM Game LEFT JOIN AgeGroup ON Game.Id = AgeGroup.GameId LEFT JOIN Games_Roms ON Game.Id = Games_Roms.GameId" + platformWhereClause + " LEFT JOIN AlternativeName ON Game.Id = AlternativeName.Game " + nameWhereClause + " GROUP BY Game.Id HAVING RomCount > 0) Game LEFT JOIN Relation_Game_Genres ON Game.Id = Relation_Game_Genres.GameId LEFT JOIN Relation_Game_GameModes ON Game.Id = Relation_Game_GameModes.GameId LEFT JOIN Relation_Game_PlayerPerspectives ON Game.Id = Relation_Game_PlayerPerspectives.GameId LEFT JOIN Relation_Game_Themes ON Game.Id = Relation_Game_Themes.GameId " + whereClause + " " + havingClause + " " + orderByClause;
List<IGDB.Models.Game> RetVal = new List<IGDB.Models.Game>();
string sql = @"
SET SESSION sql_mode=(SELECT REPLACE(@@sql_mode,'ONLY_FULL_GROUP_BY',''));
SELECT DISTINCT
Game.Id,
Game.`Name`,
Game.NameThe,
Game.Slug,
Game.PlatformId,
Game.TotalRating,
Game.TotalRatingCount,
Game.Cover,
Game.Artworks,
Game.FirstReleaseDate,
Game.Category,
Game.ParentGame,
Game.AgeRatings,
Game.AgeGroupId,
Game.RomCount,
RomSavedStates.RomSaveCount,
RomGroupSavedStates.MediaGroupSaveCount,
CASE
WHEN Favourites.UserId IS NULL THEN 0
ELSE 1
END AS Favourite
FROM
(SELECT DISTINCT
Game.*,
CASE
WHEN Game.`Name` LIKE 'The %' THEN CONCAT(TRIM(SUBSTR(Game.`Name` FROM 4)), ', The')
ELSE Game.`Name`
END AS NameThe,
Games_Roms.PlatformId,
AgeGroup.AgeGroupId,
COUNT(Games_Roms.Id) AS RomCount
FROM
Game
LEFT JOIN AgeGroup ON Game.Id = AgeGroup.GameId
LEFT JOIN Games_Roms ON Game.Id = Games_Roms.GameId" + platformWhereClause + @"
LEFT JOIN AlternativeName ON Game.Id = AlternativeName.Game " + nameWhereClause + @"
GROUP BY Game.Id
HAVING RomCount > 0) Game
LEFT JOIN
(SELECT
Games_Roms.GameId, COUNT(GameState.Id) AS RomSaveCount
FROM
GameState
JOIN Games_Roms ON GameState.RomId = Games_Roms.Id
WHERE
GameState.IsMediaGroup = 0
AND GameState.UserId = @userid
GROUP BY Games_Roms.GameId) RomSavedStates ON Game.Id = RomSavedStates.GameId
LEFT JOIN
(SELECT
RomMediaGroup.GameId,
COUNT(RomMediaGroup.GameId) AS MediaGroupSaveCount
FROM
RomMediaGroup
JOIN GameState ON RomMediaGroup.Id = GameState.RomId
AND GameState.IsMediaGroup = 1
AND GameState.UserId = @userid
GROUP BY RomMediaGroup.GameId) RomGroupSavedStates ON Game.Id = RomGroupSavedStates.GameId
LEFT JOIN
Relation_Game_Genres ON Game.Id = Relation_Game_Genres.GameId
LEFT JOIN
Relation_Game_GameModes ON Game.Id = Relation_Game_GameModes.GameId
LEFT JOIN
Relation_Game_PlayerPerspectives ON Game.Id = Relation_Game_PlayerPerspectives.GameId
LEFT JOIN
Relation_Game_Themes ON Game.Id = Relation_Game_Themes.GameId
LEFT JOIN
Favourites ON Game.Id = Favourites.GameId AND Favourites.UserId = @userid " + whereClause + " " + havingClause + " " + orderByClause;
List<Games.MinimalGameItem> RetVal = new List<Games.MinimalGameItem>();
DataTable dbResponse = db.ExecuteCMD(sql, whereParams);
// get count
int RecordCount = dbResponse.Rows.Count;
// compile data for return
int pageOffset = pageSize * (pageNumber - 1);
for (int i = pageOffset; i < dbResponse.Rows.Count; i++)
{
if (i >= (pageOffset + pageSize))
if (pageNumber != 0 && pageSize != 0)
{
break;
if (i >= (pageOffset + pageSize))
{
break;
}
}
Game retGame = Storage.BuildCacheObject<Game>(new Game() , dbResponse.Rows[i]);
RetVal.Add(retGame);
Games.MinimalGameItem retMinGame = new Games.MinimalGameItem(retGame);
retMinGame.Index = i;
if (dbResponse.Rows[i]["RomSaveCount"] != DBNull.Value || dbResponse.Rows[i]["MediaGroupSaveCount"] != DBNull.Value)
{
retMinGame.HasSavedGame = true;
}
else
{
retMinGame.HasSavedGame = false;
}
if ((int)dbResponse.Rows[i]["Favourite"] == 0)
{
retMinGame.IsFavourite = false;
}
else
{
retMinGame.IsFavourite = true;
}
RetVal.Add(retMinGame);
}
GameReturnPackage gameReturn = new GameReturnPackage(RecordCount, RetVal);
// build alpha list
Dictionary<string, int> AlphaList = new Dictionary<string, int>();
int CurrentPage = 1;
int NextPageIndex = pageSize;
for (int i = 0; i < dbResponse.Rows.Count; i++)
{
string firstChar = dbResponse.Rows[i]["NameThe"].ToString().Substring(0, 1).ToUpperInvariant();
if (!"ABCDEFGHIJKLMNOPQRSTUVWXYZ".Contains(firstChar))
{
if (!AlphaList.ContainsKey("#"))
{
AlphaList.Add("#", 1);
}
}
else
{
if (!AlphaList.ContainsKey(firstChar))
{
AlphaList.Add(firstChar, CurrentPage);
}
if (NextPageIndex == i + 1)
{
NextPageIndex += pageSize;
CurrentPage += 1;
}
}
}
GameReturnPackage gameReturn = new GameReturnPackage
{
Count = RecordCount,
Games = RetVal,
AlphaList = AlphaList
};
return gameReturn;
}
@@ -495,6 +651,7 @@ namespace gaseous_server.Controllers.v1_1
public int Count { get; set; }
public List<Games.MinimalGameItem> Games { get; set; } = new List<Games.MinimalGameItem>();
public Dictionary<string, int> AlphaList { get; set; }
}
}
}

View File

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

View File

@@ -0,0 +1,532 @@
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Authorization;
using gaseous_server.Models;
using gaseous_server.Classes;
using Authentication;
using Microsoft.AspNetCore.Identity;
using System.Data;
using Asp.Versioning;
using System.IO.Compression;
namespace gaseous_server.Controllers.v1_1
{
[Route("api/v{version:apiVersion}/[controller]")]
[ApiVersion("1.0")]
[ApiVersion("1.1")]
[ApiController]
public class StateManagerController : ControllerBase
{
private readonly UserManager<ApplicationUser> _userManager;
private readonly SignInManager<ApplicationUser> _signInManager;
public StateManagerController(
UserManager<ApplicationUser> userManager,
SignInManager<ApplicationUser> signInManager
)
{
_userManager = userManager;
_signInManager = signInManager;
}
[MapToApiVersion("1.0")]
[MapToApiVersion("1.1")]
[HttpPost]
[Authorize]
[ProducesResponseType(typeof(Models.GameStateItem), StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status404NotFound)]
[Route("{RomId}")]
public async Task<ActionResult> SaveStateAsync(long RomId, UploadStateModel uploadState, bool IsMediaGroup = false)
{
var user = await _userManager.GetUserAsync(User);
byte[] CompressedState = Common.Compress(uploadState.StateByteArray);
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", uploadState.ScreenshotByteArray },
{ "state", CompressedState },
{ "zipped", true }
};
DataTable data = db.ExecuteCMD(sql, dbDict);
if (IsMediaGroup == false)
{
Logging.Log(Logging.LogType.Information, "Save State", "Saved state for rom id " + RomId + ". State size: " + uploadState.StateByteArrayBase64.Length + " Compressed size: " + CompressedState.Length);
}
else
{
Logging.Log(Logging.LogType.Information, "Save State", "Saved state for media group id " + RomId + ". State size: " + uploadState.StateByteArrayBase64.Length + " Compressed size: " + CompressedState.Length);
}
return Ok(await GetStateAsync(RomId, (long)(ulong)data.Rows[0][0], IsMediaGroup));
}
[MapToApiVersion("1.0")]
[MapToApiVersion("1.1")]
[HttpGet]
[Authorize]
[ProducesResponseType(typeof(List<Models.GameStateItem>), StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status404NotFound)]
[Route("{RomId}")]
public async Task<ActionResult> GetAllStateAsync(long RomId, bool IsMediaGroup = false)
{
var user = await _userManager.GetUserAsync(User);
Database db = new Database(Database.databaseType.MySql, Config.DatabaseConfiguration.ConnectionString);
string sql = "SELECT Id, StateDateTime, `Name`, Screenshot FROM GameState WHERE RomId = @romid AND IsMediaGroup = @ismediagroup AND UserId = @userid ORDER BY StateDateTime DESC;";
Dictionary<string, object> dbDict = new Dictionary<string, object>
{
{ "romid", RomId },
{ "userid", user.Id },
{ "ismediagroup", IsMediaGroup }
};
DataTable data = db.ExecuteCMD(sql, dbDict);
List<Models.GameStateItem> gameStates = new List<GameStateItem>();
foreach (DataRow row in data.Rows)
{
gameStates.Add(BuildGameStateItem(row));
}
return Ok(gameStates);
}
[MapToApiVersion("1.0")]
[MapToApiVersion("1.1")]
[HttpGet]
[Authorize]
[ProducesResponseType(typeof(Models.GameStateItem), StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status404NotFound)]
[Route("{RomId}/{StateId}")]
public async Task<ActionResult> GetStateAsync(long RomId, long StateId, bool IsMediaGroup = false)
{
var user = await _userManager.GetUserAsync(User);
Database db = new Database(Database.databaseType.MySql, Config.DatabaseConfiguration.ConnectionString);
string sql = "SELECT Id, StateDateTime, `Name`, Screenshot FROM GameState WHERE Id = @id AND RomId = @romid AND IsMediaGroup = @ismediagroup AND UserId = @userid;";
Dictionary<string, object> dbDict = new Dictionary<string, object>
{
{ "id", StateId },
{ "romid", RomId },
{ "userid", user.Id },
{ "ismediagroup", IsMediaGroup }
};
DataTable data = db.ExecuteCMD(sql, dbDict);
if (data.Rows.Count == 0)
{
// invalid match - return not found
return NotFound();
}
else
{
GameStateItem stateItem = BuildGameStateItem(data.Rows[0]);
return Ok(stateItem);
}
}
[MapToApiVersion("1.0")]
[MapToApiVersion("1.1")]
[HttpDelete]
[Authorize]
[ProducesResponseType(StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status404NotFound)]
[Route("{RomId}/{StateId}")]
public async Task<ActionResult> DeleteStateAsync(long RomId, long StateId, bool IsMediaGroup = false)
{
var user = await _userManager.GetUserAsync(User);
Database db = new Database(Database.databaseType.MySql, Config.DatabaseConfiguration.ConnectionString);
string sql = "DELETE FROM GameState WHERE Id = @id AND RomId = @romid AND IsMediaGroup = @ismediagroup AND UserId = @userid;";
Dictionary<string, object> dbDict = new Dictionary<string, object>
{
{ "id", StateId },
{ "romid", RomId },
{ "userid", user.Id },
{ "ismediagroup", IsMediaGroup }
};
db.ExecuteNonQuery(sql, dbDict);
return Ok();
}
[MapToApiVersion("1.0")]
[MapToApiVersion("1.1")]
[HttpPut]
[Authorize]
[ProducesResponseType(StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status404NotFound)]
[Route("{RomId}/{StateId}")]
public async Task<ActionResult> EditStateAsync(long RomId, long StateId, GameStateItemUpdateModel model, bool IsMediaGroup = false)
{
var user = await _userManager.GetUserAsync(User);
Database db = new Database(Database.databaseType.MySql, Config.DatabaseConfiguration.ConnectionString);
string sql = "UPDATE GameState SET `Name` = @name WHERE Id = @id AND RomId = @romid AND IsMediaGroup = @ismediagroup AND UserId = @userid;";
Dictionary<string, object> dbDict = new Dictionary<string, object>
{
{ "id", StateId },
{ "romid", RomId },
{ "userid", user.Id },
{ "ismediagroup", IsMediaGroup },
{ "name", model.Name }
};
db.ExecuteNonQuery(sql, dbDict);
return Ok();
}
[MapToApiVersion("1.0")]
[MapToApiVersion("1.1")]
[HttpGet]
[Authorize]
[ProducesResponseType(typeof(FileStreamResult), StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status404NotFound)]
[Route("{RomId}/{StateId}/Screenshot/")]
[Route("{RomId}/{StateId}/Screenshot/image.png")]
public async Task<ActionResult> GetStateScreenshotAsync(long RomId, long StateId, bool IsMediaGroup = false)
{
var user = await _userManager.GetUserAsync(User);
Database db = new Database(Database.databaseType.MySql, Config.DatabaseConfiguration.ConnectionString);
string sql = "SELECT Screenshot FROM GameState WHERE Id = @id AND RomId = @romid AND IsMediaGroup = @ismediagroup AND UserId = @userid;";
Dictionary<string, object> dbDict = new Dictionary<string, object>
{
{ "id", StateId },
{ "romid", RomId },
{ "userid", user.Id },
{ "ismediagroup", IsMediaGroup }
};
DataTable data = db.ExecuteCMD(sql, dbDict);
if (data.Rows.Count == 0)
{
// invalid match - return not found
return NotFound();
}
else
{
string filename = "image.jpg";
byte[] bytes = (byte[])data.Rows[0][0];
string contentType = "image/png";
var cd = new System.Net.Mime.ContentDisposition
{
FileName = filename,
Inline = true,
};
Response.Headers.Add("Content-Disposition", cd.ToString());
Response.Headers.Add("Cache-Control", "public, max-age=604800");
return File(bytes, contentType);
}
}
[MapToApiVersion("1.0")]
[MapToApiVersion("1.1")]
[HttpGet]
[Authorize]
[ProducesResponseType(typeof(FileStreamResult), StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status404NotFound)]
[Route("{RomId}/{StateId}/State/")]
[Route("{RomId}/{StateId}/State/savestate.state")]
public async Task<ActionResult> GetStateDataAsync(long RomId, long StateId, bool IsMediaGroup = false, bool StateOnly = false)
{
var user = await _userManager.GetUserAsync(User);
Database db = new Database(Database.databaseType.MySql, Config.DatabaseConfiguration.ConnectionString);
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>
{
{ "id", StateId },
{ "romid", RomId },
{ "userid", user.Id },
{ "ismediagroup", IsMediaGroup }
};
DataTable data = db.ExecuteCMD(sql, dbDict);
if (data.Rows.Count == 0)
{
// invalid match - return not found
return NotFound();
}
else
{
// get rom data
Roms.GameRomItem romItem = Roms.GetRom(RomId);
byte[] bytes;
if ((bool)data.Rows[0]["Zipped"] == false)
{
bytes = (byte[])data.Rows[0]["State"];
}
else
{
bytes = Common.Decompress((byte[])data.Rows[0]["State"]);
}
string contentType = "";
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
{
FileName = filename,
Inline = true,
};
Response.Headers.Add("Content-Disposition", cd.ToString());
Response.Headers.Add("Cache-Control", "public, max-age=604800");
return File(bytes, contentType);
}
}
[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)
{
bool HasScreenshot = true;
if (dr["Screenshot"] == DBNull.Value)
{
HasScreenshot = false;
}
GameStateItem stateItem = new GameStateItem
{
Id = (long)dr["Id"],
Name = (string)dr["Name"],
SaveTime = DateTime.Parse(((DateTime)dr["StateDateTime"]).ToString("yyyy-MM-ddThh:mm:ss") + 'Z'),
HasScreenshot = HasScreenshot
};
return stateItem;
}
}
}

View File

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

View File

@@ -0,0 +1,35 @@
namespace gaseous_server.Models
{
public class UploadStateModel
{
public string ScreenshotByteArrayBase64 { get; set; }
public string StateByteArrayBase64 { get; set; }
public byte[] ScreenshotByteArray
{
get
{
return Convert.FromBase64String(ScreenshotByteArrayBase64);
}
}
public byte[] StateByteArray
{
get
{
return Convert.FromBase64String(StateByteArrayBase64);
}
}
}
public class GameStateItem
{
public long Id { get; set; }
public string Name { get; set; } = "";
public DateTime SaveTime { get; set; }
public bool HasScreenshot { get; set; }
}
public class GameStateItemUpdateModel
{
public string Name { get; set; } = "";
}
}

View File

@@ -16,7 +16,7 @@ namespace gaseous_server.Models
{
var targetType = this.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
var sp = sourceType.GetProperty(prop.Name);
@@ -29,6 +29,8 @@ namespace gaseous_server.Models
}
}
public bool HasSavedGame { get; set; } = false;
public IGDB.Models.Cover? CoverItem
{
get
@@ -37,7 +39,11 @@ namespace gaseous_server.Models
{
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;
}
@@ -46,49 +52,5 @@ namespace gaseous_server.Models
return null;
}
}
// public List<IGDB.Models.Artwork>? ArtworksItem
// {
// get
// {
// if (this.Artworks != null)
// {
// if (this.Artworks.Ids != null)
// {
// List<IGDB.Models.Artwork> artworks = new List<IGDB.Models.Artwork>();
// foreach (long id in this.Artworks.Ids)
// {
// artworks.Add(gaseous_server.Classes.Metadata.Artworks.GetArtwork(id, Config.LibraryConfiguration.LibraryMetadataDirectory_Game(this), false));
// }
// return artworks;
// }
// }
// return null;
// }
// }
// public List<IGDB.Models.Screenshot>? ScreenshotsItem
// {
// get
// {
// if (this.Screenshots != null)
// {
// if (this.Screenshots.Ids != null)
// {
// List<IGDB.Models.Screenshot> screenshots = new List<IGDB.Models.Screenshot>();
// foreach (long id in this.Screenshots.Ids)
// {
// screenshots.Add(gaseous_server.Classes.Metadata.Screenshots.GetScreenshot(id, Config.LibraryConfiguration.LibraryMetadataDirectory_Game(this), false));
// }
// return screenshots;
// }
// }
// return null;
// }
// }
}
}

View File

@@ -13,8 +13,8 @@ using Newtonsoft.Json;
namespace gaseous_server.Models
{
public class PlatformMapping
{
public class PlatformMapping
{
private static Dictionary<string, PlatformMapItem> PlatformMapCache = new Dictionary<string, PlatformMapItem>();
/// <summary>
@@ -27,7 +27,8 @@ namespace gaseous_server.Models
{
string rawJson = reader.ReadToEnd();
List<PlatformMapItem> platforms = new List<PlatformMapItem>();
Newtonsoft.Json.JsonSerializerSettings jsonSerializerSettings = new JsonSerializerSettings{
Newtonsoft.Json.JsonSerializerSettings jsonSerializerSettings = new JsonSerializerSettings
{
MaxDepth = 64
};
platforms = Newtonsoft.Json.JsonConvert.DeserializeObject<List<PlatformMapItem>>(rawJson, jsonSerializerSettings);
@@ -42,6 +43,7 @@ namespace gaseous_server.Models
// exists
if (ResetToDefault == false)
{
WriteAvailableEmulators(mapItem);
Logging.Log(Logging.LogType.Information, "Platform Map", "Skipping import of " + mapItem.IGDBName + " - already in database.");
}
else
@@ -73,7 +75,7 @@ namespace gaseous_server.Models
foreach (PlatformMapItem mapItem in platforms)
{
// get the IGDB platform data
Platform platform = Platforms.GetPlatform(mapItem.IGDBId);
Platform platform = Platforms.GetPlatform(mapItem.IGDBId, false);
try
{
@@ -91,7 +93,7 @@ namespace gaseous_server.Models
}
}
}
public static List<PlatformMapItem> PlatformMap
{
get
@@ -253,6 +255,30 @@ namespace gaseous_server.Models
}
}
public static void WriteAvailableEmulators(PlatformMapItem item)
{
Database db = new Database(Database.databaseType.MySql, Config.DatabaseConfiguration.ConnectionString);
string sql = "";
Dictionary<string, object> dbDict = new Dictionary<string, object>();
sql = "UPDATE PlatformMap SET RetroPieDirectoryName=@RetroPieDirectoryName, WebEmulator_Type=@WebEmulator_Type, WebEmulator_Core=@WebEmulator_Core, AvailableWebEmulators=@AvailableWebEmulators WHERE Id = @Id; ";
dbDict.Add("Id", item.IGDBId);
dbDict.Add("RetroPieDirectoryName", item.RetroPieDirectoryName);
if (item.WebEmulator != null)
{
dbDict.Add("WebEmulator_Type", item.WebEmulator.Type);
dbDict.Add("WebEmulator_Core", item.WebEmulator.Core);
dbDict.Add("AvailableWebEmulators", Newtonsoft.Json.JsonConvert.SerializeObject(item.WebEmulator.AvailableWebEmulators));
}
else
{
dbDict.Add("WebEmulator_Type", "");
dbDict.Add("WebEmulator_Core", "");
dbDict.Add("AvailableWebEmulators", "");
}
db.ExecuteCMD(sql, dbDict);
}
static PlatformMapItem BuildPlatformMapItem(DataRow row)
{
long IGDBId = (long)row["Id"];
@@ -261,7 +287,7 @@ namespace gaseous_server.Models
string sql = "";
// get platform data
IGDB.Models.Platform? platform = Platforms.GetPlatform(IGDBId);
IGDB.Models.Platform? platform = Platforms.GetPlatform(IGDBId, false);
if (platform != null)
{
@@ -344,18 +370,20 @@ namespace gaseous_server.Models
mapItem.IGDBName = platform.Name;
mapItem.IGDBSlug = platform.Slug;
mapItem.AlternateNames = alternateNames;
mapItem.Extensions = new PlatformMapItem.FileExtensions{
mapItem.Extensions = new PlatformMapItem.FileExtensions
{
SupportedFileExtensions = knownExtensions,
UniqueFileExtensions = uniqueExtensions
};
mapItem.RetroPieDirectoryName = (string)Common.ReturnValueIfNull(row["RetroPieDirectoryName"], "");
mapItem.WebEmulator = new PlatformMapItem.WebEmulatorItem{
mapItem.WebEmulator = new PlatformMapItem.WebEmulatorItem
{
Type = (string)Common.ReturnValueIfNull(row["WebEmulator_Type"], ""),
Core = (string)Common.ReturnValueIfNull(row["WebEmulator_Core"], ""),
AvailableWebEmulators = Newtonsoft.Json.JsonConvert.DeserializeObject<List<PlatformMapItem.WebEmulatorItem.AvailableWebEmulatorItem>>((string)Common.ReturnValueIfNull(row["AvailableWebEmulators"], "[]"))
};
mapItem.Bios = bioss;
if (PlatformMapCache.ContainsKey(IGDBId.ToString()))
{
PlatformMapCache[IGDBId.ToString()] = mapItem;
@@ -436,7 +464,7 @@ namespace gaseous_server.Models
public string IGDBName { get; set; }
public string IGDBSlug { get; set; }
public List<string> AlternateNames { get; set; } = new List<string>();
public FileExtensions Extensions { get; set; }
public class FileExtensions
{
@@ -478,6 +506,6 @@ namespace gaseous_server.Models
public string filename { get; set; }
}
}
}
}
}

View File

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

View File

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

View File

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

View File

@@ -3,18 +3,14 @@ using System.Text.Json.Serialization;
using gaseous_server;
using gaseous_server.Classes;
using gaseous_server.Models;
using gaseous_server.SignatureIngestors.XML;
using Microsoft.AspNetCore.Http.Features;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.Versioning;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.AspNetCore.Server.Kestrel.Core;
using Microsoft.OpenApi.Models;
using Authentication;
using Microsoft.AspNetCore.Identity;
using Microsoft.AspNetCore.Identity.UI.Services;
using IGDB.Models;
using gaseous_server.Classes.Metadata;
using Asp.Versioning;
Logging.WriteToDiskOnly = true;
Logging.Log(Logging.LogType.Information, "Startup", "Starting Gaseous Server " + Assembly.GetExecutingAssembly().GetName().Version);
@@ -40,12 +36,19 @@ db = new Database(Database.databaseType.MySql, Config.DatabaseConfiguration.Conn
// set up db
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
AgeRatings.PopulateAgeMap();
// load app settings
Config.InitSettings();
// disable hasheous
Config.MetadataConfiguration.SignatureSource = HasheousClient.Models.MetadataModel.SignatureSources.LocalOnly;
// write updated settings back to the config file
Config.UpdateConfig();
@@ -55,15 +58,6 @@ Communications.MetadataSource = Config.MetadataConfiguration.MetadataSource;
// set up hasheous client
HasheousClient.WebApp.HttpHelper.BaseUri = Config.MetadataConfiguration.HasheousHost;
// set initial values
Guid APIKey = Guid.NewGuid();
if (Config.ReadSetting("API Key", "Test API Key") == "Test API Key")
{
// it's a new api key save it
Logging.Log(Logging.LogType.Information, "Startup", "Setting initial API key");
Config.SetSetting("API Key", APIKey.ToString());
}
// clean up storage
if (Directory.Exists(Config.LibraryConfiguration.LibraryTempDirectory))
{
@@ -140,12 +134,7 @@ builder.Services.AddApiVersioning(config =>
config.ApiVersionReader = ApiVersionReader.Combine(new UrlSegmentApiVersionReader(),
new HeaderApiVersionReader("x-api-version"),
new MediaTypeApiVersionReader("x-api-version"));
});
// builder.Services.AddApiVersioning(setup =>
// {
// setup.ApiVersionReader = new UrlSegmentApiVersionReader();
// });
builder.Services.AddVersionedApiExplorer(setup =>
}).AddApiExplorer(setup =>
{
setup.GroupNameFormat = "'v'VVV";
setup.SubstituteApiVersionInUrl = true;
@@ -242,15 +231,6 @@ builder.Services.ConfigureApplicationCookie(options =>
options.Cookie.SecurePolicy = CookieSecurePolicy.SameAsRequest;
options.Cookie.SameSite = SameSiteMode.Strict;
});
// builder.Services.AddIdentityCore<ApplicationUser>(options => {
// options.SignIn.RequireConfirmedAccount = false;
// options.User.RequireUniqueEmail = true;
// options.Password.RequireDigit = false;
// options.Password.RequiredLength = 10;
// options.Password.RequireNonAlphanumeric = false;
// options.Password.RequireUppercase = false;
// options.Password.RequireLowercase = false;
// });
builder.Services.AddScoped<UserStore>();
builder.Services.AddScoped<RoleStore>();
@@ -277,8 +257,16 @@ var app = builder.Build();
app.UseSwagger();
app.UseSwaggerUI(options =>
{
options.SwaggerEndpoint($"/swagger/v1/swagger.json", "v1.0");
options.SwaggerEndpoint($"/swagger/v1.1/swagger.json", "v1.1");
// options.SwaggerEndpoint($"/swagger/v1/swagger.json", "v1.0");
// options.SwaggerEndpoint($"/swagger/v1.1/swagger.json", "v1.1");
var descriptions = app.DescribeApiVersions();
foreach (var description in descriptions)
{
var url = $"/swagger/{description.GroupName}/swagger.json";
var name = description.GroupName.ToUpperInvariant();
options.SwaggerEndpoint(url, name);
}
}
);
//}
@@ -292,7 +280,7 @@ using (var scope = app.Services.CreateScope())
{
var roleManager = scope.ServiceProvider.GetRequiredService<RoleStore>();
var roles = new[] { "Admin", "Gamer", "Player" };
foreach (var role in roles)
{
if (await roleManager.FindByNameAsync(role, CancellationToken.None) == null)
@@ -322,11 +310,11 @@ app.Use(async (context, next) =>
string correlationId = Guid.NewGuid().ToString();
CallContext.SetData("CorrelationId", correlationId);
CallContext.SetData("CallingProcess", context.Request.Method + ": " + context.Request.Path);
string userIdentity;
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
{
@@ -348,7 +336,7 @@ app.Use(async (context, next) =>
// - the server will not start while the RecoverAccount.txt file exists
string PasswordRecoveryFile = Path.Combine(Config.LibraryConfiguration.LibraryRootDirectory, "RecoverAccount.txt");
if (!String.IsNullOrEmpty(Environment.GetEnvironmentVariable("recoveraccount")))
{
{
if (File.Exists(PasswordRecoveryFile))
{
// password has already been set - do nothing and just exit
@@ -364,7 +352,7 @@ if (!String.IsNullOrEmpty(Environment.GetEnvironmentVariable("recoveraccount")))
string password = new string(Enumerable.Repeat(chars, length).Select(s => s[random.Next(s.Length)]).ToArray());
File.WriteAllText(PasswordRecoveryFile, password);
// reset the password
using (var scope = app.Services.CreateScope())
{
@@ -421,69 +409,38 @@ var platformMap = PlatformMapping.PlatformMap;
// add background tasks
ProcessQueue.QueueItems.Add(new ProcessQueue.QueueItem(
ProcessQueue.QueueItemType.SignatureIngestor,
int.Parse(Config.ReadSetting("Interval_SignatureIngestor", "60"))
)
ProcessQueue.QueueItemType.SignatureIngestor)
);
ProcessQueue.QueueItems.Add(new ProcessQueue.QueueItem(
ProcessQueue.QueueItemType.TitleIngestor,
int.Parse(Config.ReadSetting("Interval_TitleIngestor", "1")),
new List<ProcessQueue.QueueItemType>
{
ProcessQueue.QueueItemType.OrganiseLibrary,
ProcessQueue.QueueItemType.LibraryScan
})
ProcessQueue.QueueItemType.TitleIngestor)
);
ProcessQueue.QueueItems.Add(new ProcessQueue.QueueItem(
ProcessQueue.QueueItemType.MetadataRefresh,
int.Parse(Config.ReadSetting("Interval_MetadataRefresh", "360"))
)
ProcessQueue.QueueItemType.MetadataRefresh)
);
ProcessQueue.QueueItems.Add(new ProcessQueue.QueueItem(
ProcessQueue.QueueItemType.OrganiseLibrary,
int.Parse(Config.ReadSetting("Interval_OrganiseLibrary", "1440")),
new List<ProcessQueue.QueueItemType>
{
ProcessQueue.QueueItemType.LibraryScan,
ProcessQueue.QueueItemType.TitleIngestor,
ProcessQueue.QueueItemType.Rematcher
})
ProcessQueue.QueueItemType.OrganiseLibrary)
);
ProcessQueue.QueueItems.Add(new ProcessQueue.QueueItem(
ProcessQueue.QueueItemType.LibraryScan,
int.Parse(Config.ReadSetting("Interval_LibraryScan", "1440")),
new List<ProcessQueue.QueueItemType>
{
ProcessQueue.QueueItemType.OrganiseLibrary,
ProcessQueue.QueueItemType.Rematcher
})
ProcessQueue.QueueItemType.LibraryScan)
);
ProcessQueue.QueueItems.Add(new ProcessQueue.QueueItem(
ProcessQueue.QueueItemType.Rematcher,
int.Parse(Config.ReadSetting("Interval_Rematcher", "1440")),
new List<ProcessQueue.QueueItemType>
{
ProcessQueue.QueueItemType.OrganiseLibrary,
ProcessQueue.QueueItemType.LibraryScan
})
);
ProcessQueue.QueueItems.Add(new ProcessQueue.QueueItem(
ProcessQueue.QueueItemType.Maintainer,
int.Parse(Config.ReadSetting("Interval_Maintainer", "10080")),
new List<ProcessQueue.QueueItemType>
{
ProcessQueue.QueueItemType.All
})
ProcessQueue.QueueItemType.Rematcher)
);
ProcessQueue.QueueItem tempCleanup = new ProcessQueue.QueueItem(
ProcessQueue.QueueItemType.TempCleanup,
1,
new List<ProcessQueue.QueueItemType>(),
false,
false
// maintenance tasks
ProcessQueue.QueueItem dailyMaintenance = new ProcessQueue.QueueItem(
ProcessQueue.QueueItemType.DailyMaintainer
);
ProcessQueue.QueueItems.Add(dailyMaintenance);
ProcessQueue.QueueItem weeklyMaintenance = new ProcessQueue.QueueItem(
ProcessQueue.QueueItemType.WeeklyMaintainer
);
ProcessQueue.QueueItems.Add(weeklyMaintenance);
ProcessQueue.QueueItem tempCleanup = new ProcessQueue.QueueItem(
ProcessQueue.QueueItemType.TempCleanup
);
tempCleanup.ForceExecute();
ProcessQueue.QueueItems.Add(tempCleanup);
Logging.WriteToDiskOnly = false;

View File

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

View File

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

View File

@@ -0,0 +1,11 @@
CREATE TABLE `GameState` (
`Id` BIGINT NOT NULL AUTO_INCREMENT,
`UserId` VARCHAR(45) NULL,
`RomId` BIGINT NULL,
`IsMediaGroup` INT NULL,
`StateDateTime` DATETIME NULL,
`Name` VARCHAR(100) NULL,
`Screenshot` LONGBLOB NULL,
`State` LONGBLOB NULL,
PRIMARY KEY (`Id`),
INDEX `idx_UserId` (`UserId` ASC) VISIBLE);

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

File diff suppressed because it is too large Load Diff

View File

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

View File

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

View File

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

Binary file not shown.

Binary file not shown.

Before

Width:  |  Height:  |  Size: 16 KiB

After

Width:  |  Height:  |  Size: 19 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 72 KiB

After

Width:  |  Height:  |  Size: 22 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 15 KiB

After

Width:  |  Height:  |  Size: 16 KiB

View File

@@ -0,0 +1,79 @@
<!-- <div style='width:640px;height:480px;max-width:100%'> -->
<div style='width:100%;height:100%;'>
<div id='game'></div>
</div>
<script type='text/javascript'>
EJS_player = '#game';
// Can also be fceumm or nestopia
EJS_core = getQueryString('core', 'string');
// Lightgun
EJS_lightgun = false; // can be true or false
// URL to BIOS file
EJS_biosUrl = emuBios;
// URL to Game rom
EJS_gameUrl = decodeURIComponent(getQueryString('rompath', 'string'));
// load state if defined
if (StateUrl) {
console.log('Loading saved state from: ' + StateUrl);
EJS_loadStateURL = StateUrl;
EJS_startOnLoaded = true;
}
// Path to the data directory
EJS_pathtodata = '/emulators/EmulatorJS/data/';
EJS_DEBUG_XX = emulatorDebugMode;
console.log("Debug enabled: " + EJS_DEBUG_XX);
EJS_backgroundImage = emuBackground;
EJS_backgroundBlur = true;
EJS_fullscreenOnLoaded = false;
EJS_gameName = emuGameTitle;
EJS_threads = false;
EJS_Buttons = {
saveSavFiles: false,
loadSavFiles: false
}
EJS_onSaveState = function(e) {
var returnValue = {
"ScreenshotByteArrayBase64": btoa(Uint8ToString(e.screenshot)),
"StateByteArrayBase64": btoa(Uint8ToString(e.state))
};
var url = '/api/v1.1/StateManager/' + romId + '?IsMediaGroup=' + IsMediaGroup;
ajaxCall(
url,
'POST',
function (result) {
console.log("Upload complete");
console.log(result);
displayNotification('State Saved', 'Game state has been saved.', '/api/v1.1/StateManager/' + romId + '/' + result.value.id + '/Screenshot/image.png?IsMediaGroup=' + IsMediaGroup);
},
function (error) {
console.log("An error occurred");
console.log(error);
},
JSON.stringify(returnValue)
);
returnValue = undefined;
}
EJS_onLoadState = function(e) {
showDialog('emulatorloadstate', { "romId": romId, "IsMediaGroup": IsMediaGroup });
}
</script>
<script src='/emulators/EmulatorJS/data/loader.js'></script>

Binary file not shown.

Before

Width:  |  Height:  |  Size: 518 B

After

Width:  |  Height:  |  Size: 786 B

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