Compare commits

...

51 Commits

Author SHA1 Message Date
dependabot[bot]
c2f84a363d chore(deps): bump Microsoft.VisualStudio.Web.CodeGeneration.Design and Microsoft.Extensions.DependencyInjection
Bumps [Microsoft.VisualStudio.Web.CodeGeneration.Design](https://github.com/dotnet/Scaffolding) and [Microsoft.Extensions.DependencyInjection](https://github.com/dotnet/runtime). These dependencies needed to be updated together.

Updates `Microsoft.VisualStudio.Web.CodeGeneration.Design` from 8.0.2 to 8.0.6
- [Release notes](https://github.com/dotnet/Scaffolding/releases)
- [Commits](https://github.com/dotnet/Scaffolding/commits)

Updates `Microsoft.Extensions.DependencyInjection` from 8.0.0 to 8.0.1
- [Release notes](https://github.com/dotnet/runtime/releases)
- [Commits](https://github.com/dotnet/runtime/compare/v8.0.0...v8.0.1)

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

Signed-off-by: dependabot[bot] <support@github.com>
2024-10-14 04:46:56 +00:00
Michael Green
146597dd4b Minor UI tweaks (#431) 2024-09-20 15:00:44 -07:00
Michael Green
f4ba84a54f Update README.MD (#428) 2024-09-17 11:30:52 +10:00
Michael Green
64fb76484b Update to profile cards to display now-playing (#427) 2024-09-17 10:48:49 +10:00
Michael Green
bfade006bd New UI Styling Fixes (#426)
Resolve some issues with how the new UI renders.
2024-09-16 16:25:36 +10:00
Michael Green
c8140d7178 Revamp of BIOS handling (#423)
Many of the platforms are similar enough to other platforms that they'll
share the same BIOS files.

The current storage method stores BIOS files per platform, meaning that
files can be duplicated multiple times to satisfy the requirements of
each platform.

This change stores the files as their hash with a .bios extension
(example: `85ad74194e87c08904327de1a9443b7a.bios`) in a flat directory
structure. This allows BIOS files that are used by multiple platforms to
be shared without duplication.
2024-09-15 20:35:36 +10:00
Michael Green
070589f718 Fix for home path change (#422)
The migration of the docker container to a rootless one requires the
default library to move.

This change strips the library path from the path to the ROM and
replaces it with an SQL view that concatenates the library path and ROM
path.
2024-09-14 01:32:18 +10:00
Michael Green
bb86cb52f6 Added a command line tool for user management (#420)
The CLI tool can be started from the root of the /App directory in the
container with the command:
`./gaseous-cli`

Running without arguments presents the following help screen:
```
Gaseous CLI - A tool for managing the Gaseous Server
Usage: gaseous-cli [command] [options]
Commands:
  user [command] [options] - Manage users
  role [command] [options] - Manage roles
  help - Display this help message
```
2024-09-09 15:11:36 +10:00
Michael Green
7dfb0b54eb All images now support rootless (#418) 2024-09-08 00:14:29 +10:00
Michael Green
f0783fcae8 Added a library scan button (#417) 2024-09-06 01:59:59 +10:00
Michael Green
68be24d514 Delete .github/workflows/codeql.yml (#414) 2024-09-03 12:35:41 +10:00
Michael Green
a5da1a9033 Tweaked the display on the home screen (#411) 2024-09-02 09:36:54 +10:00
Michael Green
fc09681cdd Fixes a bug where the state manager would show an empty list (#410) 2024-09-01 23:25:57 +10:00
Michael Green
6185912151 Regression - Fixed media group save state loading (#409) 2024-09-01 11:37:07 +10:00
Michael Green
deef919d5b UI overhaul (#383) 2024-09-01 01:00: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
f85109abd2 Merge Branch v1.7.0 into main (#348) 2024-04-16 11:54:28 +10:00
Michael Green
080a823cda Merge 1.7.3 into main (#331) 2024-04-15 14:38:17 +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
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
240 changed files with 24351 additions and 9799 deletions

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.12.7z
RUN 7z x -y -o/workspace/gaseous-server/wwwroot/emulators/EmulatorJS 4.0.12.7z

View File

@@ -0,0 +1,48 @@
// 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",
"PWABuilder.pwa-studio",
"ms-azuretools.vscode-docker"
]
}
}
// Uncomment to connect as root instead. More info: https://aka.ms/dev-containers-non-root.
// "remoteUser": "root"
}

View File

@@ -0,0 +1,25 @@
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=${IGDB_CLIENT_ID}
- igdbclientsecret=${IGDB_CLIENT_SECRET}
mariadb:
hostname: mariadb
image: mariadb:latest
ports:
- 3306:3306
environment:
- MARIADB_ROOT_PASSWORD=${DATABASE_PASSWORD}
- MARIADB_DATABASE=${DATABASE_DB}
- MARIADB_USER=${DATABASE_USER}
- MARIADB_PASSWORD=${DATABASE_PASSWORD}

View File

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

4
.github/release.yml vendored
View File

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

View File

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

View File

@@ -8,9 +8,14 @@ on:
jobs: jobs:
docker: docker:
runs-on: ubuntu-latest runs-on: ubuntu-latest
permissions:
packages: write
contents: read
attestations: write
id-token: write
steps: steps:
- name: Checkout - name: Checkout
uses: actions/checkout@v3 uses: actions/checkout@v4
with: with:
submodules: 'true' submodules: 'true'
- name: Install dotnet tool - name: Install dotnet tool
@@ -20,18 +25,39 @@ jobs:
- name: Sign in to Nuget - name: Sign in to Nuget
run: dotnet nuget add source --username michael-j-green --password ${{ secrets.NUGETKEY }} --store-password-in-clear-text --name github "https://nuget.pkg.github.com/gaseous-project/index.json" run: dotnet nuget add source --username michael-j-green --password ${{ secrets.NUGETKEY }} --store-password-in-clear-text --name github "https://nuget.pkg.github.com/gaseous-project/index.json"
- name: Set up QEMU - name: Set up QEMU
uses: docker/setup-qemu-action@v2 uses: docker/setup-qemu-action@v3
- name: Set up Docker Buildx - name: Set up Docker Buildx
uses: docker/setup-buildx-action@v2 uses: docker/setup-buildx-action@v3
- name: Login to Docker Hub - name: Login to Docker Hub
uses: docker/login-action@v2 uses: docker/login-action@v3
with: with:
username: ${{ secrets.DOCKERHUB_USERNAME }} username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_TOKEN }} password: ${{ secrets.DOCKERHUB_TOKEN }}
- name: Build and push - name: Login to GitHub Package Registry
uses: docker/build-push-action@v4 uses: docker/login-action@v3
with:
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Build and push standard image
uses: docker/build-push-action@v6
with: with:
context: . context: .
platforms: linux/amd64 #,linux/arm64 file: ./build/Dockerfile
platforms: linux/amd64,linux/arm64
push: true push: true
tags: gaseousgames/gaseousserver:latest,gaseousgames/gaseousserver:${{ github.ref_name}} tags: |
gaseousgames/gaseousserver:latest
gaseousgames/gaseousserver:${{ github.ref_name}}
ghcr.io/gaseous-project/gaseousserver:latest
ghcr.io/gaseous-project/gaseousserver:${{ github.ref_name}}
- name: Build and push image with embedded mariadb
uses: docker/build-push-action@v6
with:
context: .
file: ./build/Dockerfile-EmbeddedDB
platforms: linux/amd64,linux/arm64
push: true
tags: |
gaseousgames/gaseousserver:${{ github.ref_name}}-embeddeddb
ghcr.io/gaseous-project/gaseousserver:${{ github.ref_name}}-embeddeddb

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

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

View File

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

3
.gitignore vendored
View File

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

7
.vscode/launch.json vendored
View File

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

View File

@@ -1,22 +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
# download and unzip EmulatorJS from CDN
RUN apt-get update && apt-get install -y p7zip-full
RUN mkdir -p out/wwwroot/emulators/EmulatorJS
RUN wget https://cdn.emulatorjs.org/releases/4.0.9.7z
RUN 7z x -y -oout/wwwroot/emulators/EmulatorJS 4.0.9.7z
# 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 # Visual Studio Version 16
VisualStudioVersion = 25.0.1704.4 VisualStudioVersion = 25.0.1704.4
MinimumVisualStudioVersion = 10.0.40219.1 MinimumVisualStudioVersion = 10.0.40219.1
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "gaseous-server", "gaseous-server\gaseous-server.csproj", "{A01D2EFF-C82E-473B-84D7-7C25E736F5D2}" Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "gaseous-server", "gaseous-server\gaseous-server.csproj", "{A01D2EFF-C82E-473B-84D7-7C25E736F5D2}"
EndProject EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{17FA6F12-8532-420C-9489-CB8FDE42137C}" Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{17FA6F12-8532-420C-9489-CB8FDE42137C}"
ProjectSection(SolutionItems) = preProject ProjectSection(SolutionItems) = preProject
@@ -21,36 +21,30 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "screenshots", "screenshots"
screenshots\Game.png = screenshots\Game.png screenshots\Game.png = screenshots\Game.png
EndProjectSection EndProjectSection
EndProject EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "gaseous-cli", "gaseous-cli\gaseous-cli.csproj", "{419CC4E4-8932-4E4A-B027-5521AA0CBA85}"
EndProject
Global Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU Debug|Any CPU = Debug|Any CPU
Release|Any CPU = Release|Any CPU Release|Any CPU = Release|Any CPU
EndGlobalSection EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution GlobalSection(ProjectConfigurationPlatforms) = postSolution
{08699C93-15CD-4E39-9053-D3C8056CE938}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{08699C93-15CD-4E39-9053-D3C8056CE938}.Debug|Any CPU.Build.0 = Debug|Any CPU
{08699C93-15CD-4E39-9053-D3C8056CE938}.Release|Any CPU.ActiveCfg = Release|Any CPU
{08699C93-15CD-4E39-9053-D3C8056CE938}.Release|Any CPU.Build.0 = Release|Any CPU
{FFCEC386-033F-4772-A45B-D33579F2E5EE}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{FFCEC386-033F-4772-A45B-D33579F2E5EE}.Debug|Any CPU.Build.0 = Debug|Any CPU
{FFCEC386-033F-4772-A45B-D33579F2E5EE}.Release|Any CPU.ActiveCfg = Release|Any CPU
{FFCEC386-033F-4772-A45B-D33579F2E5EE}.Release|Any CPU.Build.0 = Release|Any CPU
{A01D2EFF-C82E-473B-84D7-7C25E736F5D2}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {A01D2EFF-C82E-473B-84D7-7C25E736F5D2}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{A01D2EFF-C82E-473B-84D7-7C25E736F5D2}.Debug|Any CPU.Build.0 = Debug|Any CPU {A01D2EFF-C82E-473B-84D7-7C25E736F5D2}.Debug|Any CPU.Build.0 = Debug|Any CPU
{A01D2EFF-C82E-473B-84D7-7C25E736F5D2}.Release|Any CPU.ActiveCfg = Release|Any CPU {A01D2EFF-C82E-473B-84D7-7C25E736F5D2}.Release|Any CPU.ActiveCfg = Release|Any CPU
{A01D2EFF-C82E-473B-84D7-7C25E736F5D2}.Release|Any CPU.Build.0 = Release|Any CPU {A01D2EFF-C82E-473B-84D7-7C25E736F5D2}.Release|Any CPU.Build.0 = Release|Any CPU
{B07A4655-A003-416B-A790-ADAA5B548E1A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {419CC4E4-8932-4E4A-B027-5521AA0CBA85}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{B07A4655-A003-416B-A790-ADAA5B548E1A}.Debug|Any CPU.Build.0 = Debug|Any CPU {419CC4E4-8932-4E4A-B027-5521AA0CBA85}.Debug|Any CPU.Build.0 = Debug|Any CPU
{B07A4655-A003-416B-A790-ADAA5B548E1A}.Release|Any CPU.ActiveCfg = Release|Any CPU {419CC4E4-8932-4E4A-B027-5521AA0CBA85}.Release|Any CPU.ActiveCfg = Release|Any CPU
{B07A4655-A003-416B-A790-ADAA5B548E1A}.Release|Any CPU.Build.0 = Release|Any CPU {419CC4E4-8932-4E4A-B027-5521AA0CBA85}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection EndGlobalSection
GlobalSection(SolutionProperties) = preSolution GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE HideSolutionNode = FALSE
EndGlobalSection EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {979BF092-AFBC-4F19-B55F-32E73959BD1A}
EndGlobalSection
GlobalSection(NestedProjects) = preSolution GlobalSection(NestedProjects) = preSolution
{F1A847C7-57BC-4DA9-8F83-CD060A7F5122} = {17FA6F12-8532-420C-9489-CB8FDE42137C} {F1A847C7-57BC-4DA9-8F83-CD060A7F5122} = {17FA6F12-8532-420C-9489-CB8FDE42137C}
EndGlobalSection EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {979BF092-AFBC-4F19-B55F-32E73959BD1A}
EndGlobalSection
EndGlobal EndGlobal

149
LICENSE
View File

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

194
README.MD
View File

@@ -1,186 +1,42 @@
# Gaseous Server [![.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) [![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)
# <img src="./logo.png" height="28" style="float: right;" /> Gaseous Server
This is the server for the Gaseous system. It offers ROM and title management, as well as some basic in browser emulation of those ROMs. This is the server for the Gaseous system. It offers ROM and title management, as well as some basic in browser emulation of those ROMs.
## Warning Version 1.7.0 and later contain user authentication, and can be exposed to the internet. However, it is recommended to not 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.
This project is currently not suitable for being exposed to the internet. 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**.
1. there is currently 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**.
## Screenshots ## Screenshots
![Library](./screenshots/Library.png) ![Library](./gaseous-server/wwwroot/screenshots/Library.png)
![Game](./screenshots/Game.png) ![Game](./gaseous-server/wwwroot/screenshots/Game.png)
![Emulator](./screenshots/Emulator.png) ![Emulator](./gaseous-server/wwwroot/screenshots/Emulator.png)
## Requirements ## Requirements
* MariaDB 11.1.2 or MySQL Server 8+ * MariaDB 11.1.2+ (preferred) or MySQL Server 8+
* These are the database versions Gaseous has been tested and developed against. Your mileage may vary with earlier versions. * These are the database versions Gaseous has been tested and developed against. Your mileage may vary with earlier versions.
* Currently MariaDB is the preferred database server, while MySQL will continue to be supported for existing users (they should be interchangable). * MariaDB is the preferred database server, while MySQL will continue to be supported for existing users (they should be interchangable).
* Note that due to the earlier database schema using MySQL specific features, moving to MariaDB from MySQL will require rebuilding your database from scratch. The "Library Scan" background task can be used to re-import all titles. * Note that due to the earlier database schema using MySQL specific features, moving to MariaDB from MySQL will require rebuilding your database from scratch. The "Library Scan" background task can be used to re-import all titles.
* Internet Game Database API Key. See: https://api-docs.igdb.com/#account-creation * Internet Game Database API Key. See: https://api-docs.igdb.com/#account-creation
## Third Party Projects # Installation
The following projects are used by Gaseous See https://github.com/gaseous-project/gaseous-server/wiki/Installation for installation instructions.
* 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
## 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. Open the docker-compose-{database}-build.yml file and edit the igdbclientid and igdbclientsecret to the values retrieved from your IGDB account
4. Run the command ```docker-compose --file docker-compose-{database}-build.yml up -d```
5. Connect to the host on port 5198
## Source
### Build and deploy
1. Install and configure a MariaDB or MySQL instance - this is beyond the scope of this document
2. Install the dotnet 7.0 packages appropriate for your operating system
* See: https://learn.microsoft.com/en-us/dotnet/core/install/linux
3. Create a database user with permission to create a databse. Gaseous will create the new database and apply the database schema on it's first startup.
4. Clone the repo with ```git clone https://github.com/gaseous-project/gaseous-server.git```
5. Change into the gaseous-server directory
6. As the main branch is the development branch, you might want to change to a stable version - these are tagged with a version number. For example to change to the 1.5.0 release, use the command ```git checkout v1.5.0```
* Check the releases page for the version you would like to run: https://github.com/gaseous-project/gaseous-server/releases
7. Download the emulator files from ```https://cdn.emulatorjs.org/releases/4.0.9.zip``` and extract the files to ```gaseous-server/wwwroot/emulators/EmulatorJS```
8. Create a directory in the home directory of the user that will run the server. For example, if running as the user ```gaseous```, create the directory ```/home/gaseous/.gaseous-server```
9. Change into the ```.gaseous-server``` directory created in the previous step
10. Copy the JSON from the config file above into a new file named ```config.json```
11. Update the database section with the database server hostname, username, password, and port
12. Compile the server by changing back to the repo cloned earlier and executing:
* ```dotnet restore "gaseous-server/gaseous-server.csproj"```
* ```dotnet publish "gaseous-server/gaseous-server.csproj" --use-current-runtime --self-contained false -c Release -o <output directory>```
* replace ```<output directory>``` with the directory of your choosing. The compiled application will be copied there. For this example we'll use ```/opt/gaseous-server```
13. The server can then be started by executing ```dotnet /opt/gaseous-server/gaseous-server.dll```
* If you would like the server to run on a different ip address and port (for example 0.0.0.0:8080), add the --urls argument: ```dotnet /opt/gaseous-server/gaseous-server.dll --urls http://0.0.0.0:8080```
**Note**: The above instructions were tested on macOS Ventura, and Ubuntu 22.04.3. There was a report that Debian 11 had an issue with the git submodule commands (see: https://github.com/gaseous-project/gaseous-server/issues/71). This was possibly due to an older git package.
### Updating from source
1. Stop the server
2. Switch to the source directory
3. Update your repo:
* If running from the main branch, run ```git pull``` to update the repo
* If running from another branch or tag, run:
* ```git fetch```
* ```git checkout <branch or tag name>```
4. Run steps 12 and 13 from the above Build guide
# Adding Content # Adding Content
While games can be added to the server without them, it is recommended adding some signature DAT files beforehand to allow for better matching of ROMs to games. 1. Import signatures: see https://github.com/gaseous-project/gaseous-server/wiki/Signatures
2. Add ROMs: see https://github.com/gaseous-project/gaseous-server/wiki/Adding-ROMs
These signature DAT files contain a list of titles with hashes for many of the ROM images that have been found by the community. ## 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
Currently supported DAT's: ## Third Party Projects
* TOSEC: https://www.tosecdev.org/downloads/category/56-2023-01-23 The following projects are used by Gaseous
* MAME Arcade and MAME Mess: https://www.progettosnaps.net/dats/MAME * [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)
If there are other DAT's you'd like to see support for, please raise an issue with a link to the DAT's. ## Discord Server
Join our Discord server: https://discord.gg/Nhu7wpT3k4
## 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.

73
build/Dockerfile Normal file
View File

@@ -0,0 +1,73 @@
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 .. ./
# Build Gaseous Web Server
# 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
# Build Gaseous CLI
# Restore as distinct layers
RUN dotnet restore "gaseous-cli/gaseous-cli.csproj" -a $TARGETARCH
# Build and publish a release
RUN dotnet publish "gaseous-cli/gaseous-cli.csproj" --use-current-runtime --self-contained true -c Release -o out -a $TARGETARCH
# update apt-get
RUN apt-get update
# # download and unzip EmulatorJS from CDN
RUN apt-get install -y p7zip-full
RUN mkdir -p out/wwwroot/emulators/EmulatorJS
RUN wget https://cdn.emulatorjs.org/releases/4.0.12.7z
RUN 7z x -y -oout/wwwroot/emulators/EmulatorJS 4.0.12.7z
# 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 .
# variables
ARG PUID=1000
ARG PGID=1000
ARG dbhost=localhost
ARG dbuser=root
ARG dbpass=gaseous
ENV PUID=${PUID}
ENV PGID=${PGID}
ENV dbhost=${dbhost}
ENV dbuser=${dbuser}
ENV dbpass=${dbpass}
# install supervisord
RUN apt-get update && apt-get install -y supervisor
COPY ../build/standard/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/standard/entrypoint.sh /usr/sbin/entrypoint.sh
RUN chmod +x /usr/sbin/entrypoint.sh
# volumes
VOLUME /home/gaseous/.gaseous-server
# start services
ENTRYPOINT [ "/usr/sbin/entrypoint.sh" ]

View File

@@ -0,0 +1,79 @@
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 .. ./
# Build Gaseous Web Server
# 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
# Build Gaseous CLI
# Restore as distinct layers
RUN dotnet restore "gaseous-cli/gaseous-cli.csproj" -a $TARGETARCH
# Build and publish a release
RUN dotnet publish "gaseous-cli/gaseous-cli.csproj" --use-current-runtime --self-contained true -c Release -o out -a $TARGETARCH
# update apt-get
RUN apt-get update
# # download and unzip EmulatorJS from CDN
RUN apt-get install -y p7zip-full
RUN mkdir -p out/wwwroot/emulators/EmulatorJS
RUN wget https://cdn.emulatorjs.org/releases/4.0.12.7z
RUN 7z x -y -oout/wwwroot/emulators/EmulatorJS 4.0.12.7z
# 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/embeddeddb/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/embeddeddb/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/embeddeddb/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" ]

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -4,6 +4,7 @@ services:
container_name: gaseous-server container_name: gaseous-server
build: build:
context: ./ context: ./
dockerfile: ./build/Dockerfile
restart: unless-stopped restart: unless-stopped
networks: networks:
- gaseous - gaseous
@@ -12,8 +13,9 @@ services:
ports: ports:
- 5198:80 - 5198:80
volumes: volumes:
- gs:/root/.gaseous-server - gs:/home/gaseous/.gaseous-server
environment: environment:
- TZ=Australia/Sydney
- dbhost=gsdb - dbhost=gsdb
- dbuser=root - dbuser=root
- dbpass=gaseous - dbpass=gaseous

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: mariadb
restart: unless-stopped
networks:
- gaseous
volumes:
- gsdb:/var/lib/mysql
environment:
- MARIADB_ROOT_PASSWORD=gaseous
- MARIADB_USER=gaseous
- MARIADB_PASSWORD=gaseous
networks:
gaseous:
driver: bridge
volumes:
gs:
gsdb:

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:

354
gaseous-cli/Program.cs Normal file
View File

@@ -0,0 +1,354 @@
using System;
using Authentication;
using gaseous_server.Classes;
using Microsoft.AspNetCore.Identity;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
/* ------------------------------------------------- */
/* This tool is a CLI tool that is used to manage */
/* the Gaseous Server. */
/* Functions such as user management, and backups */
/* are available. */
/* ------------------------------------------------- */
// load app settings
Config.InitSettings();
// set up database connection
Database db = new Database(Database.databaseType.MySql, Config.DatabaseConfiguration.ConnectionString);
// set up identity
IServiceCollection services = new ServiceCollection();
services.AddLogging();
services.AddIdentity<ApplicationUser, ApplicationRole>(options =>
{
options.Password.RequireDigit = true;
options.Password.RequireLowercase = true;
options.Password.RequireNonAlphanumeric = false;
options.Password.RequireUppercase = true;
options.Password.RequiredLength = 10;
options.User.AllowedUserNameCharacters = null;
options.User.RequireUniqueEmail = true;
options.SignIn.RequireConfirmedPhoneNumber = false;
options.SignIn.RequireConfirmedEmail = false;
options.SignIn.RequireConfirmedAccount = false;
})
.AddUserStore<UserStore>()
.AddRoleStore<RoleStore>()
;
services.AddScoped<UserStore>();
services.AddScoped<RoleStore>();
services.AddTransient<IUserStore<ApplicationUser>, UserStore>();
services.AddTransient<IRoleStore<ApplicationRole>, RoleStore>();
var userManager = services.BuildServiceProvider().GetService<UserManager<ApplicationUser>>();
// load the command line arguments
string[] cmdArgs = Environment.GetCommandLineArgs();
// check if the user has entered any arguments
if (cmdArgs.Length == 1)
{
// no arguments were entered
Console.WriteLine("Gaseous CLI - A tool for managing the Gaseous Server");
Console.WriteLine("Usage: gaseous-cli [command] [options]");
Console.WriteLine("Commands:");
Console.WriteLine(" user [command] [options] - Manage users");
Console.WriteLine(" role [command] [options] - Manage roles");
// Console.WriteLine(" backup [command] [options] - Manage backups");
// Console.WriteLine(" restore [command] [options] - Restore backups");
Console.WriteLine(" help - Display this help message");
return;
}
// check if the user has entered the help command
if (cmdArgs[1] == "help")
{
// display the help message
Console.WriteLine("Gaseous CLI - A tool for managing the Gaseous Server");
Console.WriteLine("Usage: gaseous-cli [command] [options]");
Console.WriteLine("Commands:");
Console.WriteLine(" user [command] [options] - Manage users");
Console.WriteLine(" role [command] [options] - Manage roles");
// Console.WriteLine(" backup [command] [options] - Manage backups");
// Console.WriteLine(" restore [command] [options] - Restore backups");
Console.WriteLine(" help - Display this help message");
return;
}
// check if the user has entered the user command
if (cmdArgs[1] == "user")
{
// check if the user has entered any arguments
if (cmdArgs.Length == 2)
{
// no arguments were entered
Console.WriteLine("User Management");
Console.WriteLine("Usage: gaseous-cli user [command] [options]");
Console.WriteLine("Commands:");
Console.WriteLine(" add [username] [password] - Add a new user");
Console.WriteLine(" delete [username] - Delete a user");
Console.WriteLine(" resetpassword [username] [password] - Reset a user's password");
Console.WriteLine(" list - List all users");
return;
}
// check if the user has entered the add command
if (cmdArgs[2] == "add")
{
// check if the user has entered the username and password
if (cmdArgs.Length < 5)
{
// the username and password were not entered
Console.WriteLine("Error: Please enter a username and password");
return;
}
// add a new user
UserTable<ApplicationUser> userTable = new UserTable<ApplicationUser>(db);
if (userTable.GetUserByEmail(cmdArgs[3]) != null)
{
Console.WriteLine("Error: User already exists");
return;
}
// create the user object
ApplicationUser user = new ApplicationUser
{
Id = Guid.NewGuid().ToString(),
Email = cmdArgs[3],
NormalizedEmail = cmdArgs[3].ToUpper(),
EmailConfirmed = true,
UserName = cmdArgs[3],
NormalizedUserName = cmdArgs[3].ToUpper()
};
// create the password
PasswordHasher<ApplicationUser> passwordHasher = new PasswordHasher<ApplicationUser>();
user.PasswordHash = passwordHasher.HashPassword(user, cmdArgs[4]);
await userManager.CreateAsync(user);
await userManager.AddToRoleAsync(user, "Player");
Console.WriteLine("User created successfully with default role: Player");
return;
}
// check if the user has entered the delete command
if (cmdArgs[2] == "delete")
{
// check if the user has entered the username
if (cmdArgs.Length < 4)
{
// the username was not entered
Console.WriteLine("Error: Please enter a username");
return;
}
// delete the user
UserTable<ApplicationUser> userTable = new UserTable<ApplicationUser>(db);
ApplicationUser user = userTable.GetUserByEmail(cmdArgs[3]);
if (user == null)
{
Console.WriteLine("Error: User not found");
return;
}
await userManager.DeleteAsync(user);
Console.WriteLine("User deleted successfully");
return;
}
// check if the user has entered the resetpassword command
if (cmdArgs[2] == "resetpassword")
{
// check if the user has entered the username and password
if (cmdArgs.Length < 5)
{
// the username and password were not entered
Console.WriteLine("Error: Please enter a username and password");
return;
}
// reset the user's password
UserTable<ApplicationUser> userTable = new UserTable<ApplicationUser>(db);
ApplicationUser user = userTable.GetUserByEmail(cmdArgs[3]);
if (user == null)
{
Console.WriteLine("Error: User not found");
return;
}
// create the password
PasswordHasher<ApplicationUser> passwordHasher = new PasswordHasher<ApplicationUser>();
user.PasswordHash = passwordHasher.HashPassword(user, cmdArgs[4]);
await userManager.UpdateAsync(user);
Console.WriteLine("Password reset successfully");
return;
}
// check if the user has entered the list command
if (cmdArgs[2] == "list")
{
// list all users
UserTable<ApplicationUser> userTable = new UserTable<ApplicationUser>(db);
var userList = userTable.GetUsers();
foreach (var user in userList)
{
var roles = await userManager.GetRolesAsync(user);
Console.WriteLine(user.Email + " - " + string.Join(", ", roles));
}
return;
}
}
// check if the user has entered the role command
if (cmdArgs[1] == "role")
{
// check if the user has entered any arguments
if (cmdArgs.Length == 2)
{
// no arguments were entered
Console.WriteLine("Role Management");
Console.WriteLine("Usage: gaseous-cli role [command] [options]");
Console.WriteLine("Commands:");
Console.WriteLine(" set [username] [role] - Set the role of a user");
Console.WriteLine(" list - List all roles");
return;
}
// check if the user has entered the role command
if (cmdArgs[2] == "set")
{
// check if the user has entered the username and role
if (cmdArgs.Length < 5)
{
// the username and role were not entered
Console.WriteLine("Error: Please enter a username and role");
return;
}
// set the role of the user
UserTable<ApplicationUser> userTable = new UserTable<ApplicationUser>(db);
ApplicationUser user = userTable.GetUserByEmail(cmdArgs[3]);
if (user == null)
{
Console.WriteLine("Error: User not found");
return;
}
// remove all existing roles from user
var roles = await userManager.GetRolesAsync(user);
await userManager.RemoveFromRolesAsync(user, roles.ToArray());
// add the new role to the user
await userManager.AddToRoleAsync(user, cmdArgs[4]);
Console.WriteLine("Role set successfully");
return;
}
// check if the user has entered the list command
if (cmdArgs[2] == "list")
{
// list all roles
string[] roles = { "Player", "Gamer", "Admin" };
foreach (var role in roles)
{
Console.WriteLine(role);
}
return;
}
}
// // check if the user has entered the backup command
// if (cmdArgs[1] == "backup")
// {
// // check if the user has entered any arguments
// if (cmdArgs.Length == 2)
// {
// // no arguments were entered
// Console.WriteLine("Backup Management");
// Console.WriteLine("Usage: gaseous-cli backup [command] [options]");
// Console.WriteLine("Commands:");
// Console.WriteLine(" create - Create a backup");
// Console.WriteLine(" list - List all backups");
// Console.WriteLine(" remove [backup_id] - Remove a backup");
// return;
// }
// // check if the user has entered the create command
// if (cmdArgs[2] == "create")
// {
// // create a backup
// Backup.CreateBackup();
// return;
// }
// // check if the user has entered the list command
// if (cmdArgs[2] == "list")
// {
// // list all backups
// Backup.ListBackups();
// return;
// }
// // check if the user has entered the remove command
// if (cmdArgs[2] == "remove")
// {
// // check if the user has entered the backup id
// if (cmdArgs.Length < 4)
// {
// // the backup id was not entered
// Console.WriteLine("Error: Please enter a backup id");
// return;
// }
// // remove the backup
// Backup.RemoveBackup(cmdArgs[3]);
// return;
// }
// }
// // check if the user has entered the restore command
// if (cmdArgs[1] == "restore")
// {
// // check if the user has entered any arguments
// if (cmdArgs.Length == 2)
// {
// // no arguments were entered
// Console.WriteLine("Restore Management");
// Console.WriteLine("Usage: gaseous-cli restore [command] [options]");
// Console.WriteLine("Commands:");
// Console.WriteLine(" restore [backup_id] - Restore a backup");
// return;
// }
// // check if the user has entered the restore command
// if (cmdArgs[2] == "restore")
// {
// // check if the user has entered the backup id
// if (cmdArgs.Length < 4)
// {
// // the backup id was not entered
// Console.WriteLine("Error: Please enter a backup id");
// return;
// }
// // restore the backup
// Restore.RestoreBackup(cmdArgs[3]);
// return;
// }
// }
// the user entered an invalid command
Console.WriteLine("Error: Invalid command");

View File

@@ -0,0 +1,28 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net8.0</TargetFramework>
<RootNamespace>gaseous_cli</RootNamespace>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
<WarningLevel>4</WarningLevel>
<DocumentationFile>bin\Debug\net8.0\gaseous-cli.xml</DocumentationFile>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
<WarningLevel>4</WarningLevel>
<DocumentationFile>bin\Release\net8.0\gaseous-cli.xml</DocumentationFile>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Newtonsoft.Json" Version="13.0.3" />
<PackageReference Include="MySqlConnector" Version="2.3.7" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\gaseous-server\gaseous-server.csproj" />
</ItemGroup>
</Project>

Binary file not shown.

View File

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

View File

@@ -9,7 +9,7 @@ namespace Authentication
/// <summary> /// <summary>
/// Class that represents the Role table in the MySQL Database /// Class that represents the Role table in the MySQL Database
/// </summary> /// </summary>
public class RoleTable public class RoleTable
{ {
private Database _database; private Database _database;
@@ -63,7 +63,7 @@ namespace Authentication
parameters.Add("@id", roleId); parameters.Add("@id", roleId);
DataTable table = _database.ExecuteCMD(commandText, parameters); DataTable table = _database.ExecuteCMD(commandText, parameters);
if (table.Rows.Count == 0) if (table.Rows.Count == 0)
{ {
return null; return null;
@@ -104,7 +104,7 @@ namespace Authentication
var roleName = GetRoleName(roleId); var roleName = GetRoleName(roleId);
ApplicationRole? role = null; ApplicationRole? role = null;
if(roleName != null) if (roleName != null)
{ {
role = new ApplicationRole(); role = new ApplicationRole();
role.Id = roleId; role.Id = roleId;
@@ -153,7 +153,7 @@ namespace Authentication
string commandText = "Select Name from Roles"; string commandText = "Select Name from Roles";
var rows = _database.ExecuteCMDDict(commandText); var rows = _database.ExecuteCMDDict(commandText);
foreach(Dictionary<string, object> row in rows) foreach (Dictionary<string, object> row in rows)
{ {
ApplicationRole role = (ApplicationRole)Activator.CreateInstance(typeof(ApplicationRole)); ApplicationRole role = (ApplicationRole)Activator.CreateInstance(typeof(ApplicationRole));
role.Id = (string)row["Id"]; role.Id = (string)row["Id"];

View File

@@ -82,7 +82,7 @@ namespace Authentication
parameters.Add("providerKey", userLogin.ProviderKey); parameters.Add("providerKey", userLogin.ProviderKey);
DataTable table = _database.ExecuteCMD(commandText, parameters); DataTable table = _database.ExecuteCMD(commandText, parameters);
if (table.Rows.Count == 0) if (table.Rows.Count == 0)
{ {
return null; return null;

View File

@@ -35,7 +35,7 @@ namespace Authentication
parameters.Add("@userId", userId); parameters.Add("@userId", userId);
var rows = _database.ExecuteCMD(commandText, parameters).Rows; var rows = _database.ExecuteCMD(commandText, parameters).Rows;
foreach(DataRow row in rows) foreach (DataRow row in rows)
{ {
roles.Add((string)row["Name"]); roles.Add((string)row["Name"]);
} }

View File

@@ -10,7 +10,7 @@ namespace Authentication
/// Class that represents the Users table in the MySQL Database /// Class that represents the Users table in the MySQL Database
/// </summary> /// </summary>
public class UserTable<TUser> public class UserTable<TUser>
where TUser :ApplicationUser where TUser : ApplicationUser
{ {
private Database _database; private Database _database;
@@ -34,7 +34,7 @@ namespace Authentication
Dictionary<string, object> parameters = new Dictionary<string, object>() { { "@id", userId } }; Dictionary<string, object> parameters = new Dictionary<string, object>() { { "@id", userId } };
DataTable table = _database.ExecuteCMD(commandText, parameters); DataTable table = _database.ExecuteCMD(commandText, parameters);
if (table.Rows.Count == 0) if (table.Rows.Count == 0)
{ {
return null; return null;
@@ -75,7 +75,7 @@ namespace Authentication
public TUser GetUserById(string userId) public TUser GetUserById(string userId)
{ {
TUser user = null; TUser user = null;
string commandText = "Select * from Users where Id = @id"; string commandText = "Select * from Users LEFT JOIN (SELECT Id As ProfileId, UserId FROM UserProfiles) UserProfiles ON Users.Id = UserProfiles.UserId where Id = @id";
Dictionary<string, object> parameters = new Dictionary<string, object>() { { "@id", userId } }; Dictionary<string, object> parameters = new Dictionary<string, object>() { { "@id", userId } };
var rows = _database.ExecuteCMDDict(commandText, parameters); var rows = _database.ExecuteCMDDict(commandText, parameters);
@@ -89,7 +89,7 @@ namespace Authentication
user.SecurityStamp = (string?)(string.IsNullOrEmpty((string?)row["SecurityStamp"]) ? null : row["SecurityStamp"]); user.SecurityStamp = (string?)(string.IsNullOrEmpty((string?)row["SecurityStamp"]) ? null : row["SecurityStamp"]);
user.ConcurrencyStamp = (string?)(string.IsNullOrEmpty((string?)row["ConcurrencyStamp"]) ? null : row["ConcurrencyStamp"]); user.ConcurrencyStamp = (string?)(string.IsNullOrEmpty((string?)row["ConcurrencyStamp"]) ? null : row["ConcurrencyStamp"]);
user.Email = (string?)(string.IsNullOrEmpty((string?)row["Email"]) ? null : row["Email"]); user.Email = (string?)(string.IsNullOrEmpty((string?)row["Email"]) ? null : row["Email"]);
user.EmailConfirmed = row["EmailConfirmed"] == "1" ? true:false; user.EmailConfirmed = row["EmailConfirmed"] == "1" ? true : false;
user.PhoneNumber = (string?)(string.IsNullOrEmpty((string?)row["PhoneNumber"]) ? null : row["PhoneNumber"]); user.PhoneNumber = (string?)(string.IsNullOrEmpty((string?)row["PhoneNumber"]) ? null : row["PhoneNumber"]);
user.PhoneNumberConfirmed = row["PhoneNumberConfirmed"] == "1" ? true : false; user.PhoneNumberConfirmed = row["PhoneNumberConfirmed"] == "1" ? true : false;
user.NormalizedEmail = (string?)(string.IsNullOrEmpty((string?)row["NormalizedEmail"]) ? null : row["NormalizedEmail"]); user.NormalizedEmail = (string?)(string.IsNullOrEmpty((string?)row["NormalizedEmail"]) ? null : row["NormalizedEmail"]);
@@ -97,9 +97,10 @@ namespace Authentication
user.LockoutEnabled = row["LockoutEnabled"] == "1" ? true : false; user.LockoutEnabled = row["LockoutEnabled"] == "1" ? true : false;
user.LockoutEnd = string.IsNullOrEmpty((string?)row["LockoutEnd"]) ? DateTime.Now : DateTime.Parse((string?)row["LockoutEnd"]); user.LockoutEnd = string.IsNullOrEmpty((string?)row["LockoutEnd"]) ? DateTime.Now : DateTime.Parse((string?)row["LockoutEnd"]);
user.AccessFailedCount = string.IsNullOrEmpty((string?)row["AccessFailedCount"]) ? 0 : int.Parse((string?)row["AccessFailedCount"]); user.AccessFailedCount = string.IsNullOrEmpty((string?)row["AccessFailedCount"]) ? 0 : int.Parse((string?)row["AccessFailedCount"]);
user.TwoFactorEnabled = row["TwoFactorEnabled"] == "1" ? true:false; user.TwoFactorEnabled = row["TwoFactorEnabled"] == "1" ? true : false;
user.SecurityProfile = GetSecurityProfile(user); user.SecurityProfile = GetSecurityProfile(user);
user.UserPreferences = GetPreferences(user); user.UserPreferences = GetPreferences(user);
user.ProfileId = string.IsNullOrEmpty((string?)row["ProfileId"]) ? Guid.Empty : Guid.Parse((string?)row["ProfileId"]);
} }
return user; return user;
@@ -113,11 +114,11 @@ namespace Authentication
public List<TUser> GetUserByName(string normalizedUserName) public List<TUser> GetUserByName(string normalizedUserName)
{ {
List<TUser> users = new List<TUser>(); List<TUser> users = new List<TUser>();
string commandText = "Select * from Users where NormalizedEmail = @name"; string commandText = "Select * from Users LEFT JOIN (SELECT Id As ProfileId, UserId FROM UserProfiles) UserProfiles ON Users.Id = UserProfiles.UserId where NormalizedEmail = @name";
Dictionary<string, object> parameters = new Dictionary<string, object>() { { "@name", normalizedUserName } }; Dictionary<string, object> parameters = new Dictionary<string, object>() { { "@name", normalizedUserName } };
var rows = _database.ExecuteCMDDict(commandText, parameters); var rows = _database.ExecuteCMDDict(commandText, parameters);
foreach(Dictionary<string, object> row in rows) foreach (Dictionary<string, object> row in rows)
{ {
TUser user = (TUser)Activator.CreateInstance(typeof(TUser)); TUser user = (TUser)Activator.CreateInstance(typeof(TUser));
user.Id = (string)row["Id"]; user.Id = (string)row["Id"];
@@ -126,7 +127,7 @@ namespace Authentication
user.SecurityStamp = (string?)(string.IsNullOrEmpty((string?)row["SecurityStamp"]) ? null : row["SecurityStamp"]); user.SecurityStamp = (string?)(string.IsNullOrEmpty((string?)row["SecurityStamp"]) ? null : row["SecurityStamp"]);
user.ConcurrencyStamp = (string?)(string.IsNullOrEmpty((string?)row["ConcurrencyStamp"]) ? null : row["ConcurrencyStamp"]); user.ConcurrencyStamp = (string?)(string.IsNullOrEmpty((string?)row["ConcurrencyStamp"]) ? null : row["ConcurrencyStamp"]);
user.Email = (string?)(string.IsNullOrEmpty((string?)row["Email"]) ? null : row["Email"]); user.Email = (string?)(string.IsNullOrEmpty((string?)row["Email"]) ? null : row["Email"]);
user.EmailConfirmed = row["EmailConfirmed"] == "1" ? true:false; user.EmailConfirmed = row["EmailConfirmed"] == "1" ? true : false;
user.PhoneNumber = (string?)(string.IsNullOrEmpty((string?)row["PhoneNumber"]) ? null : row["PhoneNumber"]); user.PhoneNumber = (string?)(string.IsNullOrEmpty((string?)row["PhoneNumber"]) ? null : row["PhoneNumber"]);
user.PhoneNumberConfirmed = row["PhoneNumberConfirmed"] == "1" ? true : false; user.PhoneNumberConfirmed = row["PhoneNumberConfirmed"] == "1" ? true : false;
user.NormalizedEmail = (string?)(string.IsNullOrEmpty((string?)row["NormalizedEmail"]) ? null : row["NormalizedEmail"]); user.NormalizedEmail = (string?)(string.IsNullOrEmpty((string?)row["NormalizedEmail"]) ? null : row["NormalizedEmail"]);
@@ -134,9 +135,10 @@ namespace Authentication
user.LockoutEnabled = row["LockoutEnabled"] == "1" ? true : false; user.LockoutEnabled = row["LockoutEnabled"] == "1" ? true : false;
user.LockoutEnd = string.IsNullOrEmpty((string?)row["LockoutEnd"]) ? DateTime.Now : DateTime.Parse((string?)row["LockoutEnd"]); user.LockoutEnd = string.IsNullOrEmpty((string?)row["LockoutEnd"]) ? DateTime.Now : DateTime.Parse((string?)row["LockoutEnd"]);
user.AccessFailedCount = string.IsNullOrEmpty((string?)row["AccessFailedCount"]) ? 0 : int.Parse((string?)row["AccessFailedCount"]); user.AccessFailedCount = string.IsNullOrEmpty((string?)row["AccessFailedCount"]) ? 0 : int.Parse((string?)row["AccessFailedCount"]);
user.TwoFactorEnabled = row["TwoFactorEnabled"] == "1" ? true:false; user.TwoFactorEnabled = row["TwoFactorEnabled"] == "1" ? true : false;
user.SecurityProfile = GetSecurityProfile(user); user.SecurityProfile = GetSecurityProfile(user);
user.UserPreferences = GetPreferences(user); user.UserPreferences = GetPreferences(user);
user.ProfileId = string.IsNullOrEmpty((string?)row["ProfileId"]) ? Guid.Empty : Guid.Parse((string?)row["ProfileId"]);
users.Add(user); users.Add(user);
} }
@@ -146,10 +148,10 @@ namespace Authentication
public List<TUser> GetUsers() public List<TUser> GetUsers()
{ {
List<TUser> users = new List<TUser>(); List<TUser> users = new List<TUser>();
string commandText = "Select * from Users order by NormalizedUserName"; string commandText = "Select * from Users LEFT JOIN (SELECT Id As ProfileId, UserId FROM UserProfiles) UserProfiles ON Users.Id = UserProfiles.UserId order by NormalizedUserName";
var rows = _database.ExecuteCMDDict(commandText); var rows = _database.ExecuteCMDDict(commandText);
foreach(Dictionary<string, object> row in rows) foreach (Dictionary<string, object> row in rows)
{ {
TUser user = (TUser)Activator.CreateInstance(typeof(TUser)); TUser user = (TUser)Activator.CreateInstance(typeof(TUser));
user.Id = (string)row["Id"]; user.Id = (string)row["Id"];
@@ -158,7 +160,7 @@ namespace Authentication
user.SecurityStamp = (string?)(string.IsNullOrEmpty((string?)row["SecurityStamp"]) ? null : row["SecurityStamp"]); user.SecurityStamp = (string?)(string.IsNullOrEmpty((string?)row["SecurityStamp"]) ? null : row["SecurityStamp"]);
user.ConcurrencyStamp = (string?)(string.IsNullOrEmpty((string?)row["ConcurrencyStamp"]) ? null : row["ConcurrencyStamp"]); user.ConcurrencyStamp = (string?)(string.IsNullOrEmpty((string?)row["ConcurrencyStamp"]) ? null : row["ConcurrencyStamp"]);
user.Email = (string?)(string.IsNullOrEmpty((string?)row["Email"]) ? null : row["Email"]); user.Email = (string?)(string.IsNullOrEmpty((string?)row["Email"]) ? null : row["Email"]);
user.EmailConfirmed = row["EmailConfirmed"] == "1" ? true:false; user.EmailConfirmed = row["EmailConfirmed"] == "1" ? true : false;
user.PhoneNumber = (string?)(string.IsNullOrEmpty((string?)row["PhoneNumber"]) ? null : row["PhoneNumber"]); user.PhoneNumber = (string?)(string.IsNullOrEmpty((string?)row["PhoneNumber"]) ? null : row["PhoneNumber"]);
user.PhoneNumberConfirmed = row["PhoneNumberConfirmed"] == "1" ? true : false; user.PhoneNumberConfirmed = row["PhoneNumberConfirmed"] == "1" ? true : false;
user.NormalizedEmail = (string?)(string.IsNullOrEmpty((string?)row["NormalizedEmail"]) ? null : row["NormalizedEmail"]); user.NormalizedEmail = (string?)(string.IsNullOrEmpty((string?)row["NormalizedEmail"]) ? null : row["NormalizedEmail"]);
@@ -166,9 +168,10 @@ namespace Authentication
user.LockoutEnabled = row["LockoutEnabled"] == "1" ? true : false; user.LockoutEnabled = row["LockoutEnabled"] == "1" ? true : false;
user.LockoutEnd = string.IsNullOrEmpty((string?)row["LockoutEnd"]) ? DateTime.Now : DateTime.Parse((string?)row["LockoutEnd"]); user.LockoutEnd = string.IsNullOrEmpty((string?)row["LockoutEnd"]) ? DateTime.Now : DateTime.Parse((string?)row["LockoutEnd"]);
user.AccessFailedCount = string.IsNullOrEmpty((string?)row["AccessFailedCount"]) ? 0 : int.Parse((string?)row["AccessFailedCount"]); user.AccessFailedCount = string.IsNullOrEmpty((string?)row["AccessFailedCount"]) ? 0 : int.Parse((string?)row["AccessFailedCount"]);
user.TwoFactorEnabled = row["TwoFactorEnabled"] == "1" ? true:false; user.TwoFactorEnabled = row["TwoFactorEnabled"] == "1" ? true : false;
user.SecurityProfile = GetSecurityProfile(user); user.SecurityProfile = GetSecurityProfile(user);
user.UserPreferences = GetPreferences(user); user.UserPreferences = GetPreferences(user);
user.ProfileId = string.IsNullOrEmpty((string?)row["ProfileId"]) ? Guid.Empty : Guid.Parse((string?)row["ProfileId"]);
users.Add(user); users.Add(user);
} }
@@ -255,10 +258,11 @@ namespace Authentication
/// <returns></returns> /// <returns></returns>
public int Insert(TUser user) public int Insert(TUser user)
{ {
string commandText = @"Insert into Users (UserName, Id, PasswordHash, SecurityStamp, ConcurrencyStamp, Email, EmailConfirmed, PhoneNumber, PhoneNumberConfirmed, NormalizedEmail, NormalizedUserName, AccessFailedCount, LockoutEnabled, LockoutEnd, TwoFactorEnabled) values (@name, @id, @pwdHash, @SecStamp, @concurrencystamp, @email ,@emailconfirmed ,@phonenumber, @phonenumberconfirmed, @normalizedemail, @normalizedusername, @accesscount, @lockoutenabled, @lockoutenddate, @twofactorenabled);"; string commandText = @"Insert into Users (UserName, Id, PasswordHash, SecurityStamp, ConcurrencyStamp, Email, EmailConfirmed, PhoneNumber, PhoneNumberConfirmed, NormalizedEmail, NormalizedUserName, AccessFailedCount, LockoutEnabled, LockoutEnd, TwoFactorEnabled) values (@name, @id, @pwdHash, @SecStamp, @concurrencystamp, @email ,@emailconfirmed ,@phonenumber, @phonenumberconfirmed, @normalizedemail, @normalizedusername, @accesscount, @lockoutenabled, @lockoutenddate, @twofactorenabled); Insert into UserProfiles (Id, UserId, DisplayName, Quip, UnstructuredData) values (@profileId, @id, @email, '', '{}');";
Dictionary<string, object> parameters = new Dictionary<string, object>(); Dictionary<string, object> parameters = new Dictionary<string, object>();
parameters.Add("@name", user.UserName); parameters.Add("@name", user.UserName);
parameters.Add("@id", user.Id); parameters.Add("@id", user.Id);
parameters.Add("@profileId", Guid.NewGuid());
parameters.Add("@pwdHash", user.PasswordHash); parameters.Add("@pwdHash", user.PasswordHash);
parameters.Add("@SecStamp", user.SecurityStamp); parameters.Add("@SecStamp", user.SecurityStamp);
parameters.Add("@concurrencystamp", user.ConcurrencyStamp); parameters.Add("@concurrencystamp", user.ConcurrencyStamp);
@@ -289,7 +293,7 @@ namespace Authentication
/// <returns></returns> /// <returns></returns>
private int Delete(string userId) private int Delete(string userId)
{ {
string commandText = "Delete from Users where Id = @userId; Delete from User_Settings where Id = @userId; Delete from GameState where UserId = @userId;"; string commandText = "Delete from Users where Id = @userId; Delete from User_Settings where Id = @userId; Delete from UserProfiles where UserId = @userId; Delete from GameState where UserId = @userId;";
Dictionary<string, object> parameters = new Dictionary<string, object>(); Dictionary<string, object> parameters = new Dictionary<string, object>();
parameters.Add("@userId", userId); parameters.Add("@userId", userId);
@@ -373,7 +377,7 @@ namespace Authentication
Dictionary<string, object> parameters = new Dictionary<string, object>(); Dictionary<string, object> parameters = new Dictionary<string, object>();
parameters.Add("Id", user.Id); parameters.Add("Id", user.Id);
parameters.Add("SecurityProfile", Newtonsoft.Json.JsonConvert.SerializeObject(securityProfile)); parameters.Add("SecurityProfile", Newtonsoft.Json.JsonConvert.SerializeObject(securityProfile));
return _database.ExecuteCMD(commandText, parameters).Rows.Count; return _database.ExecuteCMD(commandText, parameters).Rows.Count;
} }
@@ -405,7 +409,7 @@ namespace Authentication
List<UserPreferenceViewModel> userPreferences = GetPreferences(user); List<UserPreferenceViewModel> userPreferences = GetPreferences(user);
Database db = new Database(Database.databaseType.MySql, Config.DatabaseConfiguration.ConnectionString); Database db = new Database(Database.databaseType.MySql, Config.DatabaseConfiguration.ConnectionString);
foreach (UserPreferenceViewModel modelItem in model) foreach (UserPreferenceViewModel modelItem in model)
{ {
bool prefItemFound = false; bool prefItemFound = false;
@@ -437,5 +441,30 @@ namespace Authentication
return 0; return 0;
} }
} }
public Guid SetAvatar(TUser user, byte[] bytes)
{
Database db = new Database(Database.databaseType.MySql, Config.DatabaseConfiguration.ConnectionString);
string sql;
Dictionary<string, object> dbDict = new Dictionary<string, object>
{
{ "userid", user.Id }
};
if (bytes.Length == 0)
{
sql = "DELETE FROM UserAvatars WHERE UserId = @userid";
db.ExecuteNonQuery(sql, dbDict);
return Guid.Empty;
}
else
{
sql = "DELETE FROM UserAvatars WHERE UserId = @userid; INSERT INTO UserAvatars (UserId, Id, Avatar) VALUES (@userid, @id, @avatar);";
dbDict.Add("id", Guid.NewGuid());
dbDict.Add("avatar", bytes);
db.ExecuteNonQuery(sql, dbDict);
return (Guid)dbDict["id"];
}
}
} }
} }

View File

@@ -8,7 +8,9 @@ namespace Authentication
public List<String> Roles { get; set; } public List<String> Roles { get; set; }
public SecurityProfileViewModel SecurityProfile { get; set; } public SecurityProfileViewModel SecurityProfile { get; set; }
public List<UserPreferenceViewModel> UserPreferences { get; set; } public List<UserPreferenceViewModel> UserPreferences { get; set; }
public string HighestRole { public Guid ProfileId { get; set; }
public string HighestRole
{
get get
{ {
string _highestRole = ""; string _highestRole = "";

View File

@@ -8,37 +8,46 @@ namespace Authentication
public DateTimeOffset? LockoutEnd { get; set; } public DateTimeOffset? LockoutEnd { get; set; }
public List<string> Roles { get; set; } public List<string> Roles { get; set; }
public SecurityProfileViewModel SecurityProfile { get; set; } public SecurityProfileViewModel SecurityProfile { get; set; }
public string HighestRole { public Guid ProfileId { get; set; }
public string HighestRole
{
get get
{ {
string _highestRole = ""; string _highestRole = "";
foreach (string role in Roles) if (Roles != null)
{ {
switch (role) foreach (string role in Roles)
{ {
case "Admin": switch (role)
// there is no higher {
_highestRole = role; case "Admin":
break; // there is no higher
case "Gamer":
// only one high is Admin, so check for that
if (_highestRole != "Admin")
{
_highestRole = role; _highestRole = role;
} break;
break; case "Gamer":
case "Player": // only one high is Admin, so check for that
// make sure _highestRole isn't already set to Gamer or Admin if (_highestRole != "Admin")
if (_highestRole != "Admin" && _highestRole != "Gamer") {
{ _highestRole = role;
_highestRole = role; }
} break;
break; case "Player":
default: // make sure _highestRole isn't already set to Gamer or Admin
_highestRole = "Player"; if (_highestRole != "Admin" && _highestRole != "Gamer")
break; {
_highestRole = role;
}
break;
default:
_highestRole = "Player";
break;
}
} }
} }
else
{
_highestRole = "Player";
}
return _highestRole; return _highestRole;
} }

View File

@@ -4,12 +4,39 @@ using System.Security.Cryptography;
namespace gaseous_server.Classes namespace gaseous_server.Classes
{ {
public class Bios public class Bios
{ {
public Bios() public Bios()
{ {
} }
public static void MigrateToNewFolderStructure()
{
// migrate from old BIOS file structure which had each bios file inside a folder named for the platform to the new structure which has each file in a subdirectory named after the MD5 hash
if (Directory.Exists(Config.LibraryConfiguration.LibraryBIOSDirectory))
{
foreach (Models.PlatformMapping.PlatformMapItem platformMapping in Models.PlatformMapping.PlatformMap)
{
if (platformMapping.Bios != null)
{
foreach (Models.PlatformMapping.PlatformMapItem.EmulatorBiosItem emulatorBiosItem in platformMapping.Bios)
{
string oldBiosPath = Path.Combine(Config.LibraryConfiguration.LibraryBIOSDirectory, platformMapping.IGDBSlug.ToString(), emulatorBiosItem.filename);
string newBiosPath = Path.Combine(Config.LibraryConfiguration.LibraryFirmwareDirectory, emulatorBiosItem.hash + ".bios");
if (File.Exists(oldBiosPath))
{
File.Copy(oldBiosPath, newBiosPath, true);
}
}
}
}
// remove old BIOS folder structure
Directory.Delete(Config.LibraryConfiguration.LibraryBIOSDirectory, true);
}
}
public static Models.PlatformMapping.PlatformMapItem? BiosHashSignatureLookup(string MD5) public static Models.PlatformMapping.PlatformMapItem? BiosHashSignatureLookup(string MD5)
{ {
@@ -96,10 +123,11 @@ namespace gaseous_server.Classes
{ {
get get
{ {
return Path.Combine(Config.LibraryConfiguration.LibraryBIOSDirectory, platformslug, base.filename); return Path.Combine(Config.LibraryConfiguration.LibraryFirmwareDirectory, hash + ".bios");
} }
} }
public bool Available { public bool Available
{
get get
{ {
bool fileExists = File.Exists(biosPath); bool fileExists = File.Exists(biosPath);

View File

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

View File

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

View File

@@ -3,6 +3,7 @@ using System.Data;
using Newtonsoft.Json; using Newtonsoft.Json;
using IGDB.Models; using IGDB.Models;
using gaseous_server.Classes.Metadata; using gaseous_server.Classes.Metadata;
using NuGet.Common;
namespace gaseous_server.Classes namespace gaseous_server.Classes
{ {
@@ -79,7 +80,8 @@ namespace gaseous_server.Classes
get get
{ {
string logPath = Path.Combine(ConfigurationPath, "Logs"); string logPath = Path.Combine(ConfigurationPath, "Logs");
if (!Directory.Exists(logPath)) { if (!Directory.Exists(logPath))
{
Directory.CreateDirectory(logPath); Directory.CreateDirectory(logPath);
} }
return logPath; return logPath;
@@ -91,7 +93,7 @@ namespace gaseous_server.Classes
get get
{ {
string logFileExtension = "txt"; string logFileExtension = "txt";
string logPathName = Path.Combine(LogPath, "Server Log " + DateTime.Now.ToUniversalTime().ToString("yyyyMMdd") + "." + logFileExtension); string logPathName = Path.Combine(LogPath, "Server Log " + DateTime.Now.ToUniversalTime().ToString("yyyyMMdd") + "." + logFileExtension);
return logPathName; return logPathName;
} }
@@ -117,11 +119,33 @@ namespace gaseous_server.Classes
if (_tempConfig != null) if (_tempConfig != null)
{ {
_config = _tempConfig; _config = _tempConfig;
} else
// load environment variables if we're in a docker container
if (!String.IsNullOrEmpty(Environment.GetEnvironmentVariable("INDOCKER")))
{
if (Environment.GetEnvironmentVariable("INDOCKER") == "1")
{
Console.WriteLine("Running in Docker - setting configuration from variables");
_config.DatabaseConfiguration.HostName = (string)Common.GetEnvVar("dbhost", _config.DatabaseConfiguration.HostName);
_config.DatabaseConfiguration.UserName = (string)Common.GetEnvVar("dbuser", _config.DatabaseConfiguration.UserName);
_config.DatabaseConfiguration.Password = (string)Common.GetEnvVar("dbpass", _config.DatabaseConfiguration.Password);
_config.DatabaseConfiguration.DatabaseName = (string)Common.GetEnvVar("dbname", _config.DatabaseConfiguration.DatabaseName);
_config.DatabaseConfiguration.Port = int.Parse((string)Common.GetEnvVar("dbport", _config.DatabaseConfiguration.Port.ToString()));
_config.MetadataConfiguration.MetadataSource = (HasheousClient.Models.MetadataModel.MetadataSources)Enum.Parse(typeof(HasheousClient.Models.MetadataModel.MetadataSources), (string)Common.GetEnvVar("metadatasource", _config.MetadataConfiguration.MetadataSource.ToString()));
_config.MetadataConfiguration.SignatureSource = (HasheousClient.Models.MetadataModel.SignatureSources)Enum.Parse(typeof(HasheousClient.Models.MetadataModel.SignatureSources), (string)Common.GetEnvVar("signaturesource", _config.MetadataConfiguration.SignatureSource.ToString())); ;
_config.MetadataConfiguration.MaxLibraryScanWorkers = int.Parse((string)Common.GetEnvVar("maxlibraryscanworkers", _config.MetadataConfiguration.MaxLibraryScanWorkers.ToString()));
_config.MetadataConfiguration.HasheousHost = (string)Common.GetEnvVar("hasheoushost", _config.MetadataConfiguration.HasheousHost);
_config.IGDBConfiguration.ClientId = (string)Common.GetEnvVar("igdbclientid", _config.IGDBConfiguration.ClientId);
_config.IGDBConfiguration.Secret = (string)Common.GetEnvVar("igdbclientsecret", _config.IGDBConfiguration.Secret);
}
}
}
else
{ {
throw new Exception("There was an error reading the config file: Json returned null"); throw new Exception("There was an error reading the config file: Json returned null");
} }
} else }
else
{ {
// no config file! // no config file!
// use defaults and save // use defaults and save
@@ -130,8 +154,8 @@ namespace gaseous_server.Classes
} }
} }
Console.WriteLine("Using configuration:"); // Console.WriteLine("Using configuration:");
Console.WriteLine(Newtonsoft.Json.JsonConvert.SerializeObject(_config, Formatting.Indented)); // Console.WriteLine(Newtonsoft.Json.JsonConvert.SerializeObject(_config, Formatting.Indented));
} }
public static void UpdateConfig() public static void UpdateConfig()
@@ -161,7 +185,7 @@ namespace gaseous_server.Classes
File.WriteAllText(ConfigurationFilePath, configRaw); File.WriteAllText(ConfigurationFilePath, configRaw);
} }
private static Dictionary<string, string> AppSettings = new Dictionary<string, string>(); private static Dictionary<string, object> AppSettings = new Dictionary<string, object>();
public static void InitSettings() public static void InitSettings()
{ {
@@ -171,62 +195,197 @@ namespace gaseous_server.Classes
DataTable dbResponse = db.ExecuteCMD(sql); DataTable dbResponse = db.ExecuteCMD(sql);
foreach (DataRow dataRow in dbResponse.Rows) foreach (DataRow dataRow in dbResponse.Rows)
{ {
if (AppSettings.ContainsKey((string)dataRow["Setting"])) string SettingName = (string)dataRow["Setting"];
{
AppSettings[(string)dataRow["Setting"]] = (string)dataRow["Value"];
}
else
{
AppSettings.Add((string)dataRow["Setting"], (string)dataRow["Value"]);
}
}
}
public static string ReadSetting(string SettingName, string DefaultValue) if (AppSettings.ContainsKey(SettingName))
{ {
if (AppSettings.ContainsKey(SettingName)) AppSettings.Remove(SettingName);
{ }
return AppSettings[SettingName];
} // Logging.Log(Logging.LogType.Information, "Load Settings", "Loading setting " + SettingName + " from database");
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);
try try
{ {
Logging.Log(Logging.LogType.Debug, "Database", "Reading setting '" + SettingName + "'"); if (Database.schema_version >= 1016)
DataTable dbResponse = db.ExecuteCMD(sql, dbDict);
if (dbResponse.Rows.Count == 0)
{ {
// no value with that name stored - respond with the default value switch ((int)dataRow["ValueType"])
SetSetting(SettingName, DefaultValue); {
return DefaultValue; 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 else
{ {
AppSettings.Add(SettingName, (string)dbResponse.Rows[0][0]); AppSettings.Add(SettingName, dataRow["Value"]);
return (string)dbResponse.Rows[0][0];
} }
} }
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) catch (Exception ex)
{ {
Logging.Log(Logging.LogType.Critical, "Database", "Failed reading setting " + SettingName, ex); Logging.Log(Logging.LogType.Critical, "Settings", "Exception when reading server setting " + SettingName + ".", ex);
throw;
} }
} }
} }
public static void SetSetting(string SettingName, string Value) public static T ReadSetting<T>(string SettingName, T DefaultValue)
{ {
Database db = new Database(Database.databaseType.MySql, Config.DatabaseConfiguration.ConnectionString); Database db = new Database(Database.databaseType.MySql, Config.DatabaseConfiguration.ConnectionString);
string sql = "REPLACE INTO Settings (Setting, Value) VALUES (@SettingName, @Value)"; try
Dictionary<string, object> dbDict = new Dictionary<string, object>(); {
dbDict.Add("SettingName", SettingName); if (AppSettings.ContainsKey(SettingName))
dbDict.Add("Value", Value); {
return (T)AppSettings[SettingName];
}
else
{
string sql;
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 + "'"); Logging.Log(Logging.LogType.Debug, "Database", "Storing setting '" + SettingName + "' to value: '" + Value + "'");
try try
@@ -264,7 +423,8 @@ namespace gaseous_server.Classes
public class Database public class Database
{ {
private static string _DefaultHostName { private static string _DefaultHostName
{
get get
{ {
if (!String.IsNullOrEmpty(Environment.GetEnvironmentVariable("dbhost"))) if (!String.IsNullOrEmpty(Environment.GetEnvironmentVariable("dbhost")))
@@ -308,11 +468,41 @@ namespace gaseous_server.Classes
} }
} }
private static string _DefaultDatabaseName
{
get
{
if (!String.IsNullOrEmpty(Environment.GetEnvironmentVariable("dbname")))
{
return Environment.GetEnvironmentVariable("dbname");
}
else
{
return "gaseous";
}
}
}
private static int _DefaultDatabasePort
{
get
{
if (!String.IsNullOrEmpty(Environment.GetEnvironmentVariable("dbport")))
{
return int.Parse(Environment.GetEnvironmentVariable("dbport"));
}
else
{
return 3306;
}
}
}
public string HostName = _DefaultHostName; public string HostName = _DefaultHostName;
public string UserName = _DefaultUserName; public string UserName = _DefaultUserName;
public string Password = _DefaultPassword; public string Password = _DefaultPassword;
public string DatabaseName = "gaseous"; public string DatabaseName = _DefaultDatabaseName;
public int Port = 3306; public int Port = _DefaultDatabasePort;
[JsonIgnore] [JsonIgnore]
public string ConnectionString public string ConnectionString
@@ -341,11 +531,7 @@ namespace gaseous_server.Classes
{ {
get get
{ {
return ReadSetting("LibraryRootDirectory", Path.Combine(Config.ConfigurationPath, "Data")); return Path.Combine(Config.ConfigurationPath, "Data");
}
set
{
SetSetting("LibraryRootDirectory", value);
} }
} }
@@ -389,6 +575,14 @@ namespace gaseous_server.Classes
} }
} }
public string LibraryFirmwareDirectory
{
get
{
return Path.Combine(LibraryRootDirectory, "Firmware");
}
}
public string LibraryUploadDirectory public string LibraryUploadDirectory
{ {
get get
@@ -450,7 +644,7 @@ namespace gaseous_server.Classes
return MetadataPath; return MetadataPath;
} }
public string LibrarySignatureImportDirectory public string LibrarySignaturesDirectory
{ {
get get
{ {
@@ -458,16 +652,26 @@ namespace gaseous_server.Classes
} }
} }
public string LibrarySignaturesProcessedDirectory
{
get
{
return Path.Combine(LibraryRootDirectory, "Signatures - Processed");
}
}
public void InitLibrary() public void InitLibrary()
{ {
if (!Directory.Exists(LibraryRootDirectory)) { Directory.CreateDirectory(LibraryRootDirectory); } if (!Directory.Exists(LibraryRootDirectory)) { Directory.CreateDirectory(LibraryRootDirectory); }
if (!Directory.Exists(LibraryImportDirectory)) { Directory.CreateDirectory(LibraryImportDirectory); } if (!Directory.Exists(LibraryImportDirectory)) { Directory.CreateDirectory(LibraryImportDirectory); }
if (!Directory.Exists(LibraryBIOSDirectory)) { Directory.CreateDirectory(LibraryBIOSDirectory); } // if (!Directory.Exists(LibraryBIOSDirectory)) { Directory.CreateDirectory(LibraryBIOSDirectory); }
if (!Directory.Exists(LibraryFirmwareDirectory)) { Directory.CreateDirectory(LibraryFirmwareDirectory); }
if (!Directory.Exists(LibraryUploadDirectory)) { Directory.CreateDirectory(LibraryUploadDirectory); } if (!Directory.Exists(LibraryUploadDirectory)) { Directory.CreateDirectory(LibraryUploadDirectory); }
if (!Directory.Exists(LibraryMetadataDirectory)) { Directory.CreateDirectory(LibraryMetadataDirectory); } if (!Directory.Exists(LibraryMetadataDirectory)) { Directory.CreateDirectory(LibraryMetadataDirectory); }
if (!Directory.Exists(LibraryTempDirectory)) { Directory.CreateDirectory(LibraryTempDirectory); } if (!Directory.Exists(LibraryTempDirectory)) { Directory.CreateDirectory(LibraryTempDirectory); }
if (!Directory.Exists(LibraryCollectionsDirectory)) { Directory.CreateDirectory(LibraryCollectionsDirectory); } if (!Directory.Exists(LibraryCollectionsDirectory)) { Directory.CreateDirectory(LibraryCollectionsDirectory); }
if (!Directory.Exists(LibrarySignatureImportDirectory)) { Directory.CreateDirectory(LibrarySignatureImportDirectory); } if (!Directory.Exists(LibrarySignaturesDirectory)) { Directory.CreateDirectory(LibrarySignaturesDirectory); }
if (!Directory.Exists(LibrarySignaturesProcessedDirectory)) { Directory.CreateDirectory(LibrarySignaturesProcessedDirectory); }
} }
} }
@@ -503,11 +707,22 @@ namespace gaseous_server.Classes
} }
} }
private static bool _HasheousSubmitFixes { get; set; } = false;
private static string _HasheousAPIKey { get; set; } = "";
private static int _MaxLibraryScanWorkers private static int _MaxLibraryScanWorkers
{ {
get get
{ {
return 4; if (!String.IsNullOrEmpty(Environment.GetEnvironmentVariable("maxlibraryscanworkers")))
{
return int.Parse(Environment.GetEnvironmentVariable("maxlibraryscanworkers"));
}
else
{
return 4;
}
} }
} }
@@ -515,9 +730,9 @@ namespace gaseous_server.Classes
{ {
get get
{ {
if (!String.IsNullOrEmpty(Environment.GetEnvironmentVariable("hasheoushoust"))) if (!String.IsNullOrEmpty(Environment.GetEnvironmentVariable("hasheoushost")))
{ {
return Environment.GetEnvironmentVariable("hasheoushoust"); return Environment.GetEnvironmentVariable("hasheoushost");
} }
else else
{ {
@@ -530,6 +745,10 @@ namespace gaseous_server.Classes
public HasheousClient.Models.MetadataModel.SignatureSources SignatureSource = _SignatureSource; public HasheousClient.Models.MetadataModel.SignatureSources SignatureSource = _SignatureSource;
public bool HasheousSubmitFixes = _HasheousSubmitFixes;
public string HasheousAPIKey = _HasheousAPIKey;
public int MaxLibraryScanWorkers = _MaxLibraryScanWorkers; public int MaxLibraryScanWorkers = _MaxLibraryScanWorkers;
public string HasheousHost = _HasheousHost; public string HasheousHost = _HasheousHost;

View File

@@ -9,6 +9,21 @@ namespace gaseous_server.Classes
{ {
public class Database 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() public Database()
{ {
@@ -55,30 +70,41 @@ namespace gaseous_server.Classes
public void InitDB() public void InitDB()
{ {
// load resources // load resources
var assembly = Assembly.GetExecutingAssembly(); var assembly = Assembly.GetExecutingAssembly();
switch (_ConnectorType) DatabaseMemoryCacheOptions? CacheOptions = new DatabaseMemoryCacheOptions(false);
switch (_ConnectorType)
{ {
case databaseType.MySql: case databaseType.MySql:
// check if the database exists first - first run must have permissions to create a database // check if the database exists first - first run must have permissions to create a database
string sql = "CREATE DATABASE IF NOT EXISTS `" + Config.DatabaseConfiguration.DatabaseName + "`;"; string sql = "CREATE DATABASE IF NOT EXISTS `" + Config.DatabaseConfiguration.DatabaseName + "`;";
Dictionary<string, object> dbDict = new Dictionary<string, object>(); Dictionary<string, object> dbDict = new Dictionary<string, object>();
Logging.Log(Logging.LogType.Information, "Database", "Creating database if it doesn't exist"); Logging.Log(Logging.LogType.Information, "Database", "Creating database if it doesn't exist");
ExecuteCMD(sql, dbDict, 30, "server=" + Config.DatabaseConfiguration.HostName + ";port=" + Config.DatabaseConfiguration.Port + ";userid=" + Config.DatabaseConfiguration.UserName + ";password=" + Config.DatabaseConfiguration.Password); ExecuteCMD(sql, dbDict, CacheOptions, 30, "server=" + Config.DatabaseConfiguration.HostName + ";port=" + Config.DatabaseConfiguration.Port + ";userid=" + Config.DatabaseConfiguration.UserName + ";password=" + Config.DatabaseConfiguration.Password);
// check if schema version table is in place - if not, create the schema version table // check if schema version table is in place - if not, create the schema version table
sql = "SELECT TABLE_SCHEMA, TABLE_NAME FROM information_schema.TABLES WHERE TABLE_SCHEMA = '" + Config.DatabaseConfiguration.DatabaseName + "' AND TABLE_NAME = 'schema_version';"; sql = "SELECT TABLE_SCHEMA, TABLE_NAME FROM information_schema.TABLES WHERE TABLE_SCHEMA = '" + Config.DatabaseConfiguration.DatabaseName + "' AND TABLE_NAME = 'schema_version';";
DataTable SchemaVersionPresent = ExecuteCMD(sql, dbDict); DataTable SchemaVersionPresent = ExecuteCMD(sql, dbDict, CacheOptions);
if (SchemaVersionPresent.Rows.Count == 0) if (SchemaVersionPresent.Rows.Count == 0)
{ {
// no schema table present - create it // no schema table present - create it
Logging.Log(Logging.LogType.Information, "Database", "Schema version table doesn't exist. Creating it."); Logging.Log(Logging.LogType.Information, "Database", "Schema version table doesn't exist. Creating it.");
sql = "CREATE TABLE `schema_version` (`schema_version` INT NOT NULL, PRIMARY KEY (`schema_version`)); INSERT INTO `schema_version` (`schema_version`) VALUES (0);"; sql = "CREATE TABLE `schema_version` (`schema_version` INT NOT NULL, PRIMARY KEY (`schema_version`)); INSERT INTO `schema_version` (`schema_version`) VALUES (0);";
ExecuteCMD(sql, dbDict); ExecuteCMD(sql, dbDict, CacheOptions);
} }
for (int i = 1000; i < 10000; i++) sql = "SELECT schema_version FROM schema_version;";
dbDict = new Dictionary<string, object>();
DataTable SchemaVersion = ExecuteCMD(sql, dbDict, CacheOptions);
int OuterSchemaVer = (int)SchemaVersion.Rows[0][0];
if (OuterSchemaVer == 0)
{
OuterSchemaVer = 1000;
}
for (int i = OuterSchemaVer; i < 10000; i++)
{ {
string resourceName = "gaseous_server.Support.Database.MySQL.gaseous-" + i + ".sql"; string resourceName = "gaseous_server.Support.Database.MySQL.gaseous-" + i + ".sql";
string dbScript = ""; string dbScript = "";
@@ -89,40 +115,45 @@ namespace gaseous_server.Classes
using (Stream stream = assembly.GetManifestResourceStream(resourceName)) using (Stream stream = assembly.GetManifestResourceStream(resourceName))
using (StreamReader reader = new StreamReader(stream)) using (StreamReader reader = new StreamReader(stream))
{ {
dbScript = reader.ReadToEnd(); dbScript = reader.ReadToEnd();
// apply script // apply script
sql = "SELECT schema_version FROM schema_version;"; sql = "SELECT schema_version FROM schema_version;";
dbDict = new Dictionary<string, object>(); dbDict = new Dictionary<string, object>();
DataTable SchemaVersion = ExecuteCMD(sql, dbDict); SchemaVersion = ExecuteCMD(sql, dbDict, CacheOptions);
if (SchemaVersion.Rows.Count == 0) if (SchemaVersion.Rows.Count == 0)
{ {
// something is broken here... where's the table? // something is broken here... where's the table?
Logging.Log(Logging.LogType.Critical, "Database", "Schema table missing! This shouldn't happen!"); Logging.Log(Logging.LogType.Critical, "Database", "Schema table missing! This shouldn't happen!");
throw new Exception("schema_version table is missing!"); throw new Exception("schema_version table is missing!");
} }
else else
{ {
int SchemaVer = (int)SchemaVersion.Rows[0][0]; int SchemaVer = (int)SchemaVersion.Rows[0][0];
Logging.Log(Logging.LogType.Information, "Database", "Schema version is " + SchemaVer); Logging.Log(Logging.LogType.Information, "Database", "Schema version is " + SchemaVer);
if (SchemaVer < i) // update schema version variable
Database.schema_version = SchemaVer;
if (SchemaVer < i)
{ {
try try
{ {
// run pre-upgrade code // run pre-upgrade code
DatabaseMigration.PreUpgradeScript(i, _ConnectorType); DatabaseMigration.PreUpgradeScript(i, _ConnectorType);
// apply schema! // apply schema!
Logging.Log(Logging.LogType.Information, "Database", "Updating schema to version " + i); Logging.Log(Logging.LogType.Information, "Database", "Updating schema to version " + i);
ExecuteCMD(dbScript, dbDict, 180); ExecuteCMD(dbScript, dbDict, CacheOptions, 180);
sql = "UPDATE schema_version SET schema_version=@schemaver"; sql = "UPDATE schema_version SET schema_version=@schemaver";
dbDict = new Dictionary<string, object>(); dbDict = new Dictionary<string, object>();
dbDict.Add("schemaver", i); dbDict.Add("schemaver", i);
ExecuteCMD(sql, dbDict); ExecuteCMD(sql, dbDict, CacheOptions);
// run post-upgrade code // run post-upgrade code
DatabaseMigration.PostUpgradeScript(i, _ConnectorType); DatabaseMigration.PostUpgradeScript(i, _ConnectorType);
// update schema version variable
Database.schema_version = i;
} }
catch (Exception ex) catch (Exception ex)
{ {
@@ -133,47 +164,91 @@ namespace gaseous_server.Classes
} }
} }
} }
} }
Logging.Log(Logging.LogType.Information, "Database", "Database setup complete"); Logging.Log(Logging.LogType.Information, "Database", "Database setup complete");
break; break;
} }
} }
public DataTable ExecuteCMD(string Command) public DataTable ExecuteCMD(string Command)
{ {
DatabaseMemoryCacheOptions? CacheOptions = null;
Dictionary<string, object> dbDict = new Dictionary<string, object>(); Dictionary<string, object> dbDict = new Dictionary<string, object>();
return _ExecuteCMD(Command, dbDict, 30, ""); return _ExecuteCMD(Command, dbDict, CacheOptions, 30, "");
} }
public DataTable ExecuteCMD(string Command, Dictionary<string, object> Parameters) public DataTable ExecuteCMD(string Command, DatabaseMemoryCacheOptions? CacheOptions)
{ {
return _ExecuteCMD(Command, Parameters, 30, ""); Dictionary<string, object> dbDict = new Dictionary<string, object>();
} return _ExecuteCMD(Command, dbDict, CacheOptions, 30, "");
}
public DataTable ExecuteCMD(string Command, Dictionary<string, object> Parameters, int Timeout = 30, string ConnectionString = "") public DataTable ExecuteCMD(string Command, Dictionary<string, object> Parameters)
{ {
return _ExecuteCMD(Command, Parameters, Timeout, ConnectionString); DatabaseMemoryCacheOptions? CacheOptions = null;
}
return _ExecuteCMD(Command, Parameters, CacheOptions, 30, "");
}
public DataTable ExecuteCMD(string Command, Dictionary<string, object> Parameters, DatabaseMemoryCacheOptions? CacheOptions)
{
return _ExecuteCMD(Command, Parameters, CacheOptions, 30, "");
}
public DataTable ExecuteCMD(string Command, Dictionary<string, object> Parameters, int Timeout = 30, string ConnectionString = "")
{
DatabaseMemoryCacheOptions? CacheOptions = null;
return _ExecuteCMD(Command, Parameters, CacheOptions, Timeout, ConnectionString);
}
public DataTable ExecuteCMD(string Command, Dictionary<string, object> Parameters, DatabaseMemoryCacheOptions? CacheOptions, int Timeout = 30, string ConnectionString = "")
{
return _ExecuteCMD(Command, Parameters, CacheOptions, Timeout, ConnectionString);
}
public List<Dictionary<string, object>> ExecuteCMDDict(string Command) public List<Dictionary<string, object>> ExecuteCMDDict(string Command)
{ {
DatabaseMemoryCacheOptions? CacheOptions = null;
Dictionary<string, object> dbDict = new Dictionary<string, object>(); Dictionary<string, object> dbDict = new Dictionary<string, object>();
return _ExecuteCMDDict(Command, dbDict, 30, ""); return _ExecuteCMDDict(Command, dbDict, CacheOptions, 30, "");
} }
public List<Dictionary<string, object>> ExecuteCMDDict(string Command, Dictionary<string, object> Parameters) public List<Dictionary<string, object>> ExecuteCMDDict(string Command, DatabaseMemoryCacheOptions? CacheOptions)
{
return _ExecuteCMDDict(Command, Parameters, 30, "");
}
public List<Dictionary<string, object>> ExecuteCMDDict(string Command, Dictionary<string, object> Parameters, int Timeout = 30, string ConnectionString = "")
{
return _ExecuteCMDDict(Command, Parameters, Timeout, ConnectionString);
}
private List<Dictionary<string, object>> _ExecuteCMDDict(string Command, Dictionary<string, object> Parameters, int Timeout = 30, string ConnectionString = "")
{ {
DataTable dataTable = _ExecuteCMD(Command, Parameters, Timeout, ConnectionString); Dictionary<string, object> dbDict = new Dictionary<string, object>();
return _ExecuteCMDDict(Command, dbDict, CacheOptions, 30, "");
}
public List<Dictionary<string, object>> ExecuteCMDDict(string Command, Dictionary<string, object> Parameters)
{
DatabaseMemoryCacheOptions? CacheOptions = null;
return _ExecuteCMDDict(Command, Parameters, CacheOptions, 30, "");
}
public List<Dictionary<string, object>> ExecuteCMDDict(string Command, Dictionary<string, object> Parameters, DatabaseMemoryCacheOptions? CacheOptions)
{
return _ExecuteCMDDict(Command, Parameters, CacheOptions, 30, "");
}
public List<Dictionary<string, object>> ExecuteCMDDict(string Command, Dictionary<string, object> Parameters, int Timeout = 30, string ConnectionString = "")
{
DatabaseMemoryCacheOptions? CacheOptions = null;
return _ExecuteCMDDict(Command, Parameters, CacheOptions, Timeout, ConnectionString);
}
public List<Dictionary<string, object>> ExecuteCMDDict(string Command, Dictionary<string, object> Parameters, DatabaseMemoryCacheOptions? CacheOptions, int Timeout = 30, string ConnectionString = "")
{
return _ExecuteCMDDict(Command, Parameters, CacheOptions, Timeout, ConnectionString);
}
private List<Dictionary<string, object>> _ExecuteCMDDict(string Command, Dictionary<string, object> Parameters, DatabaseMemoryCacheOptions? CacheOptions, int Timeout = 30, string ConnectionString = "")
{
DataTable dataTable = _ExecuteCMD(Command, Parameters, CacheOptions, Timeout, ConnectionString);
// convert datatable to dictionary // convert datatable to dictionary
List<Dictionary<string, object?>> rows = new List<Dictionary<string, object?>>(); List<Dictionary<string, object?>> rows = new List<Dictionary<string, object?>>();
@@ -199,18 +274,49 @@ namespace gaseous_server.Classes
return rows; return rows;
} }
private DataTable _ExecuteCMD(string Command, Dictionary<string, object> Parameters, int Timeout = 30, string ConnectionString = "") private DataTable _ExecuteCMD(string Command, Dictionary<string, object> Parameters, DatabaseMemoryCacheOptions? CacheOptions, int Timeout = 30, string ConnectionString = "")
{ {
if (ConnectionString == "") { ConnectionString = _ConnectionString; } string CacheKey = Command + string.Join(";", Parameters.Select(x => string.Join("=", x.Key, x.Value)));
switch (_ConnectorType)
{ if (CacheOptions is object && CacheOptions.CacheEnabled)
case databaseType.MySql: {
MySQLServerConnector conn = new MySQLServerConnector(ConnectionString); object? CachedData = DatabaseMemoryCache.GetCacheObject(CacheKey);
return (DataTable)conn.ExecCMD(Command, Parameters, Timeout); if (CachedData is object)
default: {
return new DataTable(); return (DataTable)CachedData;
} }
} }
// purge cache if command contains "INSERT", "UPDATE", "DELETE", or "ALTER"
if (
Command.Contains("INSERT", StringComparison.InvariantCultureIgnoreCase) ||
Command.Contains("UPDATE", StringComparison.InvariantCultureIgnoreCase) ||
Command.Contains("DELETE", StringComparison.InvariantCultureIgnoreCase) ||
Command.Contains("ALTER", StringComparison.InvariantCultureIgnoreCase)
)
{
// exclude logging events from purging the cache
if (!Command.StartsWith("INSERT INTO SERVERLOGS", StringComparison.InvariantCultureIgnoreCase))
{
DatabaseMemoryCache.ClearCache();
}
}
if (ConnectionString == "") { ConnectionString = _ConnectionString; }
switch (_ConnectorType)
{
case databaseType.MySql:
MySQLServerConnector conn = new MySQLServerConnector(ConnectionString);
DataTable RetTable = conn.ExecCMD(Command, Parameters, Timeout);
if (CacheOptions is object && CacheOptions.CacheEnabled)
{
DatabaseMemoryCache.SetCacheObject(CacheKey, RetTable, CacheOptions.ExpirationSeconds);
}
return RetTable;
default:
return new DataTable();
}
}
public int ExecuteNonQuery(string Command) public int ExecuteNonQuery(string Command)
{ {
@@ -218,52 +324,194 @@ namespace gaseous_server.Classes
return _ExecuteNonQuery(Command, dbDict, 30, ""); return _ExecuteNonQuery(Command, dbDict, 30, "");
} }
public int ExecuteNonQuery(string Command, Dictionary<string, object> Parameters) public int ExecuteNonQuery(string Command, Dictionary<string, object> Parameters)
{ {
return _ExecuteNonQuery(Command, Parameters, 30, ""); return _ExecuteNonQuery(Command, Parameters, 30, "");
} }
public int ExecuteNonQuery(string Command, Dictionary<string, object> Parameters, int Timeout = 30, string ConnectionString = "") public int ExecuteNonQuery(string Command, Dictionary<string, object> Parameters, int Timeout = 30, string ConnectionString = "")
{ {
return _ExecuteNonQuery(Command, Parameters, Timeout, ConnectionString); return _ExecuteNonQuery(Command, Parameters, Timeout, ConnectionString);
} }
private int _ExecuteNonQuery(string Command, Dictionary<string, object> Parameters, int Timeout = 30, string ConnectionString = "") private int _ExecuteNonQuery(string Command, Dictionary<string, object> Parameters, int Timeout = 30, string ConnectionString = "")
{ {
if (ConnectionString == "") { ConnectionString = _ConnectionString; } if (ConnectionString == "") { ConnectionString = _ConnectionString; }
switch (_ConnectorType) switch (_ConnectorType)
{ {
case databaseType.MySql: case databaseType.MySql:
MySQLServerConnector conn = new MySQLServerConnector(ConnectionString); MySQLServerConnector conn = new MySQLServerConnector(ConnectionString);
int retVal = conn.ExecNonQuery(Command, Parameters, Timeout); int retVal = conn.ExecNonQuery(Command, Parameters, Timeout);
return retVal; return retVal;
default: default:
return 0; return 0;
} }
} }
public void ExecuteTransactionCMD(List<SQLTransactionItem> CommandList, int Timeout = 60) public void ExecuteTransactionCMD(List<SQLTransactionItem> CommandList, int Timeout = 60)
{ {
object conn; object conn;
switch (_ConnectorType) switch (_ConnectorType)
{ {
case databaseType.MySql: case databaseType.MySql:
{ {
var commands = new List<Dictionary<string, object>>(); var commands = new List<Dictionary<string, object>>();
foreach (SQLTransactionItem CommandItem in CommandList) foreach (SQLTransactionItem CommandItem in CommandList)
{ {
var nCmd = new Dictionary<string, object>(); var nCmd = new Dictionary<string, object>();
nCmd.Add("sql", CommandItem.SQLCommand); nCmd.Add("sql", CommandItem.SQLCommand);
nCmd.Add("values", CommandItem.Parameters); nCmd.Add("values", CommandItem.Parameters);
commands.Add(nCmd); commands.Add(nCmd);
} }
conn = new MySQLServerConnector(_ConnectionString); conn = new MySQLServerConnector(_ConnectionString);
((MySQLServerConnector)conn).TransactionExecCMD(commands, Timeout); ((MySQLServerConnector)conn).TransactionExecCMD(commands, Timeout);
break; break;
} }
} }
} }
public static class DatabaseMemoryCache
{
private class MemoryCacheItem
{
public MemoryCacheItem(object CacheObject)
{
cacheObject = CacheObject;
}
public MemoryCacheItem(object CacheObject, int ExpirationSeconds)
{
cacheObject = CacheObject;
expirationSeconds = ExpirationSeconds;
}
/// <summary>
/// The time the object was added to the cache in ticks
/// </summary>
public long addedTime { get; } = Environment.TickCount64;
/// <summary>
/// The time the object will expire in ticks
/// </summary>
public long expirationTime
{
get
{
return addedTime + _expirationTicks;
}
}
/// <summary>
/// The number of seconds the object will be cached
/// </summary>
public int expirationSeconds
{
get
{
return (int)TimeSpan.FromTicks(_expirationTicks).Seconds;
}
set
{
_expirationTicks = (long)TimeSpan.FromSeconds(value).Ticks;
}
}
private long _expirationTicks = (long)TimeSpan.FromSeconds(2).Ticks;
/// <summary>
/// The object to be cached
/// </summary>
public object cacheObject { get; set; }
}
private static Dictionary<string, MemoryCacheItem> MemoryCache = new Dictionary<string, MemoryCacheItem>();
private static Timer CacheTimer = new Timer(CacheTimerCallback, null, 0, 1000);
private static void CacheTimerCallback(object? state)
{
ClearExpiredCache();
}
public static object? GetCacheObject(string CacheKey)
{
if (MemoryCache.ContainsKey(CacheKey))
{
if (MemoryCache[CacheKey].expirationTime < Environment.TickCount)
{
MemoryCache.Remove(CacheKey);
return null;
}
else
{
return MemoryCache[CacheKey].cacheObject;
}
}
else
{
return null;
}
}
public static void SetCacheObject(string CacheKey, object CacheObject, int ExpirationSeconds = 2)
{
try
{
if (MemoryCache.ContainsKey(CacheKey))
{
MemoryCache.Remove(CacheKey);
}
MemoryCache.Add(CacheKey, new MemoryCacheItem(CacheObject, ExpirationSeconds));
}
catch (Exception ex)
{
Logging.Log(Logging.LogType.Debug, "Database", "Error while setting cache object", ex);
ClearCache();
}
}
public static void RemoveCacheObject(string CacheKey)
{
if (MemoryCache.ContainsKey(CacheKey))
{
MemoryCache.Remove(CacheKey);
}
}
public static void ClearCache()
{
MemoryCache.Clear();
}
private static void ClearExpiredCache()
{
try
{
long currTime = Environment.TickCount64;
Dictionary<string, MemoryCacheItem> ExpiredItems = MemoryCache;
foreach (string key in ExpiredItems.Keys)
{
if (MemoryCache[key].expirationTime < currTime)
{
Console.WriteLine("\x1b[95mPurging expired cache item " + key + ". Added: " + MemoryCache[key].addedTime + ". Expired: " + MemoryCache[key].expirationTime);
MemoryCache.Remove(key);
}
}
}
catch (Exception ex)
{
Logging.Log(Logging.LogType.Debug, "Database", "Error while clearing expired cache", ex);
}
}
}
public class DatabaseMemoryCacheOptions
{
public DatabaseMemoryCacheOptions(bool CacheEnabled = false, int ExpirationSeconds = 1)
{
this.CacheEnabled = CacheEnabled;
this.ExpirationSeconds = ExpirationSeconds;
}
public bool CacheEnabled { get; set; }
public int ExpirationSeconds { get; set; }
}
public int GetDatabaseSchemaVersion() public int GetDatabaseSchemaVersion()
{ {
@@ -280,7 +528,7 @@ namespace gaseous_server.Classes
{ {
return (int)SchemaVersion.Rows[0][0]; return (int)SchemaVersion.Rows[0][0];
} }
default: default:
return 0; return 0;
@@ -290,17 +538,17 @@ namespace gaseous_server.Classes
public bool TestConnection() public bool TestConnection()
{ {
switch (_ConnectorType) switch (_ConnectorType)
{ {
case databaseType.MySql: case databaseType.MySql:
MySQLServerConnector conn = new MySQLServerConnector(_ConnectionString); MySQLServerConnector conn = new MySQLServerConnector(_ConnectionString);
return conn.TestConnection(); return conn.TestConnection();
default: default:
return false; return false;
} }
} }
public class SQLTransactionItem public class SQLTransactionItem
{ {
public SQLTransactionItem() public SQLTransactionItem()
{ {
@@ -312,11 +560,11 @@ namespace gaseous_server.Classes
this.Parameters = Parameters; this.Parameters = Parameters;
} }
public string? SQLCommand; public string? SQLCommand;
public Dictionary<string, object>? Parameters = new Dictionary<string, object>(); public Dictionary<string, object>? Parameters = new Dictionary<string, object>();
} }
private partial class MySQLServerConnector private partial class MySQLServerConnector
{ {
private string DBConn = ""; private string DBConn = "";
@@ -329,8 +577,8 @@ namespace gaseous_server.Classes
{ {
DataTable RetTable = new DataTable(); DataTable RetTable = new DataTable();
Logging.Log(Logging.LogType.Debug, "Database", "Connecting to database", null, true); Logging.Log(Logging.LogType.Debug, "Database", "Connecting to database", null, true);
using (MySqlConnection conn = new MySqlConnection(DBConn)) using (MySqlConnection conn = new MySqlConnection(DBConn))
{ {
conn.Open(); conn.Open();
@@ -355,7 +603,9 @@ namespace gaseous_server.Classes
Logging.Log(Logging.LogType.Debug, "Database", "Parameters: " + dictValues, null, true); Logging.Log(Logging.LogType.Debug, "Database", "Parameters: " + dictValues, null, true);
} }
RetTable.Load(cmd.ExecuteReader()); RetTable.Load(cmd.ExecuteReader());
} catch (Exception ex) { }
catch (Exception ex)
{
Logging.Log(Logging.LogType.Critical, "Database", "Error while executing '" + SQL + "'", ex); Logging.Log(Logging.LogType.Critical, "Database", "Error while executing '" + SQL + "'", ex);
Trace.WriteLine("Error executing " + SQL); Trace.WriteLine("Error executing " + SQL);
Trace.WriteLine("Full exception: " + ex.ToString()); Trace.WriteLine("Full exception: " + ex.ToString());
@@ -368,12 +618,12 @@ namespace gaseous_server.Classes
return RetTable; return RetTable;
} }
public int ExecNonQuery(string SQL, Dictionary< string, object> Parameters, int Timeout) public int ExecNonQuery(string SQL, Dictionary<string, object> Parameters, int Timeout)
{ {
int result = 0; int result = 0;
Logging.Log(Logging.LogType.Debug, "Database", "Connecting to database", null, true); Logging.Log(Logging.LogType.Debug, "Database", "Connecting to database", null, true);
using (MySqlConnection conn = new MySqlConnection(DBConn)) using (MySqlConnection conn = new MySqlConnection(DBConn))
{ {
conn.Open(); conn.Open();
@@ -398,7 +648,9 @@ namespace gaseous_server.Classes
Logging.Log(Logging.LogType.Debug, "Database", "Parameters: " + dictValues, null, true); Logging.Log(Logging.LogType.Debug, "Database", "Parameters: " + dictValues, null, true);
} }
result = cmd.ExecuteNonQuery(); result = cmd.ExecuteNonQuery();
} catch (Exception ex) { }
catch (Exception ex)
{
Logging.Log(Logging.LogType.Critical, "Database", "Error while executing '" + SQL + "'", ex); Logging.Log(Logging.LogType.Critical, "Database", "Error while executing '" + SQL + "'", ex);
Trace.WriteLine("Error executing " + SQL); Trace.WriteLine("Error executing " + SQL);
Trace.WriteLine("Full exception: " + ex.ToString()); Trace.WriteLine("Full exception: " + ex.ToString());
@@ -411,10 +663,10 @@ namespace gaseous_server.Classes
return result; return result;
} }
public void TransactionExecCMD(List<Dictionary<string, object>> Parameters, int Timeout) public void TransactionExecCMD(List<Dictionary<string, object>> Parameters, int Timeout)
{ {
using (MySqlConnection conn = new MySqlConnection(DBConn)) using (MySqlConnection conn = new MySqlConnection(DBConn))
{ {
conn.Open(); conn.Open();
var command = conn.CreateCommand(); var command = conn.CreateCommand();
MySqlTransaction transaction; MySqlTransaction transaction;
@@ -431,28 +683,28 @@ namespace gaseous_server.Classes
transaction.Commit(); transaction.Commit();
conn.Close(); conn.Close();
} }
} }
private MySqlCommand buildcommand(MySqlConnection Conn, string SQL, Dictionary<string, object> Parameters, int Timeout) private MySqlCommand buildcommand(MySqlConnection Conn, string SQL, Dictionary<string, object> Parameters, int Timeout)
{ {
var cmd = new MySqlCommand(); var cmd = new MySqlCommand();
cmd.Connection = Conn; cmd.Connection = Conn;
cmd.CommandText = SQL; cmd.CommandText = SQL;
cmd.CommandTimeout = Timeout; cmd.CommandTimeout = Timeout;
{ {
var withBlock = cmd.Parameters; var withBlock = cmd.Parameters;
if (Parameters is object) if (Parameters is object)
{ {
if (Parameters.Count > 0) if (Parameters.Count > 0)
{ {
foreach (string param in Parameters.Keys) foreach (string param in Parameters.Keys)
withBlock.AddWithValue(param, Parameters[param]); withBlock.AddWithValue(param, Parameters[param]);
} }
} }
} }
return cmd; return cmd;
} }
public bool TestConnection() public bool TestConnection()
{ {
@@ -470,7 +722,7 @@ namespace gaseous_server.Classes
} }
} }
} }
} }
} }
} }

View File

@@ -1,23 +1,80 @@
using System; using System;
using System.Data; using System.Data;
using System.Reflection;
using gaseous_server.Classes.Metadata;
using gaseous_server.Models;
using IGDB.Models;
namespace gaseous_server.Classes namespace gaseous_server.Classes
{ {
public static class DatabaseMigration public static class DatabaseMigration
{ {
public static List<int> BackgroundUpgradeTargetSchemaVersions = new List<int>(); public static List<int> BackgroundUpgradeTargetSchemaVersions = new List<int>();
public static void PreUpgradeScript(int TargetSchemaVersion, Database.databaseType? DatabaseType) public static void PreUpgradeScript(int TargetSchemaVersion, Database.databaseType? DatabaseType)
{ {
// load resources
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); var assembly = Assembly.GetExecutingAssembly();
Dictionary<string, object> dbDict = new Dictionary<string, object>();
switch(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)
{ {
case Database.databaseType.MySql: case Database.databaseType.MySql:
switch (TargetSchemaVersion) switch (TargetSchemaVersion)
@@ -26,18 +83,18 @@ namespace gaseous_server.Classes
// this is a safe background task // this is a safe background task
BackgroundUpgradeTargetSchemaVersions.Add(1002); BackgroundUpgradeTargetSchemaVersions.Add(1002);
break; break;
case 1004: case 1004:
// needs to run on start up // needs to run on start up
// copy root path to new libraries format // copy root path to new libraries format
string oldRoot = Path.Combine(Config.LibraryConfiguration.LibraryRootDirectory, "Library"); string oldRoot = Path.Combine(Config.LibraryConfiguration.LibraryRootDirectory, "Library");
string sql = "INSERT INTO GameLibraries (Name, Path, DefaultLibrary, DefaultPlatform) VALUES (@name, @path, @defaultlibrary, @defaultplatform); SELECT CAST(LAST_INSERT_ID() AS SIGNED);"; sql = "INSERT INTO GameLibraries (Name, Path, DefaultLibrary, DefaultPlatform) VALUES (@name, @path, @defaultlibrary, @defaultplatform); SELECT CAST(LAST_INSERT_ID() AS SIGNED);";
dbDict.Add("name", "Default"); dbDict.Add("name", "Default");
dbDict.Add("path", oldRoot); dbDict.Add("path", oldRoot);
dbDict.Add("defaultlibrary", 1); dbDict.Add("defaultlibrary", 1);
dbDict.Add("defaultplatform", 0); dbDict.Add("defaultplatform", 0);
DataTable data = db.ExecuteCMD(sql, dbDict); data = db.ExecuteCMD(sql, dbDict);
// apply the new library id to the existing roms // apply the new library id to the existing roms
sql = "UPDATE Games_Roms SET LibraryId=@libraryid;"; sql = "UPDATE Games_Roms SET LibraryId=@libraryid;";
@@ -46,6 +103,149 @@ namespace gaseous_server.Classes
db.ExecuteCMD(sql, dbDict); db.ExecuteCMD(sql, dbDict);
break; break;
case 1016:
// delete old format LastRun_* settings from settings table
sql = "DELETE FROM Settings WHERE Setting LIKE 'LastRun_%';";
db.ExecuteNonQuery(sql);
break;
case 1023:
// load country list
Logging.Log(Logging.LogType.Information, "Database Upgrade", "Adding country look up table contents");
string countryResourceName = "gaseous_server.Support.Country.txt";
using (Stream stream = assembly.GetManifestResourceStream(countryResourceName))
using (StreamReader reader = new StreamReader(stream))
{
do
{
string[] line = reader.ReadLine().Split("|");
sql = "INSERT INTO Country (Code, Value) VALUES (@code, @value);";
dbDict = new Dictionary<string, object>{
{ "code", line[0] },
{ "value", line[1] }
};
db.ExecuteNonQuery(sql, dbDict);
} while (reader.EndOfStream == false);
}
// load language list
Logging.Log(Logging.LogType.Information, "Database Upgrade", "Adding language look up table contents");
string languageResourceName = "gaseous_server.Support.Language.txt";
using (Stream stream = assembly.GetManifestResourceStream(languageResourceName))
using (StreamReader reader = new StreamReader(stream))
{
do
{
string[] line = reader.ReadLine().Split("|");
sql = "INSERT INTO Language (Code, Value) VALUES (@code, @value);";
dbDict = new Dictionary<string, object>{
{ "code", line[0] },
{ "value", line[1] }
};
db.ExecuteNonQuery(sql, dbDict);
} while (reader.EndOfStream == false);
}
break;
case 1024:
// create profiles for all existing users
sql = "SELECT * FROM Users;";
data = db.ExecuteCMD(sql);
foreach (DataRow row in data.Rows)
{
// get legacy avatar from UserAvatars table
sql = "SELECT Avatar FROM UserAvatars WHERE UserId = @userid;";
dbDict = new Dictionary<string, object>
{
{ "userid", row["Id"] }
};
DataTable avatarData = db.ExecuteCMD(sql, dbDict);
sql = "INSERT INTO UserProfiles (Id, UserId, DisplayName, Quip, Avatar, AvatarExtension, UnstructuredData) VALUES (@id, @userid, @displayname, @quip, @avatar, @avatarextension, @data);";
dbDict = new Dictionary<string, object>
{
{ "id", Guid.NewGuid() },
{ "userid", row["Id"] },
{ "displayname", row["Email"] },
{ "quip", "" },
{ "avatar", avatarData.Rows.Count > 0 ? avatarData.Rows[0]["Avatar"] : null },
{ "avatarextension", avatarData.Rows.Count > 0 ? ".jpg" : null },
{ "data", "{}" }
};
db.ExecuteNonQuery(sql, dbDict);
}
// update all rom paths to use the new format
sql = "SELECT * FROM GameLibraries;";
data = db.ExecuteCMD(sql);
foreach (DataRow row in data.Rows)
{
sql = "SELECT * FROM Games_Roms WHERE LibraryId = @libraryid;";
dbDict = new Dictionary<string, object>
{
{ "libraryid", row["Id"] }
};
DataTable romData = db.ExecuteCMD(sql, dbDict);
string libraryRootPath = (string)row["Path"];
if (libraryRootPath.EndsWith(Path.DirectorySeparatorChar.ToString()) == false)
{
libraryRootPath += Path.DirectorySeparatorChar;
}
bool GetLastThreeElements = (bool)row["DefaultLibrary"];
foreach (DataRow romRow in romData.Rows)
{
string existingPath = (string)romRow["RelativePath"];
string newPath = "";
if (GetLastThreeElements == true)
{
// strip all but the last 3 elements from existingPath separated by directory separator
// this mode only works for the default library
string[] pathParts = existingPath.Split(Path.DirectorySeparatorChar);
if (pathParts.Length > 3)
{
newPath = Path.Combine(pathParts[pathParts.Length - 3], pathParts[pathParts.Length - 2], pathParts[pathParts.Length - 1]);
}
else
{
newPath = existingPath;
}
}
else
{
// strip the library root path from the existing path
if (existingPath.StartsWith(libraryRootPath))
{
newPath = existingPath.Substring(libraryRootPath.Length);
}
else
{
newPath = existingPath;
}
}
Logging.Log(Logging.LogType.Information, "Database Upgrade", "Updating ROM path from " + existingPath + " to " + newPath);
sql = "UPDATE Games_Roms SET RelativePath = @newpath WHERE Id = @id;";
dbDict = new Dictionary<string, object>
{
{ "newpath", newPath },
{ "id", romRow["Id"] }
};
db.ExecuteNonQuery(sql, dbDict);
}
}
// migrating metadata is a safe background task
BackgroundUpgradeTargetSchemaVersions.Add(1024);
break;
} }
break; break;
} }
@@ -60,11 +260,16 @@ namespace gaseous_server.Classes
case 1002: case 1002:
MySql_1002_MigrateMetadataVersion(); MySql_1002_MigrateMetadataVersion();
break; break;
case 1024:
MySql_1024_MigrateMetadataVersion();
break;
} }
} }
} }
public static void MySql_1002_MigrateMetadataVersion() { public static void MySql_1002_MigrateMetadataVersion()
{
Database db = new Database(Database.databaseType.MySql, Config.DatabaseConfiguration.ConnectionString); Database db = new Database(Database.databaseType.MySql, Config.DatabaseConfiguration.ConnectionString);
string sql = ""; string sql = "";
Dictionary<string, object> dbDict = new Dictionary<string, object>(); Dictionary<string, object> dbDict = new Dictionary<string, object>();
@@ -77,7 +282,7 @@ namespace gaseous_server.Classes
Logging.Log(Logging.LogType.Information, "Signature Ingestor - Database Update", "Updating " + data.Rows.Count + " database entries"); Logging.Log(Logging.LogType.Information, "Signature Ingestor - Database Update", "Updating " + data.Rows.Count + " database entries");
int Counter = 0; int Counter = 0;
int LastCounterCheck = 0; int LastCounterCheck = 0;
foreach (DataRow row in data.Rows) foreach (DataRow row in data.Rows)
{ {
List<string> Flags = Newtonsoft.Json.JsonConvert.DeserializeObject<List<string>>((string)Common.ReturnValueIfNull(row["flags"], "[]")); List<string> Flags = Newtonsoft.Json.JsonConvert.DeserializeObject<List<string>>((string)Common.ReturnValueIfNull(row["flags"], "[]"));
List<KeyValuePair<string, object>> Attributes = new List<KeyValuePair<string, object>>(); List<KeyValuePair<string, object>> Attributes = new List<KeyValuePair<string, object>>();
@@ -150,7 +355,7 @@ namespace gaseous_server.Classes
dbDict.Add("id", (int)row["Id"]); dbDict.Add("id", (int)row["Id"]);
db.ExecuteCMD(updateSQL, dbDict); db.ExecuteCMD(updateSQL, dbDict);
if ((Counter - LastCounterCheck) > 10) if ((Counter - LastCounterCheck) > 10)
{ {
LastCounterCheck = Counter; LastCounterCheck = Counter;
Logging.Log(Logging.LogType.Information, "Signature Ingestor - Database Update", "Updating " + Counter + " / " + data.Rows.Count + " database entries"); Logging.Log(Logging.LogType.Information, "Signature Ingestor - Database Update", "Updating " + Counter + " / " + data.Rows.Count + " database entries");
@@ -159,5 +364,39 @@ namespace gaseous_server.Classes
} }
} }
} }
public static void MySql_1024_MigrateMetadataVersion()
{
FileSignature fileSignature = new FileSignature();
Database db = new Database(Database.databaseType.MySql, Config.DatabaseConfiguration.ConnectionString);
string sql = "SELECT * FROM view_Games_Roms WHERE RomDataVersion = 1;";
DataTable data = db.ExecuteCMD(sql);
long count = 1;
foreach (DataRow row in data.Rows)
{
Logging.Log(Logging.LogType.Information, "Database Migration", "Updating ROM table for ROM (" + count + " / " + data.Rows.Count + "): " + (string)row["Name"]);
GameLibrary.LibraryItem library = GameLibrary.GetLibrary((int)row["LibraryId"]);
Common.hashObject hash = new Common.hashObject()
{
md5hash = (string)row["MD5"],
sha1hash = (string)row["SHA1"]
};
Signatures_Games signature = fileSignature.GetFileSignature(
library,
hash,
new FileInfo((string)row["Path"]),
(string)row["Path"]
);
Platform platform = Platforms.GetPlatform((long)row["PlatformId"], false);
Game game = Games.GetGame((long)row["GameId"], false, false, false);
ImportGame.StoreROM(library, hash, game, platform, signature, (string)row["Path"], (long)row["Id"]);
count += 1;
}
}
} }
} }

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

@@ -1,6 +1,9 @@
using System.Collections.Concurrent; using System.Collections.Concurrent;
using System.IO.Compression; using System.IO.Compression;
using System.Net;
using gaseous_server.Classes.Metadata;
using HasheousClient.Models; using HasheousClient.Models;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using NuGet.Common; using NuGet.Common;
using SevenZip; using SevenZip;
using SharpCompress.Archives; using SharpCompress.Archives;
@@ -31,7 +34,7 @@ namespace gaseous_server.Classes
if (!Directory.Exists(ExtractPath)) { Directory.CreateDirectory(ExtractPath); } if (!Directory.Exists(ExtractPath)) { Directory.CreateDirectory(ExtractPath); }
try try
{ {
switch(ImportedFileExtension) switch (ImportedFileExtension)
{ {
case ".zip": case ".zip":
Logging.Log(Logging.LogType.Information, "Get Signature", "Decompressing using zip"); Logging.Log(Logging.LogType.Information, "Get Signature", "Decompressing using zip");
@@ -105,31 +108,24 @@ namespace gaseous_server.Classes
} }
break; break;
} }
Logging.Log(Logging.LogType.Information, "Get Signature", "Processing decompressed files for signature matches"); Logging.Log(Logging.LogType.Information, "Get Signature", "Processing decompressed files for signature matches");
// loop through contents until we find the first signature match // loop through contents until we find the first signature match
List<ArchiveData> archiveFiles = new List<ArchiveData>(); List<ArchiveData> archiveFiles = new List<ArchiveData>();
bool signatureFound = false; bool signatureFound = false;
bool signatureSelectorAlreadyApplied = false;
foreach (string file in Directory.GetFiles(ExtractPath, "*.*", SearchOption.AllDirectories)) foreach (string file in Directory.GetFiles(ExtractPath, "*.*", SearchOption.AllDirectories))
{ {
bool signatureSelector = false;
if (File.Exists(file)) if (File.Exists(file))
{ {
FileInfo zfi = new FileInfo(file); FileInfo zfi = new FileInfo(file);
Common.hashObject zhash = new Common.hashObject(file); Common.hashObject zhash = new Common.hashObject(file);
Logging.Log(Logging.LogType.Information, "Get Signature", "Checking signature of decompressed file " + file); Logging.Log(Logging.LogType.Information, "Get Signature", "Checking signature of decompressed file " + file);
if (zfi != null) if (zfi != null)
{ {
ArchiveData archiveData = new ArchiveData{
FileName = Path.GetFileName(file),
FilePath = zfi.Directory.FullName.Replace(ExtractPath, ""),
Size = zfi.Length,
MD5 = hash.md5hash,
SHA1 = hash.sha1hash
};
archiveFiles.Add(archiveData);
if (signatureFound == false) if (signatureFound == false)
{ {
gaseous_server.Models.Signatures_Games zDiscoveredSignature = _GetFileSignature(zhash, zfi.Name, zfi.Extension, zfi.Length, file, true); gaseous_server.Models.Signatures_Games zDiscoveredSignature = _GetFileSignature(zhash, zfi.Name, zfi.Extension, zfi.Length, file, true);
@@ -138,7 +134,7 @@ namespace gaseous_server.Classes
if (zDiscoveredSignature.Score > discoveredSignature.Score) if (zDiscoveredSignature.Score > discoveredSignature.Score)
{ {
if ( if (
zDiscoveredSignature.Rom.SignatureSource == gaseous_server.Models.Signatures_Games.RomItem.SignatureSourceType.MAMEArcade || zDiscoveredSignature.Rom.SignatureSource == gaseous_server.Models.Signatures_Games.RomItem.SignatureSourceType.MAMEArcade ||
zDiscoveredSignature.Rom.SignatureSource == gaseous_server.Models.Signatures_Games.RomItem.SignatureSourceType.MAMEMess zDiscoveredSignature.Rom.SignatureSource == gaseous_server.Models.Signatures_Games.RomItem.SignatureSourceType.MAMEMess
) )
{ {
@@ -151,15 +147,37 @@ namespace gaseous_server.Classes
discoveredSignature = zDiscoveredSignature; discoveredSignature = zDiscoveredSignature;
signatureFound = true; signatureFound = true;
if (signatureSelectorAlreadyApplied == false)
{
signatureSelector = true;
signatureSelectorAlreadyApplied = true;
}
} }
} }
ArchiveData archiveData = new ArchiveData
{
FileName = Path.GetFileName(file),
FilePath = zfi.Directory.FullName.Replace(ExtractPath, ""),
Size = zfi.Length,
MD5 = zhash.md5hash,
SHA1 = zhash.sha1hash,
isSignatureSelector = signatureSelector
};
archiveFiles.Add(archiveData);
} }
} }
} }
discoveredSignature.Rom.Attributes.Add(new KeyValuePair<string, object>( if (discoveredSignature.Rom.Attributes == null)
{
discoveredSignature.Rom.Attributes = new Dictionary<string, object>();
}
discoveredSignature.Rom.Attributes.Add(
"ZipContents", Newtonsoft.Json.JsonConvert.SerializeObject(archiveFiles) "ZipContents", Newtonsoft.Json.JsonConvert.SerializeObject(archiveFiles)
)); );
} }
catch (Exception ex) catch (Exception ex)
{ {
@@ -175,37 +193,45 @@ namespace gaseous_server.Classes
Logging.Log(Logging.LogType.Information, "Import Game", "Checking signature for file: " + GameFileImportPath + "\nMD5 hash: " + hash.md5hash + "\nSHA1 hash: " + hash.sha1hash); Logging.Log(Logging.LogType.Information, "Import Game", "Checking signature for file: " + GameFileImportPath + "\nMD5 hash: " + hash.md5hash + "\nSHA1 hash: " + hash.sha1hash);
gaseous_server.Models.Signatures_Games discoveredSignature = new gaseous_server.Models.Signatures_Games(); gaseous_server.Models.Signatures_Games? discoveredSignature = null;
// do database search first // begin signature search
gaseous_server.Models.Signatures_Games? dbSignature = _GetFileSignatureFromDatabase(hash, ImageName, ImageExtension, ImageSize, GameFileImportPath); switch (Config.MetadataConfiguration.SignatureSource)
if (dbSignature != null)
{ {
// local signature found case HasheousClient.Models.MetadataModel.SignatureSources.LocalOnly:
Logging.Log(Logging.LogType.Information, "Import Game", "Signature found in local database for game: " + dbSignature.Game.Name); Logging.Log(Logging.LogType.Information, "Import Game", "Hasheous disabled - searching local database only");
discoveredSignature = dbSignature;
discoveredSignature = _GetFileSignatureFromDatabase(hash, ImageName, ImageExtension, ImageSize, GameFileImportPath);
break;
case HasheousClient.Models.MetadataModel.SignatureSources.Hasheous:
Logging.Log(Logging.LogType.Information, "Import Game", "Hasheous enabled - searching Hashesous and then local database if not found");
discoveredSignature = _GetFileSignatureFromHasheous(hash, ImageName, ImageExtension, ImageSize, GameFileImportPath);
if (discoveredSignature == null)
{
Logging.Log(Logging.LogType.Information, "Import Game", "Signature not found in Hasheous - checking local database");
discoveredSignature = _GetFileSignatureFromDatabase(hash, ImageName, ImageExtension, ImageSize, GameFileImportPath);
}
else
{
Logging.Log(Logging.LogType.Information, "Import Game", "Signature retrieved from Hasheous for game: " + discoveredSignature.Game.Name);
}
break;
} }
else
{
// no local signature attempt to pull from Hasheous
dbSignature = _GetFileSignatureFromHasheous(hash, ImageName, ImageExtension, ImageSize, GameFileImportPath);
if (dbSignature != null) if (discoveredSignature == null)
{ {
// signature retrieved from Hasheous // construct a signature from file data
Logging.Log(Logging.LogType.Information, "Import Game", "Signature retrieved from Hasheous for game: " + dbSignature.Game.Name); Logging.Log(Logging.LogType.Information, "Import Game", "Signature not found in local database or Hasheous (if enabled) - generating from file data");
discoveredSignature = dbSignature; discoveredSignature = _GetFileSignatureFromFileData(hash, ImageName, ImageExtension, ImageSize, GameFileImportPath);
}
else Logging.Log(Logging.LogType.Information, "Import Game", "Signature generated from provided file for game: " + discoveredSignature.Game.Name);
{
// 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;
}
} }
gaseous_server.Models.PlatformMapping.GetIGDBPlatformMapping(ref discoveredSignature, ImageExtension, false); gaseous_server.Models.PlatformMapping.GetIGDBPlatformMapping(ref discoveredSignature, ImageExtension, false);
@@ -216,8 +242,8 @@ namespace gaseous_server.Classes
return discoveredSignature; return discoveredSignature;
} }
private gaseous_server.Models.Signatures_Games? _GetFileSignatureFromDatabase(Common.hashObject hash, string ImageName, string ImageExtension, long ImageSize, string GameFileImportPath) private gaseous_server.Models.Signatures_Games? _GetFileSignatureFromDatabase(Common.hashObject hash, string ImageName, string ImageExtension, long ImageSize, string GameFileImportPath)
{ {
Logging.Log(Logging.LogType.Information, "Get Signature", "Checking local database for MD5: " + hash.md5hash); Logging.Log(Logging.LogType.Information, "Get Signature", "Checking local database for MD5: " + hash.md5hash);
// check 1: do we have a signature for it? // check 1: do we have a signature for it?
@@ -264,37 +290,97 @@ namespace gaseous_server.Classes
if (Config.MetadataConfiguration.SignatureSource == HasheousClient.Models.MetadataModel.SignatureSources.Hasheous) if (Config.MetadataConfiguration.SignatureSource == HasheousClient.Models.MetadataModel.SignatureSources.Hasheous)
{ {
HasheousClient.Hasheous hasheous = new HasheousClient.Hasheous(); HasheousClient.Hasheous hasheous = new HasheousClient.Hasheous();
SignatureLookupItem? HasheousResult = hasheous.RetrieveFromHasheousAsync(new HashLookupModel{ Console.WriteLine(HasheousClient.WebApp.HttpHelper.BaseUri);
MD5 = hash.md5hash, LookupItemModel? HasheousResult = null;
SHA1 = hash.sha1hash try
});
if (HasheousResult != null)
{ {
if (HasheousResult.Signature != null) HasheousResult = hasheous.RetrieveFromHasheous(new HashLookupModel
{ {
gaseous_server.Models.Signatures_Games signature = new Models.Signatures_Games(); MD5 = hash.md5hash,
signature.Game = HasheousResult.Signature.Game; SHA1 = hash.sha1hash
signature.Rom = HasheousResult.Signature.Rom; });
if (HasheousResult.MetadataResults != null) if (HasheousResult != null)
{
if (HasheousResult.Signature != null)
{ {
if (HasheousResult.MetadataResults.Count > 0) gaseous_server.Models.Signatures_Games signature = new Models.Signatures_Games();
string gameJson = Newtonsoft.Json.JsonConvert.SerializeObject(HasheousResult.Signature.Game);
string romJson = Newtonsoft.Json.JsonConvert.SerializeObject(HasheousResult.Signature.Rom);
signature.Game = Newtonsoft.Json.JsonConvert.DeserializeObject<Models.Signatures_Games.GameItem>(gameJson);
signature.Rom = Newtonsoft.Json.JsonConvert.DeserializeObject<Models.Signatures_Games.RomItem>(romJson);
// get platform metadata
if (HasheousResult.Platform != null)
{ {
foreach (SignatureLookupItem.MetadataResult metadataResult in HasheousResult.MetadataResults) if (HasheousResult.Platform.metadata.Count > 0)
{ {
if (metadataResult.Source == MetadataModel.MetadataSources.IGDB) foreach (HasheousClient.Models.MetadataItem metadataResult in HasheousResult.Platform.metadata)
{ {
signature.Flags.IGDBPlatformId = (long)metadataResult.PlatformId; if (metadataResult.Id.Length > 0)
signature.Flags.IGDBGameId = (long)metadataResult.GameId; {
switch (metadataResult.Source)
{
case HasheousClient.Models.MetadataSources.IGDB:
signature.Flags.IGDBPlatformId = (long)Platforms.GetPlatform(metadataResult.Id, false).Id;
break;
}
}
} }
} }
} }
}
return signature; // get game metadata
if (HasheousResult.Metadata != null)
{
if (HasheousResult.Metadata.Count > 0)
{
foreach (HasheousClient.Models.MetadataItem metadataResult in HasheousResult.Metadata)
{
if (metadataResult.Id.Length > 0)
{
switch (metadataResult.Source)
{
case HasheousClient.Models.MetadataSources.IGDB:
signature.Flags.IGDBGameId = (long)Games.GetGame(metadataResult.Id, false, false, false).Id;
break;
}
}
}
}
}
return signature;
}
} }
} }
catch (AggregateException aggEx)
{
foreach (Exception ex in aggEx.InnerExceptions)
{
// get exception type
if (ex is HttpRequestException)
{
if (ex.Message.Contains("404 (Not Found)"))
{
Logging.Log(Logging.LogType.Information, "Get Signature", "No signature found in Hasheous");
}
else
{
Logging.Log(Logging.LogType.Warning, "Get Signature", "Error retrieving signature from Hasheous", ex);
}
}
else
{
Logging.Log(Logging.LogType.Warning, "Get Signature", "Error retrieving signature from Hasheous", ex);
}
}
}
catch (Exception ex)
{
Logging.Log(Logging.LogType.Warning, "Get Signature", "Error retrieving signature from Hasheous", ex);
}
} }
return null; return null;
@@ -368,6 +454,7 @@ namespace gaseous_server.Classes
public long Size { get; set; } public long Size { get; set; }
public string MD5 { get; set; } public string MD5 { get; set; }
public string SHA1 { get; set; } public string SHA1 { get; set; }
public bool isSignatureSelector { get; set; } = false;
} }
} }
} }

View File

@@ -15,7 +15,7 @@ namespace gaseous_server.Classes
// platforms // platforms
List<FilterItem> platforms = new List<FilterItem>(); List<FilterItem> platforms = new List<FilterItem>();
string ageRestriction_Platform = "AgeGroup.AgeGroupId <= " + (int)MaximumAgeRestriction; string ageRestriction_Platform = "AgeGroup.AgeGroupId <= " + (int)MaximumAgeRestriction;
string ageRestriction_Generic = "view_Games.AgeGroupId <= " + (int)MaximumAgeRestriction; string ageRestriction_Generic = "view_Games.AgeGroupId <= " + (int)MaximumAgeRestriction;
if (IncludeUnrated == true) if (IncludeUnrated == true)
@@ -24,9 +24,9 @@ namespace gaseous_server.Classes
ageRestriction_Generic += " OR view_Games.AgeGroupId IS NULL"; ageRestriction_Generic += " OR view_Games.AgeGroupId IS NULL";
} }
string sql = "SELECT Platform.Id, Platform.`Name`, COUNT(Game.Id) AS GameCount FROM (SELECT DISTINCT Game.Id, Games_Roms.PlatformId, 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 WHERE (" + ageRestriction_Platform + ") GROUP BY Game.Id , Games_Roms.PlatformId HAVING RomCount > 0) Game JOIN Platform ON Game.PlatformId = Platform.Id GROUP BY Platform.`Name`;"; string sql = "SELECT Platform.Id, Platform.`Name`, COUNT(Game.Id) AS GameCount FROM (SELECT DISTINCT Game.Id, view_Games_Roms.PlatformId, COUNT(view_Games_Roms.Id) AS RomCount FROM Game LEFT JOIN AgeGroup ON Game.Id = AgeGroup.GameId LEFT JOIN view_Games_Roms ON Game.Id = view_Games_Roms.GameId WHERE (" + ageRestriction_Platform + ") GROUP BY Game.Id , view_Games_Roms.PlatformId HAVING RomCount > 0) Game JOIN Platform ON Game.PlatformId = Platform.Id GROUP BY Platform.`Name`;";
DataTable dbResponse = db.ExecuteCMD(sql); DataTable dbResponse = db.ExecuteCMD(sql, new Database.DatabaseMemoryCacheOptions(CacheEnabled: true, ExpirationSeconds: 300));
foreach (DataRow dr in dbResponse.Rows) foreach (DataRow dr in dbResponse.Rows)
{ {
@@ -82,8 +82,8 @@ namespace gaseous_server.Classes
// age groups // age groups
List<FilterItem> agegroupings = new List<FilterItem>(); List<FilterItem> agegroupings = new List<FilterItem>();
sql = "SELECT Game.AgeGroupId, COUNT(Game.Id) AS GameCount FROM (SELECT DISTINCT Game.Id, 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 WHERE (" + ageRestriction_Platform + ") GROUP BY Game.Id HAVING RomCount > 0) Game GROUP BY Game.AgeGroupId ORDER BY Game.AgeGroupId DESC"; sql = "SELECT Game.AgeGroupId, COUNT(Game.Id) AS GameCount FROM (SELECT DISTINCT Game.Id, AgeGroup.AgeGroupId, COUNT(view_Games_Roms.Id) AS RomCount FROM Game LEFT JOIN AgeGroup ON Game.Id = AgeGroup.GameId LEFT JOIN view_Games_Roms ON Game.Id = view_Games_Roms.GameId WHERE (" + ageRestriction_Platform + ") GROUP BY Game.Id HAVING RomCount > 0) Game GROUP BY Game.AgeGroupId ORDER BY Game.AgeGroupId DESC";
dbResponse = db.ExecuteCMD(sql); dbResponse = db.ExecuteCMD(sql, new Database.DatabaseMemoryCacheOptions(CacheEnabled: true, ExpirationSeconds: 300));
foreach (DataRow dr in dbResponse.Rows) foreach (DataRow dr in dbResponse.Rows)
{ {
@@ -112,9 +112,9 @@ namespace gaseous_server.Classes
{ {
//string sql = "SELECT DISTINCT <ITEMNAME>.Id, <ITEMNAME>.`Name`, COUNT(view_Games.Id) AS GameCount FROM <ITEMNAME> LEFT JOIN Relation_Game_<ITEMNAME>s ON Relation_Game_<ITEMNAME>s.<ITEMNAME>sId = <ITEMNAME>.Id LEFT JOIN view_Games ON view_Games.Id = Relation_Game_<ITEMNAME>s.GameId WHERE (" + AgeRestriction_Generic + ") GROUP BY <ITEMNAME>.Id HAVING GameCount > 0 ORDER BY <ITEMNAME>.`Name`;"; //string sql = "SELECT DISTINCT <ITEMNAME>.Id, <ITEMNAME>.`Name`, COUNT(view_Games.Id) AS GameCount FROM <ITEMNAME> LEFT JOIN Relation_Game_<ITEMNAME>s ON Relation_Game_<ITEMNAME>s.<ITEMNAME>sId = <ITEMNAME>.Id LEFT JOIN view_Games ON view_Games.Id = Relation_Game_<ITEMNAME>s.GameId WHERE (" + AgeRestriction_Generic + ") GROUP BY <ITEMNAME>.Id HAVING GameCount > 0 ORDER BY <ITEMNAME>.`Name`;";
string sql = "SELECT <ITEMNAME>.Id, <ITEMNAME>.`Name`, COUNT(Game.Id) AS GameCount FROM (SELECT DISTINCT Game.Id, 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 WHERE (" + AgeRestriction + ") GROUP BY Game.Id HAVING RomCount > 0) Game JOIN Relation_Game_<ITEMNAME>s ON Game.Id = Relation_Game_<ITEMNAME>s.GameId JOIN <ITEMNAME> ON Relation_Game_<ITEMNAME>s.<ITEMNAME>sId = <ITEMNAME>.Id GROUP BY <ITEMNAME>.`Name` ORDER BY <ITEMNAME>.`Name`;"; string sql = "SELECT <ITEMNAME>.Id, <ITEMNAME>.`Name`, COUNT(Game.Id) AS GameCount FROM (SELECT DISTINCT Game.Id, AgeGroup.AgeGroupId, COUNT(view_Games_Roms.Id) AS RomCount FROM Game LEFT JOIN AgeGroup ON Game.Id = AgeGroup.GameId LEFT JOIN view_Games_Roms ON Game.Id = view_Games_Roms.GameId WHERE (" + AgeRestriction + ") GROUP BY Game.Id HAVING RomCount > 0) Game JOIN Relation_Game_<ITEMNAME>s ON Game.Id = Relation_Game_<ITEMNAME>s.GameId JOIN <ITEMNAME> ON Relation_Game_<ITEMNAME>s.<ITEMNAME>sId = <ITEMNAME>.Id GROUP BY <ITEMNAME>.`Name` ORDER BY <ITEMNAME>.`Name`;";
sql = sql.Replace("<ITEMNAME>", Name); sql = sql.Replace("<ITEMNAME>", Name);
DataTable dbResponse = db.ExecuteCMD(sql); DataTable dbResponse = db.ExecuteCMD(sql, new Database.DatabaseMemoryCacheOptions(CacheEnabled: true, ExpirationSeconds: 300));
return dbResponse; return dbResponse;
} }

View File

@@ -7,37 +7,37 @@ using Microsoft.CodeAnalysis.FlowAnalysis.DataFlow;
namespace gaseous_server namespace gaseous_server
{ {
public static class GameLibrary public static class GameLibrary
{ {
// exceptions // exceptions
public class PathExists : Exception public class PathExists : Exception
{ {
public PathExists(string path) : base("The library path " + path + " already exists.") public PathExists(string path) : base("The library path " + path + " already exists.")
{} { }
} }
public class PathNotFound : Exception public class PathNotFound : Exception
{ {
public PathNotFound(string path) : base("The path " + path + " does not exist.") public PathNotFound(string path) : base("The path " + path + " does not exist.")
{} { }
} }
public class LibraryNotFound : Exception public class LibraryNotFound : Exception
{ {
public LibraryNotFound(int LibraryId) : base("Library id " + LibraryId + " does not exist.") public LibraryNotFound(int LibraryId) : base("Library id " + LibraryId + " does not exist.")
{} { }
} }
public class CannotDeleteDefaultLibrary : Exception public class CannotDeleteDefaultLibrary : Exception
{ {
public CannotDeleteDefaultLibrary() : base("Unable to delete the default library.") public CannotDeleteDefaultLibrary() : base("Unable to delete the default library.")
{} { }
} }
public class CannotDeleteLibraryWhileScanIsActive : Exception public class CannotDeleteLibraryWhileScanIsActive : Exception
{ {
public CannotDeleteLibraryWhileScanIsActive() : base("Unable to delete library while a library scan is active. Wait for all scans to complete and try again") public CannotDeleteLibraryWhileScanIsActive() : base("Unable to delete library while a library scan is active. Wait for all scans to complete and try again")
{} { }
} }
// code // code
@@ -60,13 +60,25 @@ namespace gaseous_server
} }
} }
// update default library path
public static void UpdateDefaultLibraryPath()
{
Database db = new Database(Database.databaseType.MySql, Config.DatabaseConfiguration.ConnectionString);
string sql = "UPDATE GameLibraries SET Path=@path WHERE DefaultLibrary=1;";
Dictionary<string, object> dbDict = new Dictionary<string, object>
{
{ "path", Path.Combine(Config.LibraryConfiguration.LibraryRootDirectory, "Library") }
};
db.ExecuteCMD(sql, dbDict);
}
public static List<LibraryItem> GetLibraries public static List<LibraryItem> GetLibraries
{ {
get get
{ {
List<LibraryItem> libraryItems = new List<LibraryItem>(); List<LibraryItem> libraryItems = new List<LibraryItem>();
Database db = new Database(Database.databaseType.MySql, Config.DatabaseConfiguration.ConnectionString); Database db = new Database(Database.databaseType.MySql, Config.DatabaseConfiguration.ConnectionString);
string sql = "SELECT * FROM GameLibraries"; string sql = "SELECT * FROM GameLibraries ORDER BY `Name`;";
DataTable data = db.ExecuteCMD(sql); DataTable data = db.ExecuteCMD(sql);
foreach (DataRow row in data.Rows) foreach (DataRow row in data.Rows)
{ {
@@ -113,7 +125,7 @@ namespace gaseous_server
dbDict.Add("path", PathName); dbDict.Add("path", PathName);
dbDict.Add("defaultplatform", DefaultPlatformId); dbDict.Add("defaultplatform", DefaultPlatformId);
DataTable data = db.ExecuteCMD(sql, dbDict); DataTable data = db.ExecuteCMD(sql, dbDict);
int newLibraryId = (int)(long)data.Rows[0][0]; int newLibraryId = (int)(long)data.Rows[0][0];
Logging.Log(Logging.LogType.Information, "Library Management", "Created library " + Name + " at directory " + PathName); Logging.Log(Logging.LogType.Information, "Library Management", "Created library " + Name + " at directory " + PathName);
@@ -129,10 +141,10 @@ namespace gaseous_server
if (library.IsDefaultLibrary == false) if (library.IsDefaultLibrary == false)
{ {
// check for active library scans // check for active library scans
foreach(ProcessQueue.QueueItem item in ProcessQueue.QueueItems) foreach (ProcessQueue.QueueItem item in ProcessQueue.QueueItems)
{ {
if ( if (
(item.ItemType == ProcessQueue.QueueItemType.LibraryScan && item.ItemState == ProcessQueue.QueueItemState.Running) || (item.ItemType == ProcessQueue.QueueItemType.LibraryScan && item.ItemState == ProcessQueue.QueueItemState.Running) ||
(item.ItemType == ProcessQueue.QueueItemType.LibraryScanWorker && item.ItemState == ProcessQueue.QueueItemState.Running) (item.ItemType == ProcessQueue.QueueItemType.LibraryScanWorker && item.ItemState == ProcessQueue.QueueItemState.Running)
) )
{ {
@@ -174,7 +186,25 @@ namespace gaseous_server
throw new LibraryNotFound(LibraryId); throw new LibraryNotFound(LibraryId);
} }
} }
public static LibraryItem ScanLibrary(int LibraryId)
{
// add the library to scan to the queue
LibraryItem library = GetLibrary(LibraryId);
ImportGame.LibrariesToScan.Add(library);
// start the library scan if it's not already running
foreach (ProcessQueue.QueueItem item in ProcessQueue.QueueItems)
{
if (item.ItemType == ProcessQueue.QueueItemType.LibraryScan && item.ItemState != ProcessQueue.QueueItemState.Running)
{
item.ForceExecute();
}
}
return library;
}
public class LibraryItem public class LibraryItem
{ {
public LibraryItem(int Id, string Name, string Path, long DefaultPlatformId, bool IsDefaultLibrary) public LibraryItem(int Id, string Name, string Path, long DefaultPlatformId, bool IsDefaultLibrary)

View File

@@ -16,48 +16,52 @@ using HasheousClient.Models;
namespace gaseous_server.Classes namespace gaseous_server.Classes
{ {
public class ImportGame : QueueItemStatus public class ImportGame : QueueItemStatus
{ {
public void ProcessDirectory(string ImportPath) public void ProcessDirectory(string ImportPath)
{ {
if (Directory.Exists(ImportPath)) if (Directory.Exists(ImportPath))
{ {
string[] importContents = Directory.GetFiles(ImportPath, "*.*", SearchOption.AllDirectories); string[] importContents = Directory.GetFiles(ImportPath, "*.*", SearchOption.AllDirectories);
Logging.Log(Logging.LogType.Information, "Import Games", "Found " + importContents.Length + " files to process in import directory: " + ImportPath); Logging.Log(Logging.LogType.Information, "Import Games", "Found " + importContents.Length + " files to process in import directory: " + ImportPath);
// import files first // import files first
int importCount = 1; int importCount = 1;
foreach (string importContent in importContents) { foreach (string importContent in importContents)
{
SetStatus(importCount, importContents.Length, "Importing file: " + importContent); SetStatus(importCount, importContents.Length, "Importing file: " + importContent);
ImportGameFile(importContent, null); ImportGameFile(importContent, null);
importCount += 1; importCount += 1;
} }
ClearStatus(); ClearStatus();
DeleteOrphanedDirectories(ImportPath); DeleteOrphanedDirectories(ImportPath);
} }
else else
{ {
Logging.Log(Logging.LogType.Critical, "Import Games", "The import directory " + ImportPath + " does not exist."); Logging.Log(Logging.LogType.Critical, "Import Games", "The import directory " + ImportPath + " does not exist.");
throw new DirectoryNotFoundException("Invalid path: " + ImportPath); throw new DirectoryNotFoundException("Invalid path: " + ImportPath);
} }
} }
public void ImportGameFile(string GameFileImportPath, IGDB.Models.Platform? OverridePlatform) public Dictionary<string, object> ImportGameFile(string GameFileImportPath, IGDB.Models.Platform? OverridePlatform)
{ {
Dictionary<string, object> RetVal = new Dictionary<string, object>();
RetVal.Add("path", Path.GetFileName(GameFileImportPath));
Database db = new Database(Database.databaseType.MySql, Config.DatabaseConfiguration.ConnectionString); Database db = new Database(Database.databaseType.MySql, Config.DatabaseConfiguration.ConnectionString);
string sql = ""; string sql = "";
Dictionary<string, object> dbDict = new Dictionary<string, object>(); Dictionary<string, object> dbDict = new Dictionary<string, object>();
if (Common.SkippableFiles.Contains<string>(Path.GetFileName(GameFileImportPath), StringComparer.OrdinalIgnoreCase)) if (Common.SkippableFiles.Contains<string>(Path.GetFileName(GameFileImportPath), StringComparer.OrdinalIgnoreCase))
{ {
Logging.Log(Logging.LogType.Debug, "Import Game", "Skipping item " + GameFileImportPath); Logging.Log(Logging.LogType.Debug, "Import Game", "Skipping item " + GameFileImportPath);
} }
else else
{ {
FileInfo fi = new FileInfo(GameFileImportPath); FileInfo fi = new FileInfo(GameFileImportPath);
Common.hashObject hash = new Common.hashObject(GameFileImportPath); Common.hashObject hash = new Common.hashObject(GameFileImportPath);
@@ -66,8 +70,10 @@ namespace gaseous_server.Classes
if (IsBios == null) if (IsBios == null)
{ {
// file is a rom // file is a rom
RetVal.Add("type", "rom");
// check to make sure we don't already have this file imported // check to make sure we don't already have this file imported
sql = "SELECT COUNT(Id) AS count FROM Games_Roms WHERE MD5=@md5 AND SHA1=@sha1"; sql = "SELECT COUNT(Id) AS count FROM view_Games_Roms WHERE MD5=@md5 AND SHA1=@sha1";
dbDict.Add("md5", hash.md5hash); dbDict.Add("md5", hash.md5hash);
dbDict.Add("sha1", hash.sha1hash); dbDict.Add("sha1", hash.sha1hash);
DataTable importDB = db.ExecuteCMD(sql, dbDict); DataTable importDB = db.ExecuteCMD(sql, dbDict);
@@ -87,12 +93,14 @@ namespace gaseous_server.Classes
} }
File.Move(GameFileImportPath, targetPathWithFileName, true); File.Move(GameFileImportPath, targetPathWithFileName, true);
} }
// import source was the upload directory // import source was the upload directory
if (GameFileImportPath.StartsWith(Config.LibraryConfiguration.LibraryUploadDirectory)) if (GameFileImportPath.StartsWith(Config.LibraryConfiguration.LibraryUploadDirectory))
{ {
Logging.Log(Logging.LogType.Warning, "Import Game", " " + GameFileImportPath + " already in database - skipping import"); Logging.Log(Logging.LogType.Warning, "Import Game", " " + GameFileImportPath + " already in database - skipping import");
} }
RetVal.Add("status", "duplicate");
} }
else else
{ {
@@ -121,33 +129,54 @@ namespace gaseous_server.Classes
IGDB.Models.Game determinedGame = SearchForGame(discoveredSignature, discoveredSignature.Flags.IGDBPlatformId, true); IGDB.Models.Game determinedGame = SearchForGame(discoveredSignature, discoveredSignature.Flags.IGDBPlatformId, true);
// add to database // add to database
StoreROM(GameLibrary.GetDefaultLibrary, hash, determinedGame, determinedPlatform, discoveredSignature, GameFileImportPath); long RomId = StoreROM(GameLibrary.GetDefaultLibrary, hash, determinedGame, determinedPlatform, discoveredSignature, GameFileImportPath, 0, true);
// build return value
RetVal.Add("romid", RomId);
RetVal.Add("platform", determinedPlatform);
RetVal.Add("game", determinedGame);
RetVal.Add("signature", discoveredSignature);
RetVal.Add("status", "imported");
} }
} }
else else
{ {
// file is a bios // file is a bios
if (IsBios.WebEmulator != null) RetVal.Add("type", "bios");
RetVal.Add("status", "notimported");
foreach (Classes.Bios.BiosItem biosItem in Classes.Bios.GetBios())
{ {
foreach (Classes.Bios.BiosItem biosItem in Classes.Bios.GetBios()) if (biosItem.Available == false)
{ {
if (biosItem.Available == false && biosItem.hash == hash.md5hash) if (biosItem.hash == hash.md5hash)
{ {
string biosPath = biosItem.biosPath.Replace(biosItem.filename, ""); string biosPath = Path.Combine(Config.LibraryConfiguration.LibraryFirmwareDirectory, biosItem.hash + ".bios");
if (!Directory.Exists(biosPath)) Logging.Log(Logging.LogType.Information, "Import Game", " " + GameFileImportPath + " is a BIOS file - moving to " + biosPath);
{
Directory.CreateDirectory(biosPath);
}
File.Move(GameFileImportPath, biosItem.biosPath, true); File.Move(GameFileImportPath, biosItem.biosPath, true);
break; RetVal.Add("name", biosItem.filename);
RetVal.Add("platform", Platforms.GetPlatform(biosItem.platformid, false, false));
RetVal["status"] = "imported";
return RetVal;
}
}
else
{
if (biosItem.hash == hash.md5hash)
{
RetVal["status"] = "duplicate";
return RetVal;
} }
} }
} }
} }
} }
}
return RetVal;
}
public static IGDB.Models.Game SearchForGame(gaseous_server.Models.Signatures_Games Signature, long PlatformId, bool FullDownload) public static IGDB.Models.Game SearchForGame(gaseous_server.Models.Signatures_Games Signature, long PlatformId, bool FullDownload)
{ {
@@ -173,7 +202,7 @@ namespace gaseous_server.Classes
string GameName = Signature.Game.Name; string GameName = Signature.Game.Name;
List<string> SearchCandidates = GetSearchCandidates(GameName); List<string> SearchCandidates = GetSearchCandidates(GameName);
foreach (string SearchCandidate in SearchCandidates) foreach (string SearchCandidate in SearchCandidates)
{ {
bool GameFound = false; bool GameFound = false;
@@ -197,8 +226,10 @@ namespace gaseous_server.Classes
Logging.Log(Logging.LogType.Information, "Import Game", " " + games.Length + " search results found"); Logging.Log(Logging.LogType.Information, "Import Game", " " + games.Length + " search results found");
// quite likely we've found sequels and alternate types // quite likely we've found sequels and alternate types
foreach (Game game in games) { foreach (Game game in games)
if (game.Name == SearchCandidate) { {
if (game.Name == SearchCandidate)
{
// found game title matches the search candidate // found game title matches the search candidate
determinedGame = Metadata.Games.GetGame((long)games[0].Id, false, false, false); determinedGame = Metadata.Games.GetGame((long)games[0].Id, false, false, false);
Logging.Log(Logging.LogType.Information, "Import Game", "Found exact match!"); Logging.Log(Logging.LogType.Information, "Import Game", "Found exact match!");
@@ -273,7 +304,8 @@ namespace gaseous_server.Classes
// assumption: no games have () in their titles so we'll remove them // assumption: no games have () in their titles so we'll remove them
int idx = GameName.IndexOf('('); int idx = GameName.IndexOf('(');
if (idx >= 0) { if (idx >= 0)
{
GameName = GameName.Substring(0, idx); GameName = GameName.Substring(0, idx);
} }
@@ -294,7 +326,7 @@ namespace gaseous_server.Classes
return SearchCandidates; return SearchCandidates;
} }
public static long StoreROM(GameLibrary.LibraryItem library, Common.hashObject hash, IGDB.Models.Game determinedGame, IGDB.Models.Platform determinedPlatform, gaseous_server.Models.Signatures_Games discoveredSignature, string GameFileImportPath, long UpdateId = 0) public static long StoreROM(GameLibrary.LibraryItem library, Common.hashObject hash, IGDB.Models.Game determinedGame, IGDB.Models.Platform determinedPlatform, gaseous_server.Models.Signatures_Games discoveredSignature, string GameFileImportPath, long UpdateId = 0, bool SourceIsExternal = false)
{ {
Database db = new Database(Database.databaseType.MySql, Config.DatabaseConfiguration.ConnectionString); Database db = new Database(Database.databaseType.MySql, Config.DatabaseConfiguration.ConnectionString);
@@ -304,10 +336,11 @@ namespace gaseous_server.Classes
if (UpdateId == 0) if (UpdateId == 0)
{ {
sql = "INSERT INTO Games_Roms (PlatformId, GameId, Name, Size, CRC, MD5, SHA1, DevelopmentStatus, Attributes, RomType, RomTypeMedia, MediaLabel, Path, MetadataSource, MetadataGameName, MetadataVersion, LibraryId) VALUES (@platformid, @gameid, @name, @size, @crc, @md5, @sha1, @developmentstatus, @Attributes, @romtype, @romtypemedia, @medialabel, @path, @metadatasource, @metadatagamename, @metadataversion, @libraryid); SELECT CAST(LAST_INSERT_ID() AS SIGNED);"; sql = "INSERT INTO Games_Roms (PlatformId, GameId, Name, Size, CRC, MD5, SHA1, DevelopmentStatus, Attributes, RomType, RomTypeMedia, MediaLabel, RelativePath, MetadataSource, MetadataGameName, MetadataVersion, LibraryId, RomDataVersion) VALUES (@platformid, @gameid, @name, @size, @crc, @md5, @sha1, @developmentstatus, @Attributes, @romtype, @romtypemedia, @medialabel, @path, @metadatasource, @metadatagamename, @metadataversion, @libraryid, @romdataversion); SELECT CAST(LAST_INSERT_ID() AS SIGNED);";
} else }
else
{ {
sql = "UPDATE Games_Roms SET PlatformId=@platformid, GameId=@gameid, Name=@name, Size=@size, DevelopmentStatus=@developmentstatus, Attributes=@Attributes, RomType=@romtype, RomTypeMedia=@romtypemedia, MediaLabel=@medialabel, MetadataSource=@metadatasource, MetadataGameName=@metadatagamename, MetadataVersion=@metadataversion WHERE Id=@id;"; sql = "UPDATE Games_Roms SET PlatformId=@platformid, GameId=@gameid, Name=@name, Size=@size, DevelopmentStatus=@developmentstatus, Attributes=@Attributes, RomType=@romtype, RomTypeMedia=@romtypemedia, MediaLabel=@medialabel, MetadataSource=@metadatasource, MetadataGameName=@metadatagamename, MetadataVersion=@metadataversion, RomDataVersion=@romdataversion WHERE Id=@id;";
dbDict.Add("id", UpdateId); dbDict.Add("id", UpdateId);
} }
dbDict.Add("platformid", Common.ReturnValueIfNull(determinedPlatform.Id, 0)); dbDict.Add("platformid", Common.ReturnValueIfNull(determinedPlatform.Id, 0));
@@ -322,6 +355,7 @@ namespace gaseous_server.Classes
dbDict.Add("metadatagamename", discoveredSignature.Game.Name); dbDict.Add("metadatagamename", discoveredSignature.Game.Name);
dbDict.Add("metadataversion", 2); dbDict.Add("metadataversion", 2);
dbDict.Add("libraryid", library.Id); dbDict.Add("libraryid", library.Id);
dbDict.Add("romdataversion", 2);
if (discoveredSignature.Rom.Attributes != null) if (discoveredSignature.Rom.Attributes != null)
{ {
@@ -341,14 +375,21 @@ namespace gaseous_server.Classes
dbDict.Add("romtype", (int)discoveredSignature.Rom.RomType); dbDict.Add("romtype", (int)discoveredSignature.Rom.RomType);
dbDict.Add("romtypemedia", Common.ReturnValueIfNull(discoveredSignature.Rom.RomTypeMedia, "")); dbDict.Add("romtypemedia", Common.ReturnValueIfNull(discoveredSignature.Rom.RomTypeMedia, ""));
dbDict.Add("medialabel", Common.ReturnValueIfNull(discoveredSignature.Rom.MediaLabel, "")); dbDict.Add("medialabel", Common.ReturnValueIfNull(discoveredSignature.Rom.MediaLabel, ""));
dbDict.Add("path", GameFileImportPath);
string libraryRootPath = library.Path;
if (libraryRootPath.EndsWith(Path.DirectorySeparatorChar.ToString()) == false)
{
libraryRootPath += Path.DirectorySeparatorChar;
}
dbDict.Add("path", GameFileImportPath.Replace(libraryRootPath, ""));
DataTable romInsert = db.ExecuteCMD(sql, dbDict); DataTable romInsert = db.ExecuteCMD(sql, dbDict);
long romId = 0; long romId = 0;
if (UpdateId == 0) if (UpdateId == 0)
{ {
romId = (long)romInsert.Rows[0][0]; romId = (long)romInsert.Rows[0][0];
} else }
else
{ {
romId = UpdateId; romId = UpdateId;
} }
@@ -356,79 +397,89 @@ namespace gaseous_server.Classes
// move to destination // move to destination
if (library.IsDefaultLibrary == true) if (library.IsDefaultLibrary == true)
{ {
MoveGameFile(romId); MoveGameFile(romId, SourceIsExternal);
} }
return romId; return romId;
} }
public static string ComputeROMPath(long RomId) public static string ComputeROMPath(long RomId)
{ {
Classes.Roms.GameRomItem rom = Classes.Roms.GetRom(RomId);
// get metadata
IGDB.Models.Platform platform = gaseous_server.Classes.Metadata.Platforms.GetPlatform(rom.PlatformId);
IGDB.Models.Game game = gaseous_server.Classes.Metadata.Games.GetGame(rom.GameId, false, false, false);
// build path
string platformSlug = "Unknown Platform";
if (platform != null)
{
platformSlug = platform.Slug;
}
string gameSlug = "Unknown Title";
if (game != null)
{
gameSlug = game.Slug;
}
string DestinationPath = Path.Combine(GameLibrary.GetDefaultLibrary.Path, gameSlug, platformSlug);
if (!Directory.Exists(DestinationPath))
{
Directory.CreateDirectory(DestinationPath);
}
string DestinationPathName = Path.Combine(DestinationPath, rom.Name);
return DestinationPathName;
}
public static bool MoveGameFile(long RomId)
{
Classes.Roms.GameRomItem rom = Classes.Roms.GetRom(RomId); Classes.Roms.GameRomItem rom = Classes.Roms.GetRom(RomId);
string romPath = rom.Path;
// get metadata
IGDB.Models.Platform platform = gaseous_server.Classes.Metadata.Platforms.GetPlatform(rom.PlatformId);
IGDB.Models.Game game = gaseous_server.Classes.Metadata.Games.GetGame(rom.GameId, false, false, false);
// build path
string platformSlug = "Unknown Platform";
if (platform != null)
{
platformSlug = platform.Slug;
}
string gameSlug = "Unknown Title";
if (game != null)
{
gameSlug = game.Slug;
}
string DestinationPath = Path.Combine(GameLibrary.GetDefaultLibrary.Path, gameSlug, platformSlug);
if (!Directory.Exists(DestinationPath))
{
Directory.CreateDirectory(DestinationPath);
}
string DestinationPathName = Path.Combine(DestinationPath, rom.Name);
return DestinationPathName;
}
public static bool MoveGameFile(long RomId, bool SourceIsExternal)
{
Classes.Roms.GameRomItem rom = Classes.Roms.GetRom(RomId);
string romPath = rom.Path;
if (SourceIsExternal == true)
{
romPath = rom.RelativePath;
}
if (File.Exists(romPath)) if (File.Exists(romPath))
{ {
string DestinationPath = ComputeROMPath(RomId); string DestinationPath = ComputeROMPath(RomId);
if (romPath == DestinationPath) if (romPath == DestinationPath)
{ {
Logging.Log(Logging.LogType.Debug, "Move Game ROM", "Destination path is the same as the current path - aborting"); Logging.Log(Logging.LogType.Debug, "Move Game ROM", "Destination path is the same as the current path - aborting");
return true; return true;
} }
else else
{ {
Logging.Log(Logging.LogType.Information, "Move Game ROM", "Moving " + romPath + " to " + DestinationPath); Logging.Log(Logging.LogType.Information, "Move Game ROM", "Moving " + romPath + " to " + DestinationPath);
if (File.Exists(DestinationPath)) if (File.Exists(DestinationPath))
{ {
Logging.Log(Logging.LogType.Information, "Move Game ROM", "A file with the same name exists at the destination - aborting"); Logging.Log(Logging.LogType.Information, "Move Game ROM", "A file with the same name exists at the destination - aborting");
return false; return false;
} }
else else
{ {
File.Move(romPath, DestinationPath); File.Move(romPath, DestinationPath);
// update the db // update the db
Database db = new Database(Database.databaseType.MySql, Config.DatabaseConfiguration.ConnectionString); Database db = new Database(Database.databaseType.MySql, Config.DatabaseConfiguration.ConnectionString);
string sql = "UPDATE Games_Roms SET Path=@path WHERE Id=@id"; string sql = "UPDATE Games_Roms SET RelativePath=@path WHERE Id=@id";
Dictionary<string, object> dbDict = new Dictionary<string, object>(); Dictionary<string, object> dbDict = new Dictionary<string, object>();
dbDict.Add("id", RomId); dbDict.Add("id", RomId);
dbDict.Add("path", DestinationPath);
db.ExecuteCMD(sql, dbDict); string libraryRootPath = rom.Library.Path;
if (libraryRootPath.EndsWith(Path.DirectorySeparatorChar.ToString()) == false)
{
libraryRootPath += Path.DirectorySeparatorChar;
}
dbDict.Add("path", DestinationPath.Replace(libraryRootPath, ""));
db.ExecuteCMD(sql, dbDict);
return true; return true;
} }
} }
} }
else else
{ {
@@ -437,31 +488,33 @@ namespace gaseous_server.Classes
} }
} }
public void OrganiseLibrary() public void OrganiseLibrary()
{ {
Logging.Log(Logging.LogType.Information, "Organise Library", "Starting default library organisation"); Logging.Log(Logging.LogType.Information, "Organise Library", "Starting default library organisation");
GameLibrary.LibraryItem library = GameLibrary.GetDefaultLibrary; GameLibrary.LibraryItem library = GameLibrary.GetDefaultLibrary;
// move rom files to their new location // move rom files to their new location
Database db = new Database(Database.databaseType.MySql, Config.DatabaseConfiguration.ConnectionString); Database db = new Database(Database.databaseType.MySql, Config.DatabaseConfiguration.ConnectionString);
string sql = "SELECT * FROM Games_Roms WHERE LibraryId = @libraryid"; string sql = "SELECT * FROM view_Games_Roms WHERE LibraryId = @libraryid";
Dictionary<string, object> dbDict = new Dictionary<string, object>(); Dictionary<string, object> dbDict = new Dictionary<string, object>();
dbDict.Add("libraryid", library.Id); dbDict.Add("libraryid", library.Id);
DataTable romDT = db.ExecuteCMD(sql, dbDict); DataTable romDT = db.ExecuteCMD(sql, dbDict);
if (romDT.Rows.Count > 0) if (romDT.Rows.Count > 0)
{ {
foreach (DataRow dr in romDT.Rows) for (int i = 0; i < romDT.Rows.Count; i++)
{ {
Logging.Log(Logging.LogType.Information, "Organise Library", "Processing ROM " + dr["name"]); SetStatus(i, romDT.Rows.Count, "Processing file " + romDT.Rows[i]["name"]);
long RomId = (long)dr["id"]; Logging.Log(Logging.LogType.Information, "Organise Library", "(" + i + "/" + romDT.Rows.Count + ") Processing ROM " + romDT.Rows[i]["name"]);
MoveGameFile(RomId); long RomId = (long)romDT.Rows[i]["id"];
} MoveGameFile(RomId, false);
} }
}
ClearStatus();
// clean up empty directories // clean up empty directories
DeleteOrphanedDirectories(GameLibrary.GetDefaultLibrary.Path); DeleteOrphanedDirectories(GameLibrary.GetDefaultLibrary.Path);
Logging.Log(Logging.LogType.Information, "Organise Library", "Finsihed default library organisation"); Logging.Log(Logging.LogType.Information, "Organise Library", "Finsihed default library organisation");
} }
@@ -474,7 +527,7 @@ namespace gaseous_server.Classes
string[] files = Directory.GetFiles(directory); string[] files = Directory.GetFiles(directory);
string[] directories = Directory.GetDirectories(directory); string[] directories = Directory.GetDirectories(directory);
if (files.Length == 0 && if (files.Length == 0 &&
directories.Length == 0) directories.Length == 0)
{ {
@@ -483,61 +536,82 @@ namespace gaseous_server.Classes
} }
} }
public void LibraryScan(GameLibrary.LibraryItem? singleLibrary = null) public static List<GameLibrary.LibraryItem> LibrariesToScan = new List<GameLibrary.LibraryItem>();
public void LibraryScan()
{ {
int maxWorkers = Config.MetadataConfiguration.MaxLibraryScanWorkers; int maxWorkers = Config.MetadataConfiguration.MaxLibraryScanWorkers;
List<GameLibrary.LibraryItem> libraries = new List<GameLibrary.LibraryItem>(); if (LibrariesToScan.Count == 0)
if (singleLibrary == null)
{ {
libraries.AddRange(GameLibrary.GetLibraries); LibrariesToScan.AddRange(GameLibrary.GetLibraries);
}
else
{
libraries.Add(singleLibrary);
} }
// setup background tasks for each library // setup background tasks for each library
foreach (GameLibrary.LibraryItem library in libraries) do
{ {
Logging.Log(Logging.LogType.Information, "Library Scan", "Starting worker process for library " + library.Name); Logging.Log(Logging.LogType.Information, "Library Scan", "Library scan queue size: " + LibrariesToScan.Count);
ProcessQueue.QueueItem queue = new ProcessQueue.QueueItem(
ProcessQueue.QueueItemType.LibraryScanWorker,
1,
new List<ProcessQueue.QueueItemType>
{
ProcessQueue.QueueItemType.OrganiseLibrary,
ProcessQueue.QueueItemType.Rematcher
},
false,
true);
queue.Options = library;
queue.ForceExecute();
ProcessQueue.QueueItems.Add(queue); GameLibrary.LibraryItem library = LibrariesToScan[0];
LibrariesToScan.RemoveAt(0);
// check number of running tasks is less than maxWorkers // check if library is already being scanned
bool allowContinue; bool libraryAlreadyScanning = false;
do List<ProcessQueue.QueueItem> ProcessQueueItems = new List<ProcessQueue.QueueItem>();
ProcessQueueItems.AddRange(ProcessQueue.QueueItems);
foreach (ProcessQueue.QueueItem item in ProcessQueueItems)
{ {
allowContinue = true; if (item.ItemType == ProcessQueue.QueueItemType.LibraryScanWorker)
int currentWorkerCount = 0;
List<ProcessQueue.QueueItem> queueItems = new List<ProcessQueue.QueueItem>();
queueItems.AddRange(ProcessQueue.QueueItems);
foreach (ProcessQueue.QueueItem item in queueItems)
{ {
if (item.ItemType == ProcessQueue.QueueItemType.LibraryScanWorker) if (((GameLibrary.LibraryItem)item.Options).Id == library.Id)
{ {
currentWorkerCount += 1; libraryAlreadyScanning = true;
} }
} }
if (currentWorkerCount >= maxWorkers) }
if (libraryAlreadyScanning == false)
{
Logging.Log(Logging.LogType.Information, "Library Scan", "Starting worker process for library " + library.Name);
ProcessQueue.QueueItem queue = new ProcessQueue.QueueItem(
ProcessQueue.QueueItemType.LibraryScanWorker,
1,
new List<ProcessQueue.QueueItemType>
{
ProcessQueue.QueueItemType.OrganiseLibrary,
ProcessQueue.QueueItemType.Rematcher
},
false,
true)
{ {
allowContinue = false; Options = library
Thread.Sleep(60000); };
} queue.ForceExecute();
} while (allowContinue == false);
} ProcessQueue.QueueItems.Add(queue);
// check number of running tasks is less than maxWorkers
bool allowContinue;
do
{
allowContinue = true;
int currentWorkerCount = 0;
List<ProcessQueue.QueueItem> LibraryScan_QueueItems = new List<ProcessQueue.QueueItem>();
LibraryScan_QueueItems.AddRange(ProcessQueue.QueueItems);
foreach (ProcessQueue.QueueItem item in LibraryScan_QueueItems)
{
if (item.ItemType == ProcessQueue.QueueItemType.LibraryScanWorker)
{
currentWorkerCount += 1;
}
}
if (currentWorkerCount >= maxWorkers)
{
allowContinue = false;
Thread.Sleep(60000);
}
} while (allowContinue == false);
}
} while (LibrariesToScan.Count > 0);
bool WorkersStillWorking; bool WorkersStillWorking;
do do
@@ -557,11 +631,17 @@ namespace gaseous_server.Classes
} while (WorkersStillWorking == true); } while (WorkersStillWorking == true);
Logging.Log(Logging.LogType.Information, "Library Scan", "Library scan complete. All workers stopped"); Logging.Log(Logging.LogType.Information, "Library Scan", "Library scan complete. All workers stopped");
if (LibrariesToScan.Count > 0)
{
Logging.Log(Logging.LogType.Information, "Library Scan", "There are still libraries to scan. Restarting scan process");
LibraryScan();
}
} }
public void LibrarySpecificScan(GameLibrary.LibraryItem library) public void LibrarySpecificScan(GameLibrary.LibraryItem library)
{ {
Logging.Log(Logging.LogType.Information, "Library Scan", "Starting scan of library: " + library.Name); Logging.Log(Logging.LogType.Information, "Library Scan", "Starting scan of library: " + library.Name);
Database db = new Database(Database.databaseType.MySql, Config.DatabaseConfiguration.ConnectionString); Database db = new Database(Database.databaseType.MySql, Config.DatabaseConfiguration.ConnectionString);
@@ -572,7 +652,7 @@ namespace gaseous_server.Classes
dupDict.Add("libraryid", library.Id); dupDict.Add("libraryid", library.Id);
db.ExecuteCMD(duplicateSql, dupDict); db.ExecuteCMD(duplicateSql, dupDict);
string sql = "SELECT * FROM Games_Roms WHERE LibraryId=@libraryid ORDER BY `name`"; string sql = "SELECT * FROM view_Games_Roms WHERE LibraryId=@libraryid ORDER BY `name`";
Dictionary<string, object> dbDict = new Dictionary<string, object>(); Dictionary<string, object> dbDict = new Dictionary<string, object>();
dbDict.Add("libraryid", library.Id); dbDict.Add("libraryid", library.Id);
DataTable dtRoms = db.ExecuteCMD(sql, dbDict); DataTable dtRoms = db.ExecuteCMD(sql, dbDict);
@@ -597,7 +677,7 @@ namespace gaseous_server.Classes
} }
} }
sql = "SELECT * FROM Games_Roms WHERE LibraryId=@libraryid ORDER BY `name`"; sql = "SELECT * FROM view_Games_Roms WHERE LibraryId=@libraryid ORDER BY `name`";
dtRoms = db.ExecuteCMD(sql, dbDict); dtRoms = db.ExecuteCMD(sql, dbDict);
// search for files in the library that aren't in the database // search for files in the library that aren't in the database
@@ -630,7 +710,7 @@ namespace gaseous_server.Classes
{ {
// file is not in database - process it // file is not in database - process it
Logging.Log(Logging.LogType.Information, "Library Scan", "Orphaned file found in library: " + LibraryFile); Logging.Log(Logging.LogType.Information, "Library Scan", "Orphaned file found in library: " + LibraryFile);
Common.hashObject hash = new Common.hashObject(LibraryFile); Common.hashObject hash = new Common.hashObject(LibraryFile);
FileInfo fi = new FileInfo(LibraryFile); FileInfo fi = new FileInfo(LibraryFile);
@@ -642,8 +722,8 @@ namespace gaseous_server.Classes
// get discovered platform // get discovered platform
long PlatformId; long PlatformId;
IGDB.Models.Platform determinedPlatform; IGDB.Models.Platform determinedPlatform;
if (sig.Flags.IGDBPlatformId == null || sig.Flags.IGDBPlatformId == 0 ) if (sig.Flags.IGDBPlatformId == null || sig.Flags.IGDBPlatformId == 0)
{ {
// no platform discovered in the signature // no platform discovered in the signature
PlatformId = library.DefaultPlatformId; PlatformId = library.DefaultPlatformId;
@@ -669,7 +749,7 @@ namespace gaseous_server.Classes
} }
ClearStatus(); ClearStatus();
sql = "SELECT * FROM Games_Roms WHERE LibraryId=@libraryid ORDER BY `name`"; sql = "SELECT * FROM view_Games_Roms WHERE LibraryId=@libraryid ORDER BY `name`";
dtRoms = db.ExecuteCMD(sql, dbDict); dtRoms = db.ExecuteCMD(sql, dbDict);
// check all roms to see if their local file still exists // check all roms to see if their local file still exists
@@ -693,7 +773,7 @@ namespace gaseous_server.Classes
if (romPath != ComputeROMPath(romId)) if (romPath != ComputeROMPath(romId))
{ {
Logging.Log(Logging.LogType.Information, "Library Scan", "ROM at path " + romPath + " found, but needs to be moved"); Logging.Log(Logging.LogType.Information, "Library Scan", "ROM at path " + romPath + " found, but needs to be moved");
MoveGameFile(romId); MoveGameFile(romId, false);
} }
else else
{ {
@@ -734,11 +814,11 @@ namespace gaseous_server.Classes
string sql = ""; string sql = "";
if (ForceExecute == false) if (ForceExecute == false)
{ {
sql = "SELECT * FROM Games_Roms WHERE (PlatformId = 0 AND GameId <> 0) OR (((PlatformId = 0 OR GameId = 0) AND MetadataSource = 0) OR (PlatformId = 0 AND GameId = 0)) AND (LastMatchAttemptDate IS NULL OR LastMatchAttemptDate < @lastmatchattemptdate) AND LibraryId = @libraryid LIMIT 100;"; sql = "SELECT * FROM view_Games_Roms WHERE (PlatformId = 0 AND GameId <> 0) OR (((PlatformId = 0 OR GameId = 0) AND MetadataSource = 0) OR (PlatformId = 0 AND GameId = 0)) AND (LastMatchAttemptDate IS NULL OR LastMatchAttemptDate < @lastmatchattemptdate) AND LibraryId = @libraryid LIMIT 100;";
} }
else else
{ {
sql = "SELECT * FROM Games_Roms WHERE (PlatformId = 0 AND GameId <> 0) OR (((PlatformId = 0 OR GameId = 0) AND MetadataSource = 0) OR (PlatformId = 0 AND GameId = 0)) AND LibraryId = @libraryid;"; sql = "SELECT * FROM view_Games_Roms WHERE (PlatformId = 0 AND GameId <> 0) OR (((PlatformId = 0 OR GameId = 0) AND MetadataSource = 0) OR (PlatformId = 0 AND GameId = 0)) AND LibraryId = @libraryid;";
} }
Dictionary<string, object> dbDict = new Dictionary<string, object>(); Dictionary<string, object> dbDict = new Dictionary<string, object>();
dbDict.Add("lastmatchattemptdate", DateTime.UtcNow.AddDays(-7)); dbDict.Add("lastmatchattemptdate", DateTime.UtcNow.AddDays(-7));
@@ -768,8 +848,8 @@ namespace gaseous_server.Classes
// get discovered platform // get discovered platform
long PlatformId; long PlatformId;
IGDB.Models.Platform determinedPlatform; IGDB.Models.Platform determinedPlatform;
if (sig.Flags.IGDBPlatformId == null || sig.Flags.IGDBPlatformId == 0 ) if (sig.Flags.IGDBPlatformId == null || sig.Flags.IGDBPlatformId == 0)
{ {
// no platform discovered in the signature // no platform discovered in the signature
PlatformId = library.DefaultPlatformId; PlatformId = library.DefaultPlatformId;

View File

@@ -3,10 +3,11 @@ using System.Data;
using System.Diagnostics; using System.Diagnostics;
using System.Reflection; using System.Reflection;
using System.Reflection.Metadata.Ecma335; using System.Reflection.Metadata.Ecma335;
using Microsoft.VisualStudio.Web.CodeGenerators.Mvc.Templates.BlazorIdentity.Pages;
namespace gaseous_server.Classes namespace gaseous_server.Classes
{ {
public class Logging public class Logging
{ {
private static DateTime lastDiskRetentionSweep = DateTime.UtcNow; private static DateTime lastDiskRetentionSweep = DateTime.UtcNow;
public static bool WriteToDiskOnly { get; set; } = false; public static bool WriteToDiskOnly { get; set; } = false;
@@ -21,8 +22,13 @@ namespace gaseous_server.Classes
ExceptionValue = Common.ReturnValueIfNull(ExceptionValue, "").ToString() ExceptionValue = Common.ReturnValueIfNull(ExceptionValue, "").ToString()
}; };
_ = Task.Run(() => WriteLogAsync(logItem, LogToDiskOnly));
}
static async Task WriteLogAsync(LogItem logItem, bool LogToDiskOnly)
{
bool AllowWrite = false; bool AllowWrite = false;
if (EventType == LogType.Debug) if (logItem.EventType == LogType.Debug)
{ {
if (Config.LoggingConfiguration.DebugLogging == true) if (Config.LoggingConfiguration.DebugLogging == true)
{ {
@@ -42,26 +48,32 @@ namespace gaseous_server.Classes
{ {
TraceOutput += Environment.NewLine + logItem.ExceptionValue.ToString(); TraceOutput += Environment.NewLine + logItem.ExceptionValue.ToString();
} }
switch(logItem.EventType) { string consoleColour = "";
switch (logItem.EventType)
{
case LogType.Information: case LogType.Information:
Console.ForegroundColor = ConsoleColor.Blue; // Console.ForegroundColor = ConsoleColor.Blue;
consoleColour = "\u001b[1;34m]";
break; break;
case LogType.Warning: case LogType.Warning:
Console.ForegroundColor = ConsoleColor.Yellow; // Console.ForegroundColor = ConsoleColor.Yellow;
consoleColour = "\u001b[1;33m]";
break; break;
case LogType.Critical: case LogType.Critical:
Console.ForegroundColor = ConsoleColor.Red; // Console.ForegroundColor = ConsoleColor.Red;
consoleColour = "\u001b[1;31m]";
break; break;
case LogType.Debug: case LogType.Debug:
Console.ForegroundColor = ConsoleColor.Magenta; // Console.ForegroundColor = ConsoleColor.Magenta;
consoleColour = "\u001b[1;36m]";
break; break;
} }
Console.WriteLine(TraceOutput); Console.WriteLine(consoleColour + TraceOutput);
Console.ResetColor(); // Console.ResetColor();
if (WriteToDiskOnly == true) if (WriteToDiskOnly == true)
{ {
@@ -143,7 +155,7 @@ namespace gaseous_server.Classes
db.ExecuteCMD(sql, dbDict); db.ExecuteCMD(sql, dbDict);
} }
catch (Exception ex) catch (Exception ex)
{ {
LogToDisk(logItem, TraceOutput, ex); LogToDisk(logItem, TraceOutput, ex);
} }
} }
@@ -162,9 +174,9 @@ namespace gaseous_server.Classes
foreach (string file in files) foreach (string file in files)
{ {
FileInfo fi = new FileInfo(file); FileInfo fi = new FileInfo(file);
if (fi.LastAccessTime < DateTime.Now.AddDays(Config.LoggingConfiguration.LogRetention * -1)) if (fi.LastAccessTime < DateTime.Now.AddDays(Config.LoggingConfiguration.LogRetention * -1))
{ {
fi.Delete(); fi.Delete();
} }
} }
} }
@@ -176,7 +188,7 @@ namespace gaseous_server.Classes
{ {
// dump the error // dump the error
File.AppendAllText(Config.LogFilePath, logItem.EventTime.ToString("yyyyMMdd HHmmss") + ": " + logItem.EventType.ToString() + ": " + logItem.Process + ": " + logItem.Message + Environment.NewLine + exception.ToString()); File.AppendAllText(Config.LogFilePath, logItem.EventTime.ToString("yyyyMMdd HHmmss") + ": " + logItem.EventType.ToString() + ": " + logItem.Process + ": " + logItem.Message + Environment.NewLine + exception.ToString());
// something went wrong writing to the db // something went wrong writing to the db
File.AppendAllText(Config.LogFilePath, logItem.EventTime.ToString("yyyyMMdd HHmmss") + ": The following event was unable to be written to the log database:"); File.AppendAllText(Config.LogFilePath, logItem.EventTime.ToString("yyyyMMdd HHmmss") + ": The following event was unable to be written to the log database:");
@@ -185,7 +197,7 @@ namespace gaseous_server.Classes
File.AppendAllText(Config.LogFilePath, TraceOutput); File.AppendAllText(Config.LogFilePath, TraceOutput);
} }
static public List<LogItem> GetLogs(LogsViewModel model) static public List<LogItem> GetLogs(LogsViewModel model)
{ {
Database db = new Database(Database.databaseType.MySql, Config.DatabaseConfiguration.ConnectionString); Database db = new Database(Database.databaseType.MySql, Config.DatabaseConfiguration.ConnectionString);
Dictionary<string, object> dbDict = new Dictionary<string, object>(); Dictionary<string, object> dbDict = new Dictionary<string, object>();
@@ -278,7 +290,7 @@ namespace gaseous_server.Classes
{ {
whereClause = "WHERE " + whereClause; whereClause = "WHERE " + whereClause;
} }
sql = "SELECT ServerLogs.Id, ServerLogs.EventTime, ServerLogs.EventType, ServerLogs.`Process`, ServerLogs.Message, ServerLogs.Exception, ServerLogs.CorrelationId, ServerLogs.CallingProcess, Users.Email FROM ServerLogs LEFT JOIN Users ON ServerLogs.CallingUser = Users.Id " + whereClause + " ORDER BY ServerLogs.Id DESC LIMIT @PageSize OFFSET @PageNumber;"; sql = "SELECT ServerLogs.Id, ServerLogs.EventTime, ServerLogs.EventType, ServerLogs.`Process`, ServerLogs.Message, ServerLogs.Exception, ServerLogs.CorrelationId, ServerLogs.CallingProcess, Users.Email FROM ServerLogs LEFT JOIN Users ON ServerLogs.CallingUser = Users.Id " + whereClause + " ORDER BY ServerLogs.Id DESC LIMIT @PageSize OFFSET @PageNumber;";
} }
else else
@@ -287,7 +299,7 @@ namespace gaseous_server.Classes
{ {
whereClause = "AND " + whereClause; whereClause = "AND " + whereClause;
} }
sql = "SELECT ServerLogs.Id, ServerLogs.EventTime, ServerLogs.EventType, ServerLogs.`Process`, ServerLogs.Message, ServerLogs.Exception, ServerLogs.CorrelationId, ServerLogs.CallingProcess, Users.Email FROM ServerLogs LEFT JOIN Users ON ServerLogs.CallingUser = Users.Id WHERE ServerLogs.Id < @StartIndex " + whereClause + " ORDER BY ServerLogs.Id DESC LIMIT @PageSize OFFSET @PageNumber;"; sql = "SELECT ServerLogs.Id, ServerLogs.EventTime, ServerLogs.EventType, ServerLogs.`Process`, ServerLogs.Message, ServerLogs.Exception, ServerLogs.CorrelationId, ServerLogs.CallingProcess, Users.Email FROM ServerLogs LEFT JOIN Users ON ServerLogs.CallingUser = Users.Id WHERE ServerLogs.Id < @StartIndex " + whereClause + " ORDER BY ServerLogs.Id DESC LIMIT @PageSize OFFSET @PageNumber;";
} }
DataTable dataTable = db.ExecuteCMD(sql, dbDict); DataTable dataTable = db.ExecuteCMD(sql, dbDict);

View File

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

View File

@@ -46,7 +46,7 @@ namespace gaseous_server.Classes.Metadata
default: default:
throw new Exception("How did you get here?"); throw new Exception("How did you get here?");
} }
return RetVal; return RetVal;
} }
} }
@@ -72,33 +72,8 @@ namespace gaseous_server.Classes.Metadata
} }
// compile the ratings values into the ratings groups // compile the ratings values into the ratings groups
AgeRestrictionGroupings highestAgeGroup = AgeRestrictionGroupings.Unclassified; AgeRestrictionGroupings highestAgeGroup = GetAgeGroupFromAgeRatings(ageRatings);
foreach (AgeRating ageRating in ageRatings)
{
foreach (KeyValuePair<AgeRestrictionGroupings, AgeGroupItem> ageGroupItem in AgeGroupingsFlat)
{
PropertyInfo[] groupProps = typeof(AgeGroupItem).GetProperties();
foreach (PropertyInfo property in groupProps)
{
if (RatingsBoards.Contains(property.Name))
{
List<AgeRatingTitle> ratingBoard = (List<AgeRatingTitle>)property.GetValue(ageGroupItem.Value);
foreach (AgeRatingTitle ratingTitle in ratingBoard)
{
if (ageRating.Rating == ratingTitle)
{
if (highestAgeGroup < ageGroupItem.Key)
{
highestAgeGroup = ageGroupItem.Key;
}
}
}
}
}
}
}
// return the compiled ratings group // return the compiled ratings group
AgeGroup ageGroup = new AgeGroup(); AgeGroup ageGroup = new AgeGroup();
ageGroup.Id = game.Id; ageGroup.Id = game.Id;
@@ -111,7 +86,7 @@ namespace gaseous_server.Classes.Metadata
{ {
ageGroup.AgeGroupId = highestAgeGroup; ageGroup.AgeGroupId = highestAgeGroup;
} }
return ageGroup; return ageGroup;
} }
else else
@@ -120,7 +95,7 @@ namespace gaseous_server.Classes.Metadata
ageGroup.Id = game.Id; ageGroup.Id = game.Id;
ageGroup.GameId = game.Id; ageGroup.GameId = game.Id;
ageGroup.AgeGroupId = null; ageGroup.AgeGroupId = null;
return ageGroup; return ageGroup;
} }
} }
@@ -130,14 +105,47 @@ namespace gaseous_server.Classes.Metadata
ageGroup.Id = game.Id; ageGroup.Id = game.Id;
ageGroup.GameId = game.Id; ageGroup.GameId = game.Id;
ageGroup.AgeGroupId = null; ageGroup.AgeGroupId = null;
return ageGroup; return ageGroup;
} }
} }
return null; return null;
} }
public static AgeRestrictionGroupings GetAgeGroupFromAgeRatings(List<AgeRating> ageRatings)
{
// compile the ratings values into the ratings groups
AgeRestrictionGroupings highestAgeGroup = AgeRestrictionGroupings.Unclassified;
foreach (AgeRating ageRating in ageRatings)
{
foreach (KeyValuePair<AgeRestrictionGroupings, AgeGroupItem> ageGroupItem in AgeGroupingsFlat)
{
PropertyInfo[] groupProps = typeof(AgeGroupItem).GetProperties();
foreach (PropertyInfo property in groupProps)
{
if (RatingsBoards.Contains(property.Name))
{
List<AgeRatingTitle> ratingBoard = (List<AgeRatingTitle>)property.GetValue(ageGroupItem.Value);
foreach (AgeRatingTitle ratingTitle in ratingBoard)
{
if (ageRating.Rating == ratingTitle)
{
if (highestAgeGroup < ageGroupItem.Key)
{
highestAgeGroup = ageGroupItem.Key;
}
}
}
}
}
}
}
return highestAgeGroup;
}
public class AgeGroup public class AgeGroup
{ {
public long? Id { get; set; } public long? Id { get; set; }
@@ -150,8 +158,8 @@ namespace gaseous_server.Classes.Metadata
get get
{ {
return new Dictionary<AgeRestrictionGroupings, List<AgeGroupItem>>{ return new Dictionary<AgeRestrictionGroupings, List<AgeGroupItem>>{
{ {
AgeRestrictionGroupings.Adult, new List<AgeGroupItem>{ Adult_Item, Mature_Item, Teen_Item, Child_Item } AgeRestrictionGroupings.Adult, new List<AgeGroupItem>{ Adult_Item, Mature_Item, Teen_Item, Child_Item }
}, },
{ {
AgeRestrictionGroupings.Mature, new List<AgeGroupItem>{ Mature_Item, Teen_Item, Child_Item } AgeRestrictionGroupings.Mature, new List<AgeGroupItem>{ Mature_Item, Teen_Item, Child_Item }
@@ -159,7 +167,7 @@ namespace gaseous_server.Classes.Metadata
{ {
AgeRestrictionGroupings.Teen, new List<AgeGroupItem>{ Teen_Item, Child_Item } AgeRestrictionGroupings.Teen, new List<AgeGroupItem>{ Teen_Item, Child_Item }
}, },
{ {
AgeRestrictionGroupings.Child, new List<AgeGroupItem>{ Child_Item } AgeRestrictionGroupings.Child, new List<AgeGroupItem>{ Child_Item }
} }
}; };
@@ -208,44 +216,48 @@ namespace gaseous_server.Classes.Metadata
} }
} }
readonly static AgeGroupItem Adult_Item = new AgeGroupItem{ readonly static AgeGroupItem Adult_Item = new AgeGroupItem
ACB = new List<AgeRatingTitle>{ AgeRatingTitle.ACB_R18, AgeRatingTitle.ACB_RC }, {
CERO = new List<AgeRatingTitle>{ AgeRatingTitle.CERO_Z }, ACB = new List<AgeRatingTitle> { AgeRatingTitle.ACB_R18, AgeRatingTitle.ACB_RC },
CLASS_IND = new List<AgeRatingTitle>{ AgeRatingTitle.CLASS_IND_Eighteen }, CERO = new List<AgeRatingTitle> { AgeRatingTitle.CERO_Z },
ESRB = new List<AgeRatingTitle>{ AgeRatingTitle.RP, AgeRatingTitle.AO }, CLASS_IND = new List<AgeRatingTitle> { AgeRatingTitle.CLASS_IND_Eighteen },
GRAC = new List<AgeRatingTitle>{ AgeRatingTitle.GRAC_Eighteen }, ESRB = new List<AgeRatingTitle> { AgeRatingTitle.RP, AgeRatingTitle.AO },
PEGI = new List<AgeRatingTitle>{ AgeRatingTitle.Eighteen}, GRAC = new List<AgeRatingTitle> { AgeRatingTitle.GRAC_Eighteen },
USK = new List<AgeRatingTitle>{ AgeRatingTitle.USK_18} PEGI = new List<AgeRatingTitle> { AgeRatingTitle.Eighteen },
USK = new List<AgeRatingTitle> { AgeRatingTitle.USK_18 }
}; };
readonly static AgeGroupItem Mature_Item = new AgeGroupItem{ readonly static AgeGroupItem Mature_Item = new AgeGroupItem
ACB = new List<AgeRatingTitle>{ AgeRatingTitle.ACB_M, AgeRatingTitle.ACB_MA15 }, {
CERO = new List<AgeRatingTitle>{ AgeRatingTitle.CERO_C, AgeRatingTitle.CERO_D }, ACB = new List<AgeRatingTitle> { AgeRatingTitle.ACB_M, AgeRatingTitle.ACB_MA15 },
CLASS_IND = new List<AgeRatingTitle>{ AgeRatingTitle.CLASS_IND_Sixteen }, CERO = new List<AgeRatingTitle> { AgeRatingTitle.CERO_C, AgeRatingTitle.CERO_D },
ESRB = new List<AgeRatingTitle>{ AgeRatingTitle.M }, CLASS_IND = new List<AgeRatingTitle> { AgeRatingTitle.CLASS_IND_Sixteen },
GRAC = new List<AgeRatingTitle>{ AgeRatingTitle.GRAC_Fifteen }, ESRB = new List<AgeRatingTitle> { AgeRatingTitle.M },
PEGI = new List<AgeRatingTitle>{ AgeRatingTitle.Sixteen}, GRAC = new List<AgeRatingTitle> { AgeRatingTitle.GRAC_Fifteen },
USK = new List<AgeRatingTitle>{ AgeRatingTitle.USK_16} PEGI = new List<AgeRatingTitle> { AgeRatingTitle.Sixteen },
USK = new List<AgeRatingTitle> { AgeRatingTitle.USK_16 }
}; };
readonly static AgeGroupItem Teen_Item = new AgeGroupItem{ readonly static AgeGroupItem Teen_Item = new AgeGroupItem
ACB = new List<AgeRatingTitle>{ AgeRatingTitle.ACB_PG }, {
CERO = new List<AgeRatingTitle>{ AgeRatingTitle.CERO_B }, ACB = new List<AgeRatingTitle> { AgeRatingTitle.ACB_PG },
CLASS_IND = new List<AgeRatingTitle>{ AgeRatingTitle.CLASS_IND_Twelve, AgeRatingTitle.CLASS_IND_Fourteen }, CERO = new List<AgeRatingTitle> { AgeRatingTitle.CERO_B },
ESRB = new List<AgeRatingTitle>{ AgeRatingTitle.T }, CLASS_IND = new List<AgeRatingTitle> { AgeRatingTitle.CLASS_IND_Twelve, AgeRatingTitle.CLASS_IND_Fourteen },
GRAC = new List<AgeRatingTitle>{ AgeRatingTitle.GRAC_Twelve }, ESRB = new List<AgeRatingTitle> { AgeRatingTitle.T },
PEGI = new List<AgeRatingTitle>{ AgeRatingTitle.Twelve}, GRAC = new List<AgeRatingTitle> { AgeRatingTitle.GRAC_Twelve },
USK = new List<AgeRatingTitle>{ AgeRatingTitle.USK_12} PEGI = new List<AgeRatingTitle> { AgeRatingTitle.Twelve },
USK = new List<AgeRatingTitle> { AgeRatingTitle.USK_12 }
}; };
readonly static AgeGroupItem Child_Item = new AgeGroupItem{ readonly static AgeGroupItem Child_Item = new AgeGroupItem
ACB = new List<AgeRatingTitle>{ AgeRatingTitle.ACB_G }, {
CERO = new List<AgeRatingTitle>{ AgeRatingTitle.CERO_A }, ACB = new List<AgeRatingTitle> { AgeRatingTitle.ACB_G },
CLASS_IND = new List<AgeRatingTitle>{ AgeRatingTitle.CLASS_IND_L, AgeRatingTitle.CLASS_IND_Ten }, CERO = new List<AgeRatingTitle> { AgeRatingTitle.CERO_A },
ESRB = new List<AgeRatingTitle>{ AgeRatingTitle.E, AgeRatingTitle.E10 }, CLASS_IND = new List<AgeRatingTitle> { AgeRatingTitle.CLASS_IND_L, AgeRatingTitle.CLASS_IND_Ten },
GRAC = new List<AgeRatingTitle>{ AgeRatingTitle.GRAC_All }, ESRB = new List<AgeRatingTitle> { AgeRatingTitle.EC, AgeRatingTitle.E, AgeRatingTitle.E10 },
PEGI = new List<AgeRatingTitle>{ AgeRatingTitle.Three, AgeRatingTitle.Seven}, GRAC = new List<AgeRatingTitle> { AgeRatingTitle.GRAC_All },
USK = new List<AgeRatingTitle>{ AgeRatingTitle.USK_0, AgeRatingTitle.USK_6} PEGI = new List<AgeRatingTitle> { AgeRatingTitle.Three, AgeRatingTitle.Seven },
USK = new List<AgeRatingTitle> { AgeRatingTitle.USK_0, AgeRatingTitle.USK_6 }
}; };
public class AgeGroupItem public class AgeGroupItem

View File

@@ -5,7 +5,7 @@ using IGDB.Models;
namespace gaseous_server.Classes.Metadata namespace gaseous_server.Classes.Metadata
{ {
public class Artworks public class Artworks
{ {
const string fieldList = "fields alpha_channel,animated,checksum,game,height,image_id,url,width;"; const string fieldList = "fields alpha_channel,animated,checksum,game,height,image_id,url,width;";
@@ -68,13 +68,19 @@ namespace gaseous_server.Classes.Metadata
returnValue = await GetObjectFromServer(WhereClause, ImagePath); returnValue = await GetObjectFromServer(WhereClause, ImagePath);
Storage.NewCacheValue(returnValue); Storage.NewCacheValue(returnValue);
forceImageDownload = true; forceImageDownload = true;
break; break;
case Storage.CacheStatus.Expired: case Storage.CacheStatus.Expired:
try try
{ {
returnValue = await GetObjectFromServer(WhereClause, ImagePath); returnValue = await GetObjectFromServer(WhereClause, ImagePath);
Storage.NewCacheValue(returnValue, true); Storage.NewCacheValue(returnValue, true);
forceImageDownload = true;
// check if old value is different from the new value - only download if it's different
Artwork oldImage = Storage.GetCacheValue<Artwork>(returnValue, "id", (long)searchValue);
if (oldImage.ImageId != returnValue.ImageId)
{
forceImageDownload = true;
}
} }
catch (Exception ex) catch (Exception ex)
{ {
@@ -120,6 +126,6 @@ namespace gaseous_server.Classes.Metadata
return result; return result;
} }
} }
} }

View File

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

View File

@@ -191,10 +191,11 @@ namespace gaseous_server.Classes.Metadata
} }
try try
{ {
if (InRateLimitAvoidanceMode == true) if (InRateLimitAvoidanceMode == true)
{ {
// sleep for a moment to help avoid hitting the rate limiter // sleep for a moment to help avoid hitting the rate limiter
Logging.Log(Logging.LogType.Information, "API Connection: Endpoint:" + Endpoint, "IGDB rate limit hit. Pausing API communications for " + RateLimitAvoidanceWait + " milliseconds to avoid rate limiter.");
Thread.Sleep(RateLimitAvoidanceWait); Thread.Sleep(RateLimitAvoidanceWait);
} }
@@ -203,7 +204,7 @@ namespace gaseous_server.Classes.Metadata
// increment rate limiter avoidance call count // increment rate limiter avoidance call count
RateLimitAvoidanceCallCount += 1; RateLimitAvoidanceCallCount += 1;
return results; return results;
} }
catch (ApiException apiEx) catch (ApiException apiEx)
@@ -219,17 +220,33 @@ namespace gaseous_server.Classes.Metadata
else else
{ {
Logging.Log(Logging.LogType.Information, "API Connection", "IGDB API rate limit hit while accessing endpoint " + Endpoint, apiEx); Logging.Log(Logging.LogType.Information, "API Connection", "IGDB API rate limit hit while accessing endpoint " + Endpoint, apiEx);
RetryAttempts += 1; RetryAttempts += 1;
return await IGDBAPI<T>(Endpoint, Fields, Query); return await IGDBAPI<T>(Endpoint, Fields, Query);
} }
case HttpStatusCode.Unauthorized:
Logging.Log(Logging.LogType.Information, "API Connection", "IGDB API unauthorised error while accessing endpoint " + Endpoint + ". Waiting " + RateLimitAvoidanceWait + " milliseconds and resetting IGDB client.", apiEx);
Thread.Sleep(RateLimitAvoidanceWait);
igdb = new IGDBClient(
// Found in Twitch Developer portal for your app
Config.IGDB.ClientId,
Config.IGDB.Secret
);
RetryAttempts += 1;
return await IGDBAPI<T>(Endpoint, Fields, Query);
default: default:
Logging.Log(Logging.LogType.Warning, "API Connection", "Exception when accessing endpoint " + Endpoint, apiEx); Logging.Log(Logging.LogType.Warning, "API Connection", "Exception when accessing endpoint " + Endpoint, apiEx);
throw; throw;
} }
} }
catch(Exception ex) catch (Exception ex)
{ {
Logging.Log(Logging.LogType.Warning, "API Connection", "Exception when accessing endpoint " + Endpoint, ex); Logging.Log(Logging.LogType.Warning, "API Connection", "Exception when accessing endpoint " + Endpoint, ex);
throw; throw;
@@ -244,7 +261,7 @@ namespace gaseous_server.Classes.Metadata
public Task<bool?> DownloadFile(Uri uri, string DestinationFile) public Task<bool?> DownloadFile(Uri uri, string DestinationFile)
{ {
var result = _DownloadFile(uri, DestinationFile); var result = _DownloadFile(uri, DestinationFile);
return result; return result;
} }
@@ -339,7 +356,7 @@ namespace gaseous_server.Classes.Metadata
int width = 0; int width = 0;
int height = 0; int height = 0;
switch (size) switch (size)
{ {
case IGDBAPI_ImageSize.screenshot_small: case IGDBAPI_ImageSize.screenshot_small:
@@ -347,7 +364,7 @@ namespace gaseous_server.Classes.Metadata
width = 235; width = 235;
height = 128; height = 128;
break; break;
case IGDBAPI_ImageSize.screenshot_thumb: case IGDBAPI_ImageSize.screenshot_thumb:
// 165x90 // 165x90
width = 165; width = 165;
@@ -377,6 +394,7 @@ namespace gaseous_server.Classes.Metadata
if (InRateLimitAvoidanceMode == true) if (InRateLimitAvoidanceMode == true)
{ {
// sleep for a moment to help avoid hitting the rate limiter // sleep for a moment to help avoid hitting the rate limiter
Logging.Log(Logging.LogType.Information, "API Connection: Fetch Image", "IGDB rate limit hit. Pausing API communications for " + RateLimitAvoidanceWait + " milliseconds to avoid rate limiter.");
Thread.Sleep(RateLimitAvoidanceWait); Thread.Sleep(RateLimitAvoidanceWait);
} }
@@ -396,7 +414,7 @@ namespace gaseous_server.Classes.Metadata
{ {
await comms.IGDBAPI_GetImage(imageSizes, ImageId, ImagePath); await comms.IGDBAPI_GetImage(imageSizes, ImageId, ImagePath);
} }
} }
catch (HttpRequestException ex) catch (HttpRequestException ex)
{ {
@@ -481,14 +499,14 @@ namespace gaseous_server.Classes.Metadata
public async Task IGDBAPI_GetImage(List<IGDBAPI_ImageSize> ImageSizes, string ImageId, string OutputPath) public async Task IGDBAPI_GetImage(List<IGDBAPI_ImageSize> ImageSizes, string ImageId, string OutputPath)
{ {
string urlTemplate = "https://images.igdb.com/igdb/image/upload/t_{size}/{hash}.jpg"; string urlTemplate = "https://images.igdb.com/igdb/image/upload/t_{size}/{hash}.jpg";
foreach (IGDBAPI_ImageSize ImageSize in ImageSizes) foreach (IGDBAPI_ImageSize ImageSize in ImageSizes)
{ {
string url = urlTemplate.Replace("{size}", Common.GetDescription(ImageSize)).Replace("{hash}", ImageId); string url = urlTemplate.Replace("{size}", Common.GetDescription(ImageSize)).Replace("{hash}", ImageId);
string newOutputPath = Path.Combine(OutputPath, Common.GetDescription(ImageSize)); string newOutputPath = Path.Combine(OutputPath, Common.GetDescription(ImageSize));
string OutputFile = ImageId + ".jpg"; string OutputFile = ImageId + ".jpg";
string fullPath = Path.Combine(newOutputPath, OutputFile); string fullPath = Path.Combine(newOutputPath, OutputFile);
await _DownloadFile(new Uri(url), fullPath); await _DownloadFile(new Uri(url), fullPath);
} }
} }

View File

@@ -76,7 +76,13 @@ namespace gaseous_server.Classes.Metadata
{ {
returnValue = await GetObjectFromServer(WhereClause, ImagePath); returnValue = await GetObjectFromServer(WhereClause, ImagePath);
Storage.NewCacheValue(returnValue, true); Storage.NewCacheValue(returnValue, true);
forceImageDownload = true;
// check if old value is different from the new value - only download if it's different
CompanyLogo oldImage = Storage.GetCacheValue<CompanyLogo>(returnValue, "id", (long)searchValue);
if (oldImage.ImageId != returnValue.ImageId)
{
forceImageDownload = true;
}
} }
catch (Exception ex) catch (Exception ex)
{ {

View File

@@ -7,9 +7,9 @@ using Microsoft.CodeAnalysis.Elfie.Model.Strings;
namespace gaseous_server.Classes.Metadata namespace gaseous_server.Classes.Metadata
{ {
public class Covers public class Covers
{ {
const string fieldList = "fields alpha_channel,animated,checksum,game,height,image_id,url,width;"; const string fieldList = "fields alpha_channel,animated,checksum,game,game_localization,height,image_id,url,width;";
public Covers() public Covers()
{ {
@@ -70,13 +70,19 @@ namespace gaseous_server.Classes.Metadata
returnValue = await GetObjectFromServer(WhereClause, ImagePath); returnValue = await GetObjectFromServer(WhereClause, ImagePath);
Storage.NewCacheValue(returnValue); Storage.NewCacheValue(returnValue);
forceImageDownload = true; forceImageDownload = true;
break; break;
case Storage.CacheStatus.Expired: case Storage.CacheStatus.Expired:
try try
{ {
returnValue = await GetObjectFromServer(WhereClause, ImagePath); returnValue = await GetObjectFromServer(WhereClause, ImagePath);
Storage.NewCacheValue(returnValue, true); Storage.NewCacheValue(returnValue, true);
forceImageDownload = true;
// check if old value is different from the new value - only download if it's different
Cover oldCover = Storage.GetCacheValue<Cover>(returnValue, "id", (long)searchValue);
if (oldCover.ImageId != returnValue.ImageId)
{
forceImageDownload = true;
}
} }
catch (Exception ex) catch (Exception ex)
{ {
@@ -135,6 +141,6 @@ namespace gaseous_server.Classes.Metadata
return result; return result;
} }
} }
} }

View File

@@ -1,13 +1,14 @@
using System; using System;
using System.Data; using System.Data;
using gaseous_server.Models;
using IGDB; using IGDB;
using IGDB.Models; using IGDB.Models;
namespace gaseous_server.Classes.Metadata namespace gaseous_server.Classes.Metadata
{ {
public class Games public class Games
{ {
const string fieldList = "fields age_ratings,aggregated_rating,aggregated_rating_count,alternative_names,artworks,bundles,category,checksum,collection,cover,created_at,dlcs,expanded_games,expansions,external_games,first_release_date,follows,forks,franchise,franchises,game_engines,game_localizations,game_modes,genres,hypes,involved_companies,keywords,language_supports,multiplayer_modes,name,parent_game,platforms,player_perspectives,ports,rating,rating_count,release_dates,remakes,remasters,screenshots,similar_games,slug,standalone_expansions,status,storyline,summary,tags,themes,total_rating,total_rating_count,updated_at,url,version_parent,version_title,videos,websites;"; const string fieldList = "fields age_ratings,aggregated_rating,aggregated_rating_count,alternative_names,artworks,bundles,category,checksum,collections,cover,created_at,dlcs,expanded_games,expansions,external_games,first_release_date,follows,forks,franchise,franchises,game_engines,game_localizations,game_modes,genres,hypes,involved_companies,keywords,language_supports,multiplayer_modes,name,parent_game,platforms,player_perspectives,ports,rating,rating_count,release_dates,remakes,remasters,screenshots,similar_games,slug,standalone_expansions,status,storyline,summary,tags,themes,total_rating,total_rating_count,updated_at,url,version_parent,version_title,videos,websites;";
public Games() public Games()
{ {
@@ -15,9 +16,9 @@ namespace gaseous_server.Classes.Metadata
} }
public class InvalidGameId : Exception public class InvalidGameId : Exception
{ {
public InvalidGameId(long Id) : base("Unable to find Game by id " + Id) public InvalidGameId(long Id) : base("Unable to find Game by id " + Id)
{} { }
} }
public static Game? GetGame(long Id, bool getAllMetadata, bool followSubGames, bool forceRefresh) public static Game? GetGame(long Id, bool getAllMetadata, bool followSubGames, bool forceRefresh)
@@ -78,15 +79,17 @@ namespace gaseous_server.Classes.Metadata
if (cacheStatus == Storage.CacheStatus.Current) { cacheStatus = Storage.CacheStatus.Expired; } if (cacheStatus == Storage.CacheStatus.Current) { cacheStatus = Storage.CacheStatus.Expired; }
} }
// set up where clause
string WhereClause = ""; string WhereClause = "";
string searchField = "";
switch (searchUsing) switch (searchUsing)
{ {
case SearchUsing.id: case SearchUsing.id:
WhereClause = "where id = " + searchValue; WhereClause = "where id = " + searchValue;
searchField = "id";
break; break;
case SearchUsing.slug: case SearchUsing.slug:
WhereClause = "where slug = " + searchValue; WhereClause = "where slug = \"" + searchValue + "\"";
searchField = "slug";
break; break;
default: default:
throw new Exception("Invalid search type"); throw new Exception("Invalid search type");
@@ -110,11 +113,11 @@ namespace gaseous_server.Classes.Metadata
catch (Exception ex) catch (Exception ex)
{ {
Logging.Log(Logging.LogType.Warning, "Metadata: " + returnValue.GetType().Name, "An error occurred while connecting to IGDB. WhereClause: " + WhereClause, ex); Logging.Log(Logging.LogType.Warning, "Metadata: " + returnValue.GetType().Name, "An error occurred while connecting to IGDB. WhereClause: " + WhereClause, ex);
returnValue = Storage.GetCacheValue<Game>(returnValue, "id", (long)searchValue); returnValue = Storage.GetCacheValue<Game>(returnValue, searchField, searchValue);
} }
return returnValue; return returnValue;
case Storage.CacheStatus.Current: case Storage.CacheStatus.Current:
returnValue = Storage.GetCacheValue<Game>(returnValue, "id", (long)searchValue); returnValue = Storage.GetCacheValue<Game>(returnValue, searchField, searchValue);
UpdateSubClasses(returnValue, false, false, false); UpdateSubClasses(returnValue, false, false, false);
return returnValue; return returnValue;
default: default:
@@ -125,17 +128,17 @@ namespace gaseous_server.Classes.Metadata
private static void UpdateSubClasses(Game Game, bool getAllMetadata, bool followSubGames, bool forceRefresh) private static void UpdateSubClasses(Game Game, bool getAllMetadata, bool followSubGames, bool forceRefresh)
{ {
// required metadata // required metadata
if (Game.Cover != null) // if (Game.Cover != null)
{ // {
try // try
{ // {
Cover GameCover = Covers.GetCover(Game.Cover.Id, Config.LibraryConfiguration.LibraryMetadataDirectory_Game(Game), forceRefresh); // Cover GameCover = Covers.GetCover(Game.Cover.Id, Config.LibraryConfiguration.LibraryMetadataDirectory_Game(Game), forceRefresh);
} // }
catch (Exception ex) // catch (Exception ex)
{ // {
Logging.Log(Logging.LogType.Critical, "Game Metadata", "Unable to fetch cover artwork.", ex); // Logging.Log(Logging.LogType.Critical, "Game Metadata", "Unable to fetch cover artwork.", ex);
} // }
} // }
if (Game.Genres != null) if (Game.Genres != null)
{ {
@@ -285,7 +288,7 @@ namespace gaseous_server.Classes.Metadata
{ {
try try
{ {
Screenshot GameScreenshot = Screenshots.GetScreenshot(ScreenshotId, Config.LibraryConfiguration.LibraryMetadataDirectory_Game(Game), forceRefresh); Screenshot GameScreenshot = Screenshots.GetScreenshot(ScreenshotId, Config.LibraryConfiguration.LibraryMetadataDirectory_Game(Game), forceRefresh);
} }
catch (Exception ex) catch (Exception ex)
{ {
@@ -347,7 +350,7 @@ namespace gaseous_server.Classes.Metadata
// get missing metadata from parent if this is a port // get missing metadata from parent if this is a port
if (result.Category == Category.Port) if (result.Category == Category.Port)
{ {
if (result.Summary == null) if (result.Summary == null)
{ {
if (result.ParentGame != null) if (result.ParentGame != null)
@@ -364,7 +367,7 @@ namespace gaseous_server.Classes.Metadata
return result; return result;
} }
public static void AssignAllGamesToPlatformIdZero() public static void AssignAllGamesToPlatformIdZero()
{ {
Database db = new Database(Database.databaseType.MySql, Config.DatabaseConfiguration.ConnectionString); Database db = new Database(Database.databaseType.MySql, Config.DatabaseConfiguration.ConnectionString);
@@ -428,7 +431,7 @@ namespace gaseous_server.Classes.Metadata
} }
string sql = "SELECT Game.Id, Game.`Name`, Game.Slug, Relation_Game_Platforms.PlatformsId AS PlatformsId, Game.Summary FROM gaseous.Game JOIN Relation_Game_Platforms ON Game.Id = Relation_Game_Platforms.GameId WHERE " + whereClause + ";"; string sql = "SELECT Game.Id, Game.`Name`, Game.Slug, Relation_Game_Platforms.PlatformsId AS PlatformsId, Game.Summary FROM gaseous.Game JOIN Relation_Game_Platforms ON Game.Id = Relation_Game_Platforms.GameId WHERE " + whereClause + ";";
// get Game metadata // get Game metadata
Game[]? results = new Game[0]; Game[]? results = new Game[0];
@@ -439,7 +442,8 @@ namespace gaseous_server.Classes.Metadata
DataTable data = db.ExecuteCMD(sql, dbDict); DataTable data = db.ExecuteCMD(sql, dbDict);
foreach (DataRow row in data.Rows) foreach (DataRow row in data.Rows)
{ {
Game game = new Game{ Game game = new Game
{
Id = (long)row["Id"], Id = (long)row["Id"],
Name = (string)Common.ReturnValueIfNull(row["Name"], ""), Name = (string)Common.ReturnValueIfNull(row["Name"], ""),
Slug = (string)Common.ReturnValueIfNull(row["Slug"], ""), Slug = (string)Common.ReturnValueIfNull(row["Slug"], ""),
@@ -476,12 +480,12 @@ namespace gaseous_server.Classes.Metadata
searchBody = "where platforms = (" + PlatformId + ") & name ~ \"" + SearchString + "\";"; searchBody = "where platforms = (" + PlatformId + ") & name ~ \"" + SearchString + "\";";
break; break;
} }
// check search cache // check search cache
Game[]? games = Communications.GetSearchCache<Game[]?>(searchFields, searchBody); Game[]? games = Communications.GetSearchCache<Game[]?>(searchFields, searchBody);
if (games == null) if (games == null)
{ {
// cache miss // cache miss
// get Game metadata // get Game metadata
Communications comms = new Communications(); Communications comms = new Communications();
@@ -501,24 +505,161 @@ namespace gaseous_server.Classes.Metadata
} }
} }
public static List<KeyValuePair<long, string>> GetAvailablePlatforms(long GameId) public static List<AvailablePlatformItem> GetAvailablePlatforms(string UserId, long GameId)
{ {
Database db = new Database(Database.databaseType.MySql, Config.DatabaseConfiguration.ConnectionString); Database db = new Database(Database.databaseType.MySql, Config.DatabaseConfiguration.ConnectionString);
string sql = "SELECT DISTINCT Games_Roms.PlatformId, Platform.`Name` FROM Games_Roms LEFT JOIN Platform ON Games_Roms.PlatformId = Platform.Id WHERE Games_Roms.GameId = @gameid ORDER BY Platform.`Name`;"; string sql = @"
Dictionary<string, object> dbDict = new Dictionary<string, object>(); SELECT DISTINCT
dbDict.Add("gameid", GameId); view_Games_Roms.GameId,
view_Games_Roms.PlatformId,
Platform.`Name`,
User_RecentPlayedRoms.UserId AS MostRecentUserId,
User_RecentPlayedRoms.RomId AS MostRecentRomId,
CASE User_RecentPlayedRoms.IsMediaGroup
WHEN 0 THEN GMR.`Name`
WHEN 1 THEN 'Media Group'
ELSE NULL
END AS `MostRecentRomName`,
User_RecentPlayedRoms.IsMediaGroup AS MostRecentRomIsMediaGroup,
User_GameFavouriteRoms.UserId AS FavouriteUserId,
User_GameFavouriteRoms.RomId AS FavouriteRomId,
CASE User_GameFavouriteRoms.IsMediaGroup
WHEN 0 THEN GFV.`Name`
WHEN 1 THEN 'Media Group'
ELSE NULL
END AS `FavouriteRomName`,
User_GameFavouriteRoms.IsMediaGroup AS FavouriteRomIsMediaGroup
FROM
view_Games_Roms
LEFT JOIN
Platform ON view_Games_Roms.PlatformId = Platform.Id
LEFT JOIN
User_RecentPlayedRoms ON User_RecentPlayedRoms.UserId = @userid
AND User_RecentPlayedRoms.GameId = view_Games_Roms.GameId
AND User_RecentPlayedRoms.PlatformId = view_Games_Roms.PlatformId
LEFT JOIN
User_GameFavouriteRoms ON User_GameFavouriteRoms.UserId = @userid
AND User_GameFavouriteRoms.GameId = view_Games_Roms.GameId
AND User_GameFavouriteRoms.PlatformId = view_Games_Roms.PlatformId
LEFT JOIN
view_Games_Roms AS GMR ON GMR.Id = User_RecentPlayedRoms.RomId
LEFT JOIN
view_Games_Roms AS GFV ON GFV.Id = User_GameFavouriteRoms.RomId
WHERE
view_Games_Roms.GameId = @gameid
ORDER BY Platform.`Name`;";
Dictionary<string, object> dbDict = new Dictionary<string, object>
{
{ "gameid", GameId },
{ "userid", UserId }
};
DataTable data = db.ExecuteCMD(sql, dbDict); DataTable data = db.ExecuteCMD(sql, dbDict);
List<KeyValuePair<long, string>> platforms = new List<KeyValuePair<long, string>>(); PlatformMapping platformMapping = new PlatformMapping();
List<AvailablePlatformItem> platforms = new List<AvailablePlatformItem>();
foreach (DataRow row in data.Rows) foreach (DataRow row in data.Rows)
{ {
KeyValuePair<long, string> valuePair = new KeyValuePair<long, string>((long)row["PlatformId"], (string)row["Name"]); IGDB.Models.Platform platform = Platforms.GetPlatform((long)row["PlatformId"]);
platforms.Add(valuePair); PlatformMapping.UserEmulatorConfiguration? emulatorConfiguration = platformMapping.GetUserEmulator(UserId, GameId, (long)platform.Id);
if (emulatorConfiguration == null)
{
if (platform.Id != 0)
{
Models.PlatformMapping.PlatformMapItem platformMap = PlatformMapping.GetPlatformMap((long)platform.Id);
emulatorConfiguration = new PlatformMapping.UserEmulatorConfiguration
{
EmulatorType = platformMap.WebEmulator.Type,
Core = platformMap.WebEmulator.Core,
EnableBIOSFiles = platformMap.EnabledBIOSHashes
};
}
}
long? LastPlayedRomId = null;
bool? LastPlayedIsMediagroup = false;
string? LastPlayedRomName = null;
if (row["MostRecentRomId"] != DBNull.Value)
{
LastPlayedRomId = (long?)row["MostRecentRomId"];
LastPlayedIsMediagroup = (bool)row["MostRecentRomIsMediaGroup"];
if (row["MostRecentRomName"] != System.DBNull.Value)
{
LastPlayedRomName = string.IsNullOrEmpty((string?)row["MostRecentRomName"]) ? "" : (string)row["MostRecentRomName"];
}
}
long? FavouriteRomId = null;
bool? FavouriteRomIsMediagroup = false;
string? FavouriteRomName = null;
if (row["FavouriteRomId"] != DBNull.Value)
{
FavouriteRomId = (long?)row["FavouriteRomId"];
FavouriteRomIsMediagroup = (bool)row["FavouriteRomIsMediaGroup"];
if (row["MostRecentRomName"] != System.DBNull.Value)
{
FavouriteRomName = string.IsNullOrEmpty((string?)row["MostRecentRomName"]) ? "" : (string)row["MostRecentRomName"];
}
}
AvailablePlatformItem valuePair = new AvailablePlatformItem
{
Id = platform.Id,
Name = platform.Name,
Category = platform.Category,
emulatorConfiguration = emulatorConfiguration,
LastPlayedRomId = LastPlayedRomId,
LastPlayedRomIsMediagroup = LastPlayedIsMediagroup,
LastPlayedRomName = LastPlayedRomName,
FavouriteRomId = FavouriteRomId,
FavouriteRomIsMediagroup = FavouriteRomIsMediagroup,
FavouriteRomName = FavouriteRomName
};
platforms.Add(valuePair);
} }
return platforms; return platforms;
} }
public static void GameSetFavouriteRom(string UserId, long GameId, long PlatformId, long RomId, bool IsMediaGroup)
{
Database db = new Database(Database.databaseType.MySql, Config.DatabaseConfiguration.ConnectionString);
string sql = "DELETE FROM User_GameFavouriteRoms WHERE UserId = @userid AND GameId = @gameid AND PlatformId = @platformid; INSERT INTO User_GameFavouriteRoms (UserId, GameId, PlatformId, RomId, IsMediaGroup) VALUES (@userid, @gameid, @platformid, @romid, @ismediagroup);";
Dictionary<string, object> dbDict = new Dictionary<string, object>
{
{ "userid", UserId },
{ "gameid", GameId },
{ "platformid", PlatformId },
{ "romid", RomId },
{ "ismediagroup", IsMediaGroup }
};
db.ExecuteCMD(sql, dbDict);
}
public static void GameClearFavouriteRom(string UserId, long GameId, long PlatformId)
{
Database db = new Database(Database.databaseType.MySql, Config.DatabaseConfiguration.ConnectionString);
string sql = "DELETE FROM User_GameFavouriteRoms WHERE UserId = @userid AND GameId = @gameid AND PlatformId = @platformid;";
Dictionary<string, object> dbDict = new Dictionary<string, object>
{
{ "userid", UserId },
{ "gameid", GameId },
{ "platformid", PlatformId }
};
db.ExecuteCMD(sql, dbDict);
}
public class AvailablePlatformItem : IGDB.Models.Platform
{
public PlatformMapping.UserEmulatorConfiguration emulatorConfiguration { get; set; }
public long? LastPlayedRomId { get; set; }
public bool? LastPlayedRomIsMediagroup { get; set; }
public string? LastPlayedRomName { get; set; }
public long? FavouriteRomId { get; set; }
public bool? FavouriteRomIsMediagroup { get; set; }
public string? FavouriteRomName { get; set; }
}
public enum SearchType public enum SearchType
{ {
where = 0, where = 0,
@@ -533,11 +674,13 @@ namespace gaseous_server.Classes.Metadata
{ {
} }
public MinimalGameItem(Game gameObject) public MinimalGameItem(Game gameObject)
{ {
this.Id = gameObject.Id; this.Id = gameObject.Id;
this.Name = gameObject.Name; this.Name = gameObject.Name;
this.Slug = gameObject.Slug;
this.Summary = gameObject.Summary;
this.TotalRating = gameObject.TotalRating; this.TotalRating = gameObject.TotalRating;
this.TotalRatingCount = gameObject.TotalRatingCount; this.TotalRatingCount = gameObject.TotalRatingCount;
this.Cover = gameObject.Cover; this.Cover = gameObject.Cover;
@@ -560,10 +703,14 @@ namespace gaseous_server.Classes.Metadata
} }
public long? Id { get; set; } public long? Id { get; set; }
public long Index { get; set; }
public string Name { get; set; } public string Name { get; set; }
public string Slug { get; set; }
public string Summary { get; set; }
public double? TotalRating { get; set; } public double? TotalRating { get; set; }
public int? TotalRatingCount { get; set; } public int? TotalRatingCount { get; set; }
public bool HasSavedGame { get; set; } = false; public bool HasSavedGame { get; set; } = false;
public bool IsFavourite { get; set; } = false;
public DateTimeOffset? FirstReleaseDate { get; set; } public DateTimeOffset? FirstReleaseDate { get; set; }
public IGDB.IdentityOrValue<IGDB.Models.Cover> Cover { get; set; } public IGDB.IdentityOrValue<IGDB.Models.Cover> Cover { get; set; }
public IGDB.IdentitiesOrValues<IGDB.Models.Artwork> Artworks { get; set; } public IGDB.IdentitiesOrValues<IGDB.Models.Artwork> Artworks { get; set; }

View File

@@ -76,7 +76,13 @@ namespace gaseous_server.Classes.Metadata
{ {
returnValue = await GetObjectFromServer(WhereClause, ImagePath); returnValue = await GetObjectFromServer(WhereClause, ImagePath);
Storage.NewCacheValue(returnValue, true); Storage.NewCacheValue(returnValue, true);
forceImageDownload = true;
// check if old value is different from the new value - only download if it's different
PlatformLogo oldImage = Storage.GetCacheValue<PlatformLogo>(returnValue, "id", (long)searchValue);
if (oldImage.ImageId != returnValue.ImageId)
{
forceImageDownload = true;
}
} }
catch (Exception ex) catch (Exception ex)
{ {

View File

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

View File

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

View File

@@ -5,7 +5,7 @@ using IGDB.Models;
namespace gaseous_server.Classes.Metadata namespace gaseous_server.Classes.Metadata
{ {
public class Screenshots public class Screenshots
{ {
const string fieldList = "fields alpha_channel,animated,checksum,game,height,image_id,url,width;"; const string fieldList = "fields alpha_channel,animated,checksum,game,height,image_id,url,width;";
@@ -68,13 +68,19 @@ namespace gaseous_server.Classes.Metadata
returnValue = await GetObjectFromServer(WhereClause, ImagePath); returnValue = await GetObjectFromServer(WhereClause, ImagePath);
Storage.NewCacheValue(returnValue); Storage.NewCacheValue(returnValue);
forceImageDownload = true; forceImageDownload = true;
break; break;
case Storage.CacheStatus.Expired: case Storage.CacheStatus.Expired:
try try
{ {
returnValue = await GetObjectFromServer(WhereClause, ImagePath); returnValue = await GetObjectFromServer(WhereClause, ImagePath);
Storage.NewCacheValue(returnValue, true); Storage.NewCacheValue(returnValue, true);
forceImageDownload = true;
// check if old value is different from the new value - only download if it's different
Screenshot oldImage = Storage.GetCacheValue<Screenshot>(returnValue, "id", (long)searchValue);
if (oldImage.ImageId != returnValue.ImageId)
{
forceImageDownload = true;
}
} }
catch (Exception ex) catch (Exception ex)
{ {
@@ -120,6 +126,6 @@ namespace gaseous_server.Classes.Metadata
return result; return result;
} }
} }
} }

View File

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

View File

@@ -5,15 +5,18 @@ using Microsoft.VisualBasic;
using IGDB.Models; using IGDB.Models;
using gaseous_server.Classes.Metadata; using gaseous_server.Classes.Metadata;
using System.IO.Compression; using System.IO.Compression;
using SharpCompress.Archives;
using SharpCompress.Common;
using gaseous_server.Models;
namespace gaseous_server.Classes namespace gaseous_server.Classes
{ {
public class RomMediaGroup public class RomMediaGroup
{ {
public class InvalidMediaGroupId : Exception public class InvalidMediaGroupId : Exception
{ {
public InvalidMediaGroupId(long Id) : base("Unable to find media group by id " + Id) public InvalidMediaGroupId(long Id) : base("Unable to find media group by id " + Id)
{} { }
} }
public static GameRomMediaGroupItem CreateMediaGroup(long GameId, long PlatformId, List<long> RomIds) public static GameRomMediaGroupItem CreateMediaGroup(long GameId, long PlatformId, List<long> RomIds)
@@ -78,14 +81,42 @@ namespace gaseous_server.Classes
} }
} }
public static List<GameRomMediaGroupItem> GetMediaGroupsFromGameId(long GameId, string userid = "") public static List<GameRomMediaGroupItem> GetMediaGroupsFromGameId(long GameId, string userid = "", long? PlatformId = null)
{ {
string PlatformWhereClause = "";
if (PlatformId != null)
{
PlatformWhereClause = " AND RomMediaGroup.PlatformId=@platformid";
}
string UserFields = "";
string UserJoin = "";
if (userid.Length > 0)
{
UserFields = ", User_RecentPlayedRoms.RomId AS MostRecentRomId, User_RecentPlayedRoms.IsMediaGroup AS MostRecentRomIsMediaGroup, User_GameFavouriteRoms.RomId AS FavouriteRomId, User_GameFavouriteRoms.IsMediaGroup AS FavouriteRomIsMediaGroup";
UserJoin = @"
LEFT JOIN
User_RecentPlayedRoms ON User_RecentPlayedRoms.UserId = @userid
AND User_RecentPlayedRoms.GameId = RomMediaGroup.GameId
AND User_RecentPlayedRoms.PlatformId = RomMediaGroup.PlatformId
AND User_RecentPlayedRoms.RomId = RomMediaGroup.Id
AND User_RecentPlayedRoms.IsMediaGroup = 1
LEFT JOIN
User_GameFavouriteRoms ON User_GameFavouriteRoms.UserId = @userid
AND User_GameFavouriteRoms.GameId = RomMediaGroup.GameId
AND User_GameFavouriteRoms.PlatformId = RomMediaGroup.PlatformId
AND User_GameFavouriteRoms.RomId = RomMediaGroup.Id
AND User_GameFavouriteRoms.IsMediaGroup = 1
";
}
Database db = new Database(Database.databaseType.MySql, Config.DatabaseConfiguration.ConnectionString); Database db = new Database(Database.databaseType.MySql, Config.DatabaseConfiguration.ConnectionString);
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;"; string sql = "SELECT DISTINCT RomMediaGroup.*, GameState.RomId AS GameStateRomId" + UserFields + " FROM gaseous.RomMediaGroup LEFT JOIN GameState ON RomMediaGroup.Id = GameState.RomId AND GameState.IsMediaGroup = 1 AND GameState.UserId = @userid " + UserJoin + " WHERE RomMediaGroup.GameId=@gameid" + PlatformWhereClause + ";";
Dictionary<string, object> dbDict = new Dictionary<string, object> Dictionary<string, object> dbDict = new Dictionary<string, object>
{ {
{ "gameid", GameId }, { "gameid", GameId },
{ "userid", userid } { "userid", userid },
{ "platformid", PlatformId }
}; };
DataTable dataTable = db.ExecuteCMD(sql, dbDict); DataTable dataTable = db.ExecuteCMD(sql, dbDict);
@@ -162,7 +193,7 @@ namespace gaseous_server.Classes
public static void DeleteMediaGroup(long Id) public static void DeleteMediaGroup(long Id)
{ {
Database db = new Database(Database.databaseType.MySql, Config.DatabaseConfiguration.ConnectionString); Database db = new Database(Database.databaseType.MySql, Config.DatabaseConfiguration.ConnectionString);
string sql = "DELETE FROM RomMediaGroup WHERE Id=@id; DELETE FROM GameState WHERE RomId=@id AND IsMediaGroup=1;"; string sql = "DELETE FROM RomMediaGroup WHERE Id=@id; DELETE FROM GameState WHERE RomId=@id AND IsMediaGroup=1; UPDATE UserTimeTracking SET PlatformId = NULL, IsMediaGroup = NULL, RomId = NULL WHERE RomId=@id AND IsMediaGroup = 1;";
Dictionary<string, object> dbDict = new Dictionary<string, object>(); Dictionary<string, object> dbDict = new Dictionary<string, object>();
dbDict.Add("id", Id); dbDict.Add("id", Id);
db.ExecuteCMD(sql, dbDict); db.ExecuteCMD(sql, dbDict);
@@ -177,22 +208,42 @@ namespace gaseous_server.Classes
internal static GameRomMediaGroupItem BuildMediaGroupFromRow(DataRow row) internal static GameRomMediaGroupItem BuildMediaGroupFromRow(DataRow row)
{ {
bool hasSaveStates = false; bool hasSaveStates = false;
if (row.Table.Columns.Contains("GameStateRomId")) if (row.Table.Columns.Contains("GameStateRomId"))
{ {
if (row["GameStateRomId"] != DBNull.Value) if (row["GameStateRomId"] != DBNull.Value)
{ {
hasSaveStates = true; hasSaveStates = true;
} }
} }
GameRomMediaGroupItem mediaGroupItem = new GameRomMediaGroupItem(); GameRomMediaGroupItem mediaGroupItem = new GameRomMediaGroupItem
mediaGroupItem.Id = (long)row["Id"]; {
mediaGroupItem.Status = (GameRomMediaGroupItem.GroupBuildStatus)row["Status"]; Id = (long)row["Id"],
mediaGroupItem.PlatformId = (long)row["PlatformId"]; Status = (GameRomMediaGroupItem.GroupBuildStatus)row["Status"],
mediaGroupItem.GameId = (long)row["GameId"]; PlatformId = (long)row["PlatformId"],
mediaGroupItem.RomIds = new List<long>(); GameId = (long)row["GameId"],
mediaGroupItem.Roms = new List<Roms.GameRomItem>(); RomIds = new List<long>(),
mediaGroupItem.HasSaveStates = hasSaveStates; Roms = new List<Roms.GameRomItem>(),
HasSaveStates = hasSaveStates
};
mediaGroupItem.RomUserLastUsed = false;
if (row.Table.Columns.Contains("MostRecentRomId"))
{
if (row["MostRecentRomId"] != DBNull.Value)
{
mediaGroupItem.RomUserLastUsed = true;
}
}
mediaGroupItem.RomUserFavourite = false;
if (row.Table.Columns.Contains("FavouriteRomId"))
{
if (row["FavouriteRomId"] != DBNull.Value)
{
mediaGroupItem.RomUserFavourite = true;
}
}
// get members // get members
Database db = new Database(Database.databaseType.MySql, Config.DatabaseConfiguration.ConnectionString); Database db = new Database(Database.databaseType.MySql, Config.DatabaseConfiguration.ConnectionString);
@@ -213,18 +264,6 @@ namespace gaseous_server.Classes
} }
} }
// check for a web emulator and update the romItem
foreach (Models.PlatformMapping.PlatformMapItem platformMapping in Models.PlatformMapping.PlatformMap)
{
if (platformMapping.IGDBId == mediaGroupItem.PlatformId)
{
if (platformMapping.WebEmulator != null)
{
mediaGroupItem.Emulator = platformMapping.WebEmulator;
}
}
}
return mediaGroupItem; return mediaGroupItem;
} }
@@ -259,6 +298,7 @@ namespace gaseous_server.Classes
{ {
Game GameObject = Games.GetGame(mediaGroupItem.GameId, false, false, false); Game GameObject = Games.GetGame(mediaGroupItem.GameId, false, false, false);
Platform PlatformObject = Platforms.GetPlatform(mediaGroupItem.PlatformId, false); Platform PlatformObject = Platforms.GetPlatform(mediaGroupItem.PlatformId, false);
PlatformMapping.PlatformMapItem platformMapItem = PlatformMapping.GetPlatformMap(mediaGroupItem.PlatformId);
Logging.Log(Logging.LogType.Information, "Media Group", "Beginning build of media group: " + GameObject.Name + " for platform " + PlatformObject.Name); Logging.Log(Logging.LogType.Information, "Media Group", "Beginning build of media group: " + GameObject.Name + " for platform " + PlatformObject.Name);
@@ -293,10 +333,124 @@ namespace gaseous_server.Classes
foreach (long RomId in mediaGroupItem.RomIds) foreach (long RomId in mediaGroupItem.RomIds)
{ {
Roms.GameRomItem rom = Roms.GetRom(RomId); Roms.GameRomItem rom = Roms.GetRom(RomId);
bool fileNameFound = false;
if (File.Exists(rom.Path)) if (File.Exists(rom.Path))
{ {
Logging.Log(Logging.LogType.Information, "Media Group", "Copying ROM: " + rom.Name); string romExt = Path.GetExtension(rom.Path);
File.Copy(rom.Path, Path.Combine(ZipFileTempPath, Path.GetFileName(rom.Path))); if (new string[] { ".zip", ".rar", ".7z" }.Contains(romExt))
{
Logging.Log(Logging.LogType.Information, "Media Group", "Decompressing ROM: " + rom.Name);
// is compressed
switch (romExt)
{
case ".zip":
try
{
using (var archive = SharpCompress.Archives.Zip.ZipArchive.Open(rom.Path))
{
foreach (var entry in archive.Entries.Where(entry => !entry.IsDirectory))
{
Logging.Log(Logging.LogType.Information, "Media Group", "Extracting file: " + entry.Key);
if (fileNameFound == false)
{
//check if extension is in valid extensions
if (platformMapItem.Extensions.SupportedFileExtensions.Contains(Path.GetExtension(entry.Key), StringComparer.InvariantCultureIgnoreCase))
{
// update rom file name
rom.Name = entry.Key;
fileNameFound = true;
}
}
entry.WriteToDirectory(ZipFileTempPath, new ExtractionOptions()
{
ExtractFullPath = true,
Overwrite = true
});
}
}
}
catch (Exception zipEx)
{
Logging.Log(Logging.LogType.Warning, "Media Group", "Unzip error", zipEx);
throw;
}
break;
case ".rar":
try
{
using (var archive = SharpCompress.Archives.Rar.RarArchive.Open(rom.Path))
{
foreach (var entry in archive.Entries.Where(entry => !entry.IsDirectory))
{
Logging.Log(Logging.LogType.Information, "Media Group", "Extracting file: " + entry.Key);
if (fileNameFound == false)
{
//check if extension is in valid extensions
if (platformMapItem.Extensions.SupportedFileExtensions.Contains(Path.GetExtension(entry.Key), StringComparer.InvariantCultureIgnoreCase))
{
// update rom file name
rom.Name = entry.Key;
fileNameFound = true;
}
}
entry.WriteToDirectory(ZipFileTempPath, new ExtractionOptions()
{
ExtractFullPath = true,
Overwrite = true
});
}
}
}
catch (Exception zipEx)
{
Logging.Log(Logging.LogType.Warning, "Media Group", "Unrar error", zipEx);
throw;
}
break;
case ".7z":
try
{
using (var archive = SharpCompress.Archives.SevenZip.SevenZipArchive.Open(rom.Path))
{
foreach (var entry in archive.Entries.Where(entry => !entry.IsDirectory))
{
Logging.Log(Logging.LogType.Information, "Media Group", "Extracting file: " + entry.Key);
if (fileNameFound == false)
{
//check if extension is in valid extensions
if (platformMapItem.Extensions.SupportedFileExtensions.Contains(Path.GetExtension(entry.Key), StringComparer.InvariantCultureIgnoreCase))
{
// update rom file name
rom.Name = entry.Key;
fileNameFound = true;
}
}
entry.WriteToDirectory(ZipFileTempPath, new ExtractionOptions()
{
ExtractFullPath = true,
Overwrite = true
});
}
}
}
catch (Exception zipEx)
{
Logging.Log(Logging.LogType.Warning, "Media Group", "7z error", zipEx);
throw;
}
break;
}
}
else
{
// is uncompressed
Logging.Log(Logging.LogType.Information, "Media Group", "Copying ROM: " + rom.Name);
File.Copy(rom.Path, Path.Combine(ZipFileTempPath, Path.GetFileName(rom.Path)));
}
romItems.Add(rom); romItems.Add(rom);
} }
@@ -393,11 +547,12 @@ namespace gaseous_server.Classes
} }
public class GameRomMediaGroupItem public class GameRomMediaGroupItem
{ {
public long Id { get; set; } public long Id { get; set; }
public long GameId { get; set; } public long GameId { get; set; }
public long PlatformId { get; set; } public long PlatformId { get; set; }
public string Platform { public string Platform
{
get get
{ {
try try
@@ -410,37 +565,40 @@ namespace gaseous_server.Classes
} }
} }
} }
public Models.PlatformMapping.PlatformMapItem.WebEmulatorItem? Emulator { get; set; } public List<long> RomIds { get; set; }
public List<long> RomIds { get; set; }
public List<Roms.GameRomItem> Roms { get; set; } public List<Roms.GameRomItem> Roms { get; set; }
public bool HasSaveStates { get; set; } = false; public bool HasSaveStates { get; set; } = false;
private GroupBuildStatus _Status { get; set; } public bool RomUserLastUsed { get; set; }
public GroupBuildStatus Status { public bool RomUserFavourite { get; set; }
get private GroupBuildStatus _Status { get; set; }
{ public GroupBuildStatus Status
if (_Status == GroupBuildStatus.Completed) {
{ get
if (File.Exists(MediaGroupZipPath)) {
{ if (_Status == GroupBuildStatus.Completed)
return GroupBuildStatus.Completed; {
} if (File.Exists(MediaGroupZipPath))
else {
{ return GroupBuildStatus.Completed;
return GroupBuildStatus.NoStatus; }
} else
} {
else return GroupBuildStatus.NoStatus;
{ }
return _Status; }
} else
} {
return _Status;
}
}
set set
{ {
_Status = value; _Status = value;
} }
} }
public long? Size { public long? Size
get {
get
{ {
if (Status == GroupBuildStatus.Completed) if (Status == GroupBuildStatus.Completed)
{ {
@@ -459,15 +617,15 @@ namespace gaseous_server.Classes
return 0; return 0;
} }
} }
} }
internal string MediaGroupZipPath internal string MediaGroupZipPath
{ {
get get
{ {
return Path.Combine(Config.LibraryConfiguration.LibraryMediaGroupDirectory, Id + ".zip"); return Path.Combine(Config.LibraryConfiguration.LibraryMediaGroupDirectory, Id + ".zip");
} }
} }
public enum GroupBuildStatus public enum GroupBuildStatus
{ {
NoStatus = 0, NoStatus = 0,
WaitingForBuild = 1, WaitingForBuild = 1,
@@ -475,6 +633,6 @@ namespace gaseous_server.Classes
Completed = 3, Completed = 3,
Failed = 4 Failed = 4
} }
} }
} }
} }

View File

@@ -4,86 +4,117 @@ using gaseous_signature_parser.models.RomSignatureObject;
using static gaseous_server.Classes.RomMediaGroup; using static gaseous_server.Classes.RomMediaGroup;
using gaseous_server.Classes.Metadata; using gaseous_server.Classes.Metadata;
using IGDB.Models; using IGDB.Models;
using static HasheousClient.Models.FixMatchModel;
using NuGet.Protocol.Core.Types;
using static gaseous_server.Classes.FileSignature;
namespace gaseous_server.Classes namespace gaseous_server.Classes
{ {
public class Roms public class Roms
{ {
public class InvalidRomId : Exception public class InvalidRomId : Exception
{ {
public InvalidRomId(long Id) : base("Unable to find ROM by id " + Id) public InvalidRomId(long Id) : base("Unable to find ROM by id " + Id)
{} { }
} }
public class InvalidRomHash : Exception
{
public InvalidRomHash(String Hash) : base("Unable to find ROM by hash " + Hash)
{ }
}
public static GameRomObject GetRoms(long GameId, long PlatformId = -1, string NameSearch = "", int pageNumber = 0, int pageSize = 0, string userid = "") public static GameRomObject GetRoms(long GameId, long PlatformId = -1, string NameSearch = "", int pageNumber = 0, int pageSize = 0, string userid = "")
{ {
GameRomObject GameRoms = new GameRomObject(); GameRomObject GameRoms = new GameRomObject();
Database db = new Database(Database.databaseType.MySql, Config.DatabaseConfiguration.ConnectionString); Database db = new Database(Database.databaseType.MySql, Config.DatabaseConfiguration.ConnectionString);
string sql = ""; string sql = "";
string sqlCount = ""; string sqlCount = "";
string sqlPlatform = ""; string sqlPlatform = "";
Dictionary<string, object> dbDict = new Dictionary<string, object>(); Dictionary<string, object> dbDict = new Dictionary<string, object>();
dbDict.Add("id", GameId); dbDict.Add("id", GameId);
dbDict.Add("userid", userid); dbDict.Add("userid", userid);
string NameSearchWhere = ""; string NameSearchWhere = "";
if (NameSearch.Length > 0) if (NameSearch.Length > 0)
{ {
NameSearchWhere = " AND Games_Roms.`Name` LIKE @namesearch"; NameSearchWhere = " AND view_Games_Roms.`Name` LIKE @namesearch";
dbDict.Add("namesearch", '%' + NameSearch + '%'); dbDict.Add("namesearch", '%' + NameSearch + '%');
} }
string UserFields = "";
string UserJoin = "";
if (userid.Length > 0)
{
UserFields = ", User_RecentPlayedRoms.RomId AS MostRecentRomId, User_RecentPlayedRoms.IsMediaGroup AS MostRecentRomIsMediaGroup, User_GameFavouriteRoms.RomId AS FavouriteRomId, User_GameFavouriteRoms.IsMediaGroup AS FavouriteRomIsMediaGroup";
UserJoin = @"
LEFT JOIN
User_RecentPlayedRoms ON User_RecentPlayedRoms.UserId = @userid
AND User_RecentPlayedRoms.GameId = view_Games_Roms.GameId
AND User_RecentPlayedRoms.PlatformId = view_Games_Roms.PlatformId
AND User_RecentPlayedRoms.RomId = view_Games_Roms.Id
AND User_RecentPlayedRoms.IsMediaGroup = 0
LEFT JOIN
User_GameFavouriteRoms ON User_GameFavouriteRoms.UserId = @userid
AND User_GameFavouriteRoms.GameId = view_Games_Roms.GameId
AND User_GameFavouriteRoms.PlatformId = view_Games_Roms.PlatformId
AND User_GameFavouriteRoms.RomId = view_Games_Roms.Id
AND User_GameFavouriteRoms.IsMediaGroup = 0
";
}
// platform query // platform query
sqlPlatform = "SELECT DISTINCT Games_Roms.PlatformId, Platform.`Name` FROM Games_Roms LEFT JOIN Platform ON Games_Roms.PlatformId = Platform.Id WHERE GameId = @id ORDER BY Platform.`Name`;"; sqlPlatform = "SELECT DISTINCT view_Games_Roms.PlatformId, Platform.`Name` FROM view_Games_Roms LEFT JOIN Platform ON view_Games_Roms.PlatformId = Platform.Id WHERE GameId = @id ORDER BY Platform.`Name`;";
if (PlatformId == -1) { if (PlatformId == -1)
{
// data query // data query
sql = "SELECT DISTINCT Games_Roms.*, Platform.`Name` AS platformname, GameState.RomId AS SavedStateRomId FROM Games_Roms LEFT JOIN Platform ON Games_Roms.PlatformId = Platform.Id LEFT JOIN GameState ON (Games_Roms.Id = GameState.RomId AND GameState.UserId = @userid AND GameState.IsMediaGroup = 0) WHERE Games_Roms.GameId = @id" + NameSearchWhere + " ORDER BY Platform.`Name`, Games_Roms.`Name` LIMIT 1000;"; sql = "SELECT DISTINCT view_Games_Roms.*, Platform.`Name` AS platformname, Game.`Name` AS gamename, GameState.RomId AS SavedStateRomId" + UserFields + " FROM view_Games_Roms LEFT JOIN Platform ON view_Games_Roms.PlatformId = Platform.Id LEFT JOIN Game ON view_Games_Roms.GameId = Game.Id LEFT JOIN GameState ON (view_Games_Roms.Id = GameState.RomId AND GameState.UserId = @userid AND GameState.IsMediaGroup = 0) " + UserJoin + " WHERE view_Games_Roms.GameId = @id" + NameSearchWhere + " ORDER BY Platform.`Name`, view_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 {
// data query
sql = "SELECT DISTINCT Games_Roms.*, Platform.`Name` AS platformname, GameState.RomId AS SavedStateRomId FROM Games_Roms LEFT JOIN Platform ON Games_Roms.PlatformId = Platform.Id LEFT JOIN GameState ON (Games_Roms.Id = GameState.RomId AND GameState.UserId = @userid AND GameState.IsMediaGroup = 0) WHERE Games_Roms.GameId = @id AND Games_Roms.PlatformId = @platformid" + NameSearchWhere + " ORDER BY Platform.`Name`, Games_Roms.`Name` LIMIT 1000;";
// count query // count query
sqlCount = "SELECT COUNT(Games_Roms.Id) AS RomCount FROM Games_Roms WHERE Games_Roms.GameId = @id AND Games_Roms.PlatformId = @platformid" + NameSearchWhere + ";"; sqlCount = "SELECT COUNT(view_Games_Roms.Id) AS RomCount FROM view_Games_Roms WHERE view_Games_Roms.GameId = @id" + NameSearchWhere + ";";
}
else
{
// data query
sql = "SELECT DISTINCT view_Games_Roms.*, Platform.`Name` AS platformname, Game.`Name` AS gamename, GameState.RomId AS SavedStateRomId" + UserFields + " FROM view_Games_Roms LEFT JOIN Platform ON view_Games_Roms.PlatformId = Platform.Id LEFT JOIN Game ON view_Games_Roms.GameId = Game.Id LEFT JOIN GameState ON (view_Games_Roms.Id = GameState.RomId AND GameState.UserId = @userid AND GameState.IsMediaGroup = 0) " + UserJoin + " WHERE view_Games_Roms.GameId = @id AND view_Games_Roms.PlatformId = @platformid" + NameSearchWhere + " ORDER BY Platform.`Name`, view_Games_Roms.`Name` LIMIT 1000;";
// count query
sqlCount = "SELECT COUNT(view_Games_Roms.Id) AS RomCount FROM view_Games_Roms WHERE view_Games_Roms.GameId = @id AND view_Games_Roms.PlatformId = @platformid" + NameSearchWhere + ";";
dbDict.Add("platformid", PlatformId); dbDict.Add("platformid", PlatformId);
} }
DataTable romDT = db.ExecuteCMD(sql, dbDict); DataTable romDT = db.ExecuteCMD(sql, dbDict, new Database.DatabaseMemoryCacheOptions(true, (int)TimeSpan.FromMinutes(1).Ticks));
Dictionary<string, object> rowCount = db.ExecuteCMDDict(sqlCount, dbDict)[0]; Dictionary<string, object> rowCount = db.ExecuteCMDDict(sqlCount, dbDict, new Database.DatabaseMemoryCacheOptions(true, (int)TimeSpan.FromMinutes(1).Ticks))[0];
DataTable platformDT = db.ExecuteCMD(sqlPlatform, dbDict);
if (romDT.Rows.Count > 0) if (romDT.Rows.Count > 0)
{ {
// set count of roms // set count of roms
GameRoms.Count = int.Parse((string)rowCount["RomCount"]); GameRoms.Count = int.Parse((string)rowCount["RomCount"]);
int pageOffset = pageSize * (pageNumber - 1); int pageOffset = pageSize * (pageNumber - 1);
for (int i = 0; i < romDT.Rows.Count; i++) for (int i = 0; i < romDT.Rows.Count; i++)
{ {
GameRomItem gameRomItem = BuildRom(romDT.Rows[i]);
if ((i >= pageOffset && i < pageOffset + pageSize) || pageSize == 0) if ((i >= pageOffset && i < pageOffset + pageSize) || pageSize == 0)
{ {
GameRomItem gameRomItem = BuildRom(romDT.Rows[i]);
GameRoms.GameRomItems.Add(gameRomItem); GameRoms.GameRomItems.Add(gameRomItem);
} }
} }
return GameRoms; return GameRoms;
} }
else else
{ {
throw new Games.InvalidGameId(GameId); throw new Games.InvalidGameId(GameId);
} }
} }
public static GameRomItem GetRom(long RomId) public static GameRomItem GetRom(long RomId)
{ {
Database db = new Database(Database.databaseType.MySql, Config.DatabaseConfiguration.ConnectionString); 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.Id = @id"; string sql = "SELECT view_Games_Roms.*, Platform.`Name` AS platformname, Game.`Name` AS gamename FROM view_Games_Roms LEFT JOIN Platform ON view_Games_Roms.PlatformId = Platform.Id LEFT JOIN Game ON view_Games_Roms.GameId = Game.Id WHERE view_Games_Roms.Id = @id";
Dictionary<string, object> dbDict = new Dictionary<string, object>(); Dictionary<string, object> dbDict = new Dictionary<string, object>();
dbDict.Add("id", RomId); dbDict.Add("id", RomId);
DataTable romDT = db.ExecuteCMD(sql, dbDict); DataTable romDT = db.ExecuteCMD(sql, dbDict);
@@ -100,6 +131,26 @@ namespace gaseous_server.Classes
} }
} }
public static GameRomItem GetRom(string MD5)
{
Database db = new Database(Database.databaseType.MySql, Config.DatabaseConfiguration.ConnectionString);
string sql = "SELECT view_Games_Roms.*, Platform.`Name` AS platformname, Game.`Name` AS gamename FROM view_Games_Roms LEFT JOIN Platform ON view_Games_Roms.PlatformId = Platform.Id LEFT JOIN Game ON view_Games_Roms.GameId = Game.Id WHERE view_Games_Roms.MD5 = @id";
Dictionary<string, object> dbDict = new Dictionary<string, object>();
dbDict.Add("id", MD5);
DataTable romDT = db.ExecuteCMD(sql, dbDict);
if (romDT.Rows.Count > 0)
{
DataRow romDR = romDT.Rows[0];
GameRomItem romItem = BuildRom(romDR);
return romItem;
}
else
{
throw new InvalidRomHash(MD5);
}
}
public static GameRomItem UpdateRom(long RomId, long PlatformId, long GameId) public static GameRomItem UpdateRom(long RomId, long PlatformId, long GameId)
{ {
// ensure metadata for platformid is present // ensure metadata for platformid is present
@@ -108,18 +159,65 @@ namespace gaseous_server.Classes
// ensure metadata for gameid is present // ensure metadata for gameid is present
IGDB.Models.Game game = Classes.Metadata.Games.GetGame(GameId, false, false, false); IGDB.Models.Game game = Classes.Metadata.Games.GetGame(GameId, false, false, false);
Database db = new Database(Database.databaseType.MySql, Config.DatabaseConfiguration.ConnectionString); Database db = new Database(Database.databaseType.MySql, Config.DatabaseConfiguration.ConnectionString);
string sql = "UPDATE Games_Roms SET PlatformId=@platformid, GameId=@gameid WHERE Id = @id"; string sql = "UPDATE Games_Roms SET PlatformId=@platformid, GameId=@gameid WHERE Id = @id";
Dictionary<string, object> dbDict = new Dictionary<string, object>(); Dictionary<string, object> dbDict = new Dictionary<string, object>();
dbDict.Add("id", RomId); dbDict.Add("id", RomId);
dbDict.Add("platformid", PlatformId); dbDict.Add("platformid", PlatformId);
dbDict.Add("gameid", GameId); dbDict.Add("gameid", GameId);
db.ExecuteCMD(sql, dbDict); db.ExecuteCMD(sql, dbDict);
GameRomItem rom = GetRom(RomId); GameRomItem rom = GetRom(RomId);
// send update to Hasheous if enabled
if (PlatformId != 0 && GameId != 0)
{
if (Config.MetadataConfiguration.HasheousSubmitFixes == true)
{
if (
Config.MetadataConfiguration.SignatureSource == HasheousClient.Models.MetadataModel.SignatureSources.Hasheous &&
(
Config.MetadataConfiguration.HasheousAPIKey != null &&
Config.MetadataConfiguration.HasheousAPIKey != "")
)
{
try
{
// find signature used for identifing the rom
string md5String = rom.Md5;
string sha1String = rom.Sha1;
if (rom.Attributes.ContainsKey("ZipContents"))
{
bool selectorFound = false;
List<ArchiveData> archiveDataValues = Newtonsoft.Json.JsonConvert.DeserializeObject<List<ArchiveData>>(rom.Attributes["ZipContents"].ToString());
foreach (ArchiveData archiveData in archiveDataValues)
{
if (archiveData.isSignatureSelector == true)
{
md5String = archiveData.MD5;
sha1String = archiveData.SHA1;
selectorFound = true;
break;
}
}
}
HasheousClient.WebApp.HttpHelper.AddHeader("X-API-Key", Config.MetadataConfiguration.HasheousAPIKey);
HasheousClient.Hasheous hasheousClient = new HasheousClient.Hasheous();
List<MetadataMatch> metadataMatchList = new List<MetadataMatch>();
metadataMatchList.Add(new MetadataMatch(HasheousClient.Models.MetadataSources.IGDB, platform.Slug, game.Slug));
hasheousClient.FixMatch(new HasheousClient.Models.FixMatchModel(md5String, sha1String, metadataMatchList));
}
catch (Exception ex)
{
Logging.Log(Logging.LogType.Critical, "Fix Match", "An error occurred while sending a fixed match to Hasheous.", ex);
}
}
}
}
return rom; return rom;
} }
public static void DeleteRom(long RomId) public static void DeleteRom(long RomId)
{ {
@@ -132,12 +230,12 @@ namespace gaseous_server.Classes
} }
Database db = new Database(Database.databaseType.MySql, Config.DatabaseConfiguration.ConnectionString); Database db = new Database(Database.databaseType.MySql, Config.DatabaseConfiguration.ConnectionString);
string sql = "DELETE FROM Games_Roms WHERE Id = @id; DELETE FROM GameState WHERE RomId = @id;"; string sql = "DELETE FROM Games_Roms WHERE Id = @id; DELETE FROM GameState WHERE RomId = @id; UPDATE UserTimeTracking SET PlatformId = NULL, IsMediaGroup = NULL, RomId = NULL WHERE RomId = @id AND IsMediaGroup = 0;";
Dictionary<string, object> dbDict = new Dictionary<string, object>(); Dictionary<string, object> dbDict = new Dictionary<string, object>();
dbDict.Add("id", RomId); dbDict.Add("id", RomId);
db.ExecuteCMD(sql, dbDict); db.ExecuteCMD(sql, dbDict);
} }
} }
private static GameRomItem BuildRom(DataRow romDR) private static GameRomItem BuildRom(DataRow romDR)
{ {
@@ -150,43 +248,67 @@ namespace gaseous_server.Classes
} }
} }
Dictionary<string, object> romAttributes = new Dictionary<string, object>();
if (romDR["attributes"] != DBNull.Value)
{
try
{
if ((string)romDR["attributes"] != "[ ]")
{
romAttributes = Newtonsoft.Json.JsonConvert.DeserializeObject<Dictionary<string, object>>((string)romDR["attributes"]);
}
}
catch (Exception ex)
{
Logging.Log(Logging.LogType.Warning, "Roms", "Error parsing rom attributes: " + ex.Message);
}
}
GameRomItem romItem = new GameRomItem GameRomItem romItem = new GameRomItem
{ {
Id = (long)romDR["id"], Id = (long)romDR["id"],
PlatformId = (long)romDR["platformid"], PlatformId = (long)romDR["platformid"],
Platform = (string)romDR["platformname"], Platform = (string)romDR["platformname"],
GameId = (long)romDR["gameid"], GameId = (long)romDR["gameid"],
Name = (string)romDR["name"], Game = (string)romDR["gamename"],
Size = (long)romDR["size"], Name = (string)romDR["name"],
Crc = ((string)romDR["crc"]).ToLower(), Size = (long)romDR["size"],
Md5 = ((string)romDR["md5"]).ToLower(), Crc = ((string)romDR["crc"]).ToLower(),
Sha1 = ((string)romDR["sha1"]).ToLower(), Md5 = ((string)romDR["md5"]).ToLower(),
DevelopmentStatus = (string)romDR["developmentstatus"], Sha1 = ((string)romDR["sha1"]).ToLower(),
Attributes = Newtonsoft.Json.JsonConvert.DeserializeObject<List<KeyValuePair<string, object>>>((string)Common.ReturnValueIfNull(romDR["attributes"], "[ ]")), DevelopmentStatus = (string)romDR["developmentstatus"],
RomType = (HasheousClient.Models.LookupResponseModel.RomItem.RomTypes)(int)romDR["romtype"], Attributes = romAttributes,
RomTypeMedia = (string)romDR["romtypemedia"], RomType = (HasheousClient.Models.SignatureModel.RomItem.RomTypes)(int)romDR["romtype"],
MediaLabel = (string)romDR["medialabel"], RomTypeMedia = (string)romDR["romtypemedia"],
Path = (string)romDR["path"], MediaLabel = (string)romDR["medialabel"],
Path = (string)romDR["path"],
RelativePath = (string)romDR["relativepath"],
SignatureSource = (gaseous_server.Models.Signatures_Games.RomItem.SignatureSourceType)(Int32)romDR["metadatasource"], SignatureSource = (gaseous_server.Models.Signatures_Games.RomItem.SignatureSourceType)(Int32)romDR["metadatasource"],
SignatureSourceGameTitle = (string)Common.ReturnValueIfNull(romDR["MetadataGameName"], ""), SignatureSourceGameTitle = (string)Common.ReturnValueIfNull(romDR["MetadataGameName"], ""),
HasSaveStates = hasSaveStates, HasSaveStates = hasSaveStates,
Library = GameLibrary.GetLibrary((int)romDR["LibraryId"]) Library = GameLibrary.GetLibrary((int)romDR["LibraryId"])
}; };
// check for a web emulator and update the romItem romItem.RomUserLastUsed = false;
foreach (Models.PlatformMapping.PlatformMapItem platformMapping in Models.PlatformMapping.PlatformMap) if (romDR.Table.Columns.Contains("MostRecentRomId"))
{ {
if (platformMapping.IGDBId == romItem.PlatformId) if (romDR["MostRecentRomId"] != DBNull.Value)
{ {
if (platformMapping.WebEmulator != null) romItem.RomUserLastUsed = true;
{
romItem.Emulator = platformMapping.WebEmulator;
}
} }
} }
return romItem; romItem.RomUserFavourite = false;
} if (romDR.Table.Columns.Contains("FavouriteRomId"))
{
if (romDR["FavouriteRomId"] != DBNull.Value)
{
romItem.RomUserFavourite = true;
}
}
return romItem;
}
public class GameRomObject public class GameRomObject
{ {
@@ -194,17 +316,20 @@ namespace gaseous_server.Classes
public int Count { get; set; } public int Count { get; set; }
} }
public class GameRomItem : HasheousClient.Models.LookupResponseModel.RomItem public class GameRomItem : HasheousClient.Models.SignatureModel.RomItem
{ {
public long PlatformId { get; set; } public long PlatformId { get; set; }
public string Platform { get; set; } public string Platform { get; set; }
public Models.PlatformMapping.PlatformMapItem.WebEmulatorItem? Emulator { get; set; } public long GameId { get; set; }
public long GameId { get; set; } public string Game { get; set; }
public string? Path { get; set; } public string? Path { get; set; }
public string? SignatureSourceGameTitle { get; set;} public string? RelativePath { get; set; }
public string? SignatureSourceGameTitle { get; set; }
public bool HasSaveStates { get; set; } = false; public bool HasSaveStates { get; set; } = false;
public GameLibrary.LibraryItem Library { get; set; } public GameLibrary.LibraryItem Library { get; set; }
} public bool RomUserLastUsed { get; set; }
} public bool RomUserFavourite { get; set; }
}
}
} }

View File

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

View File

@@ -1,5 +1,7 @@
using System.Data; using System.Data;
using gaseous_server.Models;
using gaseous_signature_parser.models.RomSignatureObject; using gaseous_signature_parser.models.RomSignatureObject;
using static gaseous_server.Classes.Common;
namespace gaseous_server.Classes namespace gaseous_server.Classes
{ {
@@ -10,7 +12,8 @@ namespace gaseous_server.Classes
if (md5.Length > 0) if (md5.Length > 0)
{ {
return _GetSignature("Signatures_Roms.md5 = @searchstring", md5); return _GetSignature("Signatures_Roms.md5 = @searchstring", md5);
} else }
else
{ {
return _GetSignature("Signatures_Roms.sha1 = @searchstring", sha1); return _GetSignature("Signatures_Roms.sha1 = @searchstring", sha1);
} }
@@ -21,7 +24,8 @@ namespace gaseous_server.Classes
if (TosecName.Length > 0) if (TosecName.Length > 0)
{ {
return _GetSignature("Signatures_Roms.name = @searchstring", TosecName); return _GetSignature("Signatures_Roms.name = @searchstring", TosecName);
} else }
else
{ {
return null; return null;
} }
@@ -44,7 +48,7 @@ namespace gaseous_server.Classes
{ {
Game = new gaseous_server.Models.Signatures_Games.GameItem Game = new gaseous_server.Models.Signatures_Games.GameItem
{ {
Id = (Int32)sigDbRow["Id"], Id = (long)(int)sigDbRow["Id"],
Name = (string)sigDbRow["Name"], Name = (string)sigDbRow["Name"],
Description = (string)sigDbRow["Description"], Description = (string)sigDbRow["Description"],
Year = (string)sigDbRow["Year"], Year = (string)sigDbRow["Year"],
@@ -53,20 +57,20 @@ namespace gaseous_server.Classes
System = (string)sigDbRow["Platform"], System = (string)sigDbRow["Platform"],
SystemVariant = (string)sigDbRow["SystemVariant"], SystemVariant = (string)sigDbRow["SystemVariant"],
Video = (string)sigDbRow["Video"], Video = (string)sigDbRow["Video"],
Country = (string)sigDbRow["Country"], Countries = new Dictionary<string, string>(GetLookup(LookupTypes.Country, (long)(int)sigDbRow["Id"])),
Language = (string)sigDbRow["Language"], Languages = new Dictionary<string, string>(GetLookup(LookupTypes.Language, (long)(int)sigDbRow["Id"])),
Copyright = (string)sigDbRow["Copyright"] Copyright = (string)sigDbRow["Copyright"]
}, },
Rom = new gaseous_server.Models.Signatures_Games.RomItem Rom = new gaseous_server.Models.Signatures_Games.RomItem
{ {
Id = (Int32)sigDbRow["romid"], Id = (long)(int)sigDbRow["romid"],
Name = (string)sigDbRow["romname"], Name = (string)sigDbRow["romname"],
Size = (Int64)sigDbRow["Size"], Size = (Int64)sigDbRow["Size"],
Crc = (string)sigDbRow["CRC"], Crc = (string)sigDbRow["CRC"],
Md5 = ((string)sigDbRow["MD5"]).ToLower(), Md5 = ((string)sigDbRow["MD5"]).ToLower(),
Sha1 = ((string)sigDbRow["SHA1"]).ToLower(), Sha1 = ((string)sigDbRow["SHA1"]).ToLower(),
DevelopmentStatus = (string)sigDbRow["DevelopmentStatus"], DevelopmentStatus = (string)sigDbRow["DevelopmentStatus"],
Attributes = Newtonsoft.Json.JsonConvert.DeserializeObject<List<KeyValuePair<string, object>>>((string)Common.ReturnValueIfNull(sigDbRow["Attributes"], "[]")), Attributes = Newtonsoft.Json.JsonConvert.DeserializeObject<Dictionary<string, object>>((string)Common.ReturnValueIfNull(sigDbRow["Attributes"], "[]")),
RomType = (gaseous_server.Models.Signatures_Games.RomItem.RomTypes)(int)sigDbRow["RomType"], RomType = (gaseous_server.Models.Signatures_Games.RomItem.RomTypes)(int)sigDbRow["RomType"],
RomTypeMedia = (string)sigDbRow["RomTypeMedia"], RomTypeMedia = (string)sigDbRow["RomTypeMedia"],
MediaLabel = (string)sigDbRow["MediaLabel"], MediaLabel = (string)sigDbRow["MediaLabel"],
@@ -77,5 +81,77 @@ namespace gaseous_server.Classes
} }
return GamesList; return GamesList;
} }
public List<Signatures_Sources> GetSources()
{
Database db = new Database(Database.databaseType.MySql, Config.DatabaseConfiguration.ConnectionString);
string sql = "SELECT * FROM Signatures_Sources ORDER BY `SourceType`, `Name`;";
DataTable sigDb = db.ExecuteCMD(sql);
List<Signatures_Sources> SourcesList = new List<Signatures_Sources>();
foreach (DataRow sigDbRow in sigDb.Rows)
{
Signatures_Sources sourceItem = new Signatures_Sources
{
Id = (int)sigDbRow["Id"],
Name = (string)sigDbRow["Name"],
Description = (string)sigDbRow["Description"],
URL = (string)sigDbRow["URL"],
Category = (string)sigDbRow["Category"],
Version = (string)sigDbRow["Version"],
Author = (string)sigDbRow["Author"],
Email = (string)sigDbRow["Email"],
Homepage = (string)sigDbRow["Homepage"],
SourceType = (gaseous_signature_parser.parser.SignatureParser)Enum.Parse(typeof(gaseous_signature_parser.parser.SignatureParser), sigDbRow["SourceType"].ToString()),
MD5 = (string)sigDbRow["SourceMD5"],
SHA1 = (string)sigDbRow["SourceSHA1"]
};
SourcesList.Add(sourceItem);
}
return SourcesList;
}
public void DeleteSource(int sourceId)
{
Database db = new Database(Database.databaseType.MySql, Config.DatabaseConfiguration.ConnectionString);
string sql = "DELETE FROM Signatures_Sources WHERE Id = @sourceId;";
Dictionary<string, object> dbDict = new Dictionary<string, object>
{
{ "sourceId", sourceId }
};
db.ExecuteCMD(sql, dbDict);
}
public Dictionary<string, string> GetLookup(LookupTypes LookupType, long GameId)
{
string tableName = "";
switch (LookupType)
{
case LookupTypes.Country:
tableName = "Countries";
break;
case LookupTypes.Language:
tableName = "Languages";
break;
}
Database db = new Database(Database.databaseType.MySql, Config.DatabaseConfiguration.ConnectionString);
string sql = "SELECT " + LookupType.ToString() + ".Code, " + LookupType.ToString() + ".Value FROM Signatures_Games_" + tableName + " JOIN " + LookupType.ToString() + " ON Signatures_Games_" + tableName + "." + LookupType.ToString() + "Id = " + LookupType.ToString() + ".Id WHERE Signatures_Games_" + tableName + ".GameId = @id;";
Dictionary<string, object> dbDict = new Dictionary<string, object>{
{ "id", GameId }
};
DataTable data = db.ExecuteCMD(sql, dbDict);
Dictionary<string, string> returnDict = new Dictionary<string, string>();
foreach (DataRow row in data.Rows)
{
returnDict.Add((string)row["Code"], (string)row["Value"]);
}
return returnDict;
}
} }
} }

View File

@@ -0,0 +1,117 @@
using System.Data;
using gaseous_server.Models;
namespace gaseous_server.Classes
{
public class Statistics
{
public StatisticsModel RecordSession(Guid SessionId, long GameId, long PlatformId, long RomId, bool IsMediaGroup, string UserId)
{
Database db = new Database(Database.databaseType.MySql, Config.DatabaseConfiguration.ConnectionString);
string sql;
Dictionary<string, object> dbDict = new Dictionary<string, object>{
{ "userid", UserId },
{ "gameid", GameId },
{ "platformid", PlatformId },
{ "romid", RomId },
{ "ismediagroup", IsMediaGroup }
};
// update last played rom id
sql = "INSERT INTO User_RecentPlayedRoms (UserId, GameId, PlatformId, RomId, IsMediaGroup) VALUES (@userid, @gameid, @platformid, @romid, @ismediagroup) ON DUPLICATE KEY UPDATE RomId = @romid, IsMediaGroup = @ismediagroup;";
db.ExecuteNonQuery(sql, dbDict);
// update sessions
if (SessionId == Guid.Empty)
{
// new session required
SessionId = Guid.NewGuid();
sql = "INSERT INTO UserTimeTracking (GameId, UserId, SessionId, SessionTime, SessionLength, PlatformId, IsMediaGroup, RomId) VALUES (@gameid, @userid, @sessionid, @sessiontime, @sessionlength, @platformid, @ismediagroup, @romid);";
dbDict = new Dictionary<string, object>{
{ "gameid", GameId },
{ "userid", UserId },
{ "sessionid", SessionId },
{ "sessiontime", DateTime.UtcNow },
{ "sessionlength", 1 },
{ "platformid", PlatformId },
{ "ismediagroup", IsMediaGroup },
{ "romid", RomId }
};
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

@@ -0,0 +1,209 @@
using System.Data;
using gaseous_server.Classes.Metadata;
using IGDB.Models;
namespace gaseous_server.Classes
{
public class UserProfile
{
static readonly Dictionary<string, string> supportedImages = new Dictionary<string, string>{
{ ".png", "image/png" },
{ ".jpg", "image/jpeg" },
{ ".jpeg", "image/jpeg" },
{ ".gif", "image/gif" },
{ ".bmp", "image/bmp" },
{ ".svg", "image/svg+xml" }
};
public Models.UserProfile? GetUserProfile(string UserId)
{
// build the user profile object
Database db = new Database(Database.databaseType.MySql, Config.DatabaseConfiguration.ConnectionString);
string sql = "SELECT Id, UserId, DisplayName, Quip, AvatarExtension, ProfileBackgroundExtension, UnstructuredData FROM UserProfiles WHERE Id = @userid;";
Dictionary<string, object> dbDict = new Dictionary<string, object>{
{ "userid", UserId }
};
DataTable data = db.ExecuteCMD(sql, dbDict);
if (data.Rows.Count == 0)
{
return null;
}
Models.UserProfile.ProfileImageItem? Avatar = null;
if (data.Rows[0]["AvatarExtension"] != DBNull.Value)
{
Avatar = new Models.UserProfile.ProfileImageItem
{
MimeType = supportedImages[data.Rows[0]["AvatarExtension"].ToString()],
Extension = data.Rows[0]["AvatarExtension"].ToString()
};
}
Models.UserProfile.ProfileImageItem? ProfileBackground = null;
if (data.Rows[0]["ProfileBackgroundExtension"] != DBNull.Value)
{
ProfileBackground = new Models.UserProfile.ProfileImageItem
{
MimeType = supportedImages[data.Rows[0]["ProfileBackgroundExtension"].ToString()],
Extension = data.Rows[0]["ProfileBackgroundExtension"].ToString()
};
}
// get now playing game - if available
Models.UserProfile.NowPlayingItem? NowPlaying = null;
sql = "SELECT * FROM `view_UserTimeTracking` WHERE UserId = @userid AND UTC_TIMESTAMP() BETWEEN SessionTime AND DATE_ADD(SessionEnd, INTERVAL 2 MINUTE) ORDER BY SessionEnd DESC LIMIT 1;";
dbDict = new Dictionary<string, object>{
{ "userid", data.Rows[0]["UserId"].ToString() }
};
DataTable nowPlayingData = db.ExecuteCMD(sql, dbDict);
if (nowPlayingData.Rows.Count > 0)
{
NowPlaying = new Models.UserProfile.NowPlayingItem
{
Game = Games.GetGame((long)nowPlayingData.Rows[0]["GameId"], false, false, false),
Platform = Platforms.GetPlatform((long)nowPlayingData.Rows[0]["PlatformId"], false, false),
Duration = Convert.ToInt64(nowPlayingData.Rows[0]["SessionLength"])
};
}
// return the user profile object
return new Models.UserProfile
{
UserId = Guid.Parse(data.Rows[0]["Id"].ToString()),
DisplayName = data.Rows[0]["DisplayName"].ToString(),
Quip = data.Rows[0]["Quip"].ToString(),
Avatar = Avatar,
ProfileBackground = ProfileBackground,
NowPlaying = NowPlaying,
Data = Newtonsoft.Json.JsonConvert.DeserializeObject<Dictionary<string, object>>(data.Rows[0]["UnstructuredData"].ToString())
};
}
public void UpdateUserProfile(string InternalUserId, Models.UserProfile profile)
{
Database db = new Database(Database.databaseType.MySql, Config.DatabaseConfiguration.ConnectionString);
string sql = "UPDATE UserProfiles SET DisplayName = @displayname, Quip = @quip, UnstructuredData = @data WHERE UserId = @internalId AND Id = @userid;";
Dictionary<string, object> dbDict = new Dictionary<string, object>{
{ "displayname", profile.DisplayName },
{ "quip", profile.Quip },
{ "data", Newtonsoft.Json.JsonConvert.SerializeObject(profile.Data) },
{ "userid", profile.UserId },
{ "internalId", InternalUserId }
};
db.ExecuteCMD(sql, dbDict);
}
public enum ImageType
{
Avatar,
Background
}
public void UpdateImage(ImageType imageType, string UserId, string InternalUserId, string Filename, byte[] bytes)
{
// check if it's a supported file type
if (!supportedImages.ContainsKey(Path.GetExtension(Filename).ToLower()))
{
throw new Exception("File type not supported");
}
string ByteFieldName;
string ExtensionFieldName;
switch (imageType)
{
case ImageType.Avatar:
ByteFieldName = "Avatar";
ExtensionFieldName = "AvatarExtension";
break;
case ImageType.Background:
ByteFieldName = "ProfileBackground";
ExtensionFieldName = "ProfileBackgroundExtension";
break;
default:
throw new Exception("Invalid image type");
}
Database db = new Database(Database.databaseType.MySql, Config.DatabaseConfiguration.ConnectionString);
string sql = String.Format("UPDATE UserProfiles SET {0} = @content, {1} = @extension WHERE Id = @userid AND UserId = @internaluserid;", ByteFieldName, ExtensionFieldName);
Dictionary<string, object> dbDict = new Dictionary<string, object>{
{ "content", bytes },
{ "extension", Path.GetExtension(Filename) },
{ "userid", UserId },
{ "internaluserid", InternalUserId }
};
db.ExecuteCMD(sql, dbDict);
}
public Models.ImageItem? GetImage(ImageType imageType, string UserId)
{
string ByteFieldName;
string ExtensionFieldName;
switch (imageType)
{
case ImageType.Avatar:
ByteFieldName = "Avatar";
ExtensionFieldName = "AvatarExtension";
break;
case ImageType.Background:
ByteFieldName = "ProfileBackground";
ExtensionFieldName = "ProfileBackgroundExtension";
break;
default:
throw new Exception("Invalid image type");
}
Database db = new Database(Database.databaseType.MySql, Config.DatabaseConfiguration.ConnectionString);
string sql = String.Format("SELECT {0}, {1} FROM UserProfiles WHERE Id = @userid;", ByteFieldName, ExtensionFieldName);
Dictionary<string, object> dbDict = new Dictionary<string, object>{
{ "userid", UserId }
};
DataTable data = db.ExecuteCMD(sql, dbDict);
if (data.Rows.Count == 0)
{
return null;
}
Models.ImageItem? image = new Models.ImageItem
{
content = data.Rows[0][ByteFieldName] as byte[],
mimeType = supportedImages[data.Rows[0][ExtensionFieldName] as string],
extension = data.Rows[0][ExtensionFieldName] as string
};
return image;
}
public void DeleteImage(ImageType imageType, string UserId)
{
string ByteFieldName;
string ExtensionFieldName;
switch (imageType)
{
case ImageType.Avatar:
ByteFieldName = "Avatar";
ExtensionFieldName = "AvatarExtension";
break;
case ImageType.Background:
ByteFieldName = "ProfileBackground";
ExtensionFieldName = "ProfileBackgroundExtension";
break;
default:
throw new Exception("Invalid image type");
}
Database db = new Database(Database.databaseType.MySql, Config.DatabaseConfiguration.ConnectionString);
string sql = String.Format("UPDATE UserProfiles SET {0} = NULL, {1} = NULL WHERE UserId = @userid;", ByteFieldName, ExtensionFieldName);
Dictionary<string, object> dbDict = new Dictionary<string, object>{
{ "userid", UserId }
};
db.ExecuteCMD(sql, dbDict);
}
}
}

View File

@@ -8,6 +8,8 @@ using Microsoft.AspNetCore.Identity;
using Microsoft.AspNetCore.Identity.UI.Services; using Microsoft.AspNetCore.Identity.UI.Services;
using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.Rendering; using Microsoft.AspNetCore.Mvc.Rendering;
using Asp.Versioning;
using IGDB;
namespace gaseous_server.Controllers namespace gaseous_server.Controllers
{ {
@@ -97,6 +99,7 @@ namespace gaseous_server.Controllers
profile.Roles = new List<string>(await _userManager.GetRolesAsync(user)); profile.Roles = new List<string>(await _userManager.GetRolesAsync(user));
profile.SecurityProfile = user.SecurityProfile; profile.SecurityProfile = user.SecurityProfile;
profile.UserPreferences = user.UserPreferences; profile.UserPreferences = user.UserPreferences;
profile.ProfileId = user.ProfileId;
profile.Roles.Sort(); profile.Roles.Sort();
return Ok(profile); return Ok(profile);
@@ -119,7 +122,7 @@ namespace gaseous_server.Controllers
profile.SecurityProfile = user.SecurityProfile; profile.SecurityProfile = user.SecurityProfile;
profile.UserPreferences = user.UserPreferences; profile.UserPreferences = user.UserPreferences;
profile.Roles.Sort(); profile.Roles.Sort();
string profileString = "var userProfile = " + Newtonsoft.Json.JsonConvert.SerializeObject(profile, Newtonsoft.Json.Formatting.Indented) + ";"; string profileString = "var userProfile = " + Newtonsoft.Json.JsonConvert.SerializeObject(profile, Newtonsoft.Json.Formatting.Indented) + ";";
byte[] bytes = Encoding.UTF8.GetBytes(profileString); byte[] bytes = Encoding.UTF8.GetBytes(profileString);
@@ -185,7 +188,8 @@ namespace gaseous_server.Controllers
user.LockoutEnabled = rawUser.LockoutEnabled; user.LockoutEnabled = rawUser.LockoutEnabled;
user.LockoutEnd = rawUser.LockoutEnd; user.LockoutEnd = rawUser.LockoutEnd;
user.SecurityProfile = rawUser.SecurityProfile; user.SecurityProfile = rawUser.SecurityProfile;
user.ProfileId = rawUser.ProfileId;
// get roles // get roles
ApplicationUser? aUser = await _userManager.FindByIdAsync(rawUser.Id); ApplicationUser? aUser = await _userManager.FindByIdAsync(rawUser.Id);
if (aUser != null) if (aUser != null)
@@ -210,12 +214,16 @@ namespace gaseous_server.Controllers
if (ModelState.IsValid) if (ModelState.IsValid)
{ {
ApplicationUser user = new ApplicationUser ApplicationUser user = new ApplicationUser
{ {
UserName = model.UserName, UserName = model.UserName,
NormalizedUserName = model.UserName.ToUpper(), NormalizedUserName = model.UserName.ToUpper(),
Email = model.Email, Email = model.Email,
NormalizedEmail = model.Email.ToUpper() NormalizedEmail = model.Email.ToUpper()
}; };
if (await _userManager.FindByEmailAsync(model.Email) != null)
{
return NotFound("User already exists");
}
var result = await _userManager.CreateAsync(user, model.Password); var result = await _userManager.CreateAsync(user, model.Password);
if (result.Succeeded) if (result.Succeeded)
{ {
@@ -230,20 +238,37 @@ namespace gaseous_server.Controllers
{ {
return Ok(result); return Ok(result);
} }
} }
else else
{ {
return NotFound(); return NotFound();
} }
} }
[HttpGet]
[Route("Users/Test")]
[Authorize(Roles = "Admin")]
public async Task<IActionResult> TestUserExists(string Email)
{
ApplicationUser? rawUser = await _userManager.FindByEmailAsync(Email);
if (rawUser != null)
{
return Ok(true);
}
else
{
return Ok(false);
}
}
[HttpGet] [HttpGet]
[Route("Users/{UserId}")] [Route("Users/{UserId}")]
[Authorize(Roles = "Admin")] [Authorize(Roles = "Admin")]
public async Task<IActionResult> GetUser(string UserId) public async Task<IActionResult> GetUser(string UserId)
{ {
ApplicationUser? rawUser = await _userManager.FindByIdAsync(UserId); ApplicationUser? rawUser = await _userManager.FindByIdAsync(UserId);
if (rawUser != null) if (rawUser != null)
{ {
UserViewModel user = new UserViewModel(); UserViewModel user = new UserViewModel();
@@ -252,7 +277,8 @@ namespace gaseous_server.Controllers
user.LockoutEnabled = rawUser.LockoutEnabled; user.LockoutEnabled = rawUser.LockoutEnabled;
user.LockoutEnd = rawUser.LockoutEnd; user.LockoutEnd = rawUser.LockoutEnd;
user.SecurityProfile = rawUser.SecurityProfile; user.SecurityProfile = rawUser.SecurityProfile;
user.ProfileId = rawUser.ProfileId;
// get roles // get roles
IList<string> aUserRoles = await _userManager.GetRolesAsync(rawUser); IList<string> aUserRoles = await _userManager.GetRolesAsync(rawUser);
user.Roles = aUserRoles.ToList(); user.Roles = aUserRoles.ToList();
@@ -293,16 +319,16 @@ namespace gaseous_server.Controllers
public async Task<IActionResult> SetUserRoles(string UserId, string RoleName) public async Task<IActionResult> SetUserRoles(string UserId, string RoleName)
{ {
ApplicationUser? user = await _userManager.FindByIdAsync(UserId); ApplicationUser? user = await _userManager.FindByIdAsync(UserId);
if (user != null) if (user != null)
{ {
// get roles // get roles
List<string> userRoles = (await _userManager.GetRolesAsync(user)).ToList(); List<string> userRoles = (await _userManager.GetRolesAsync(user)).ToList();
// delete all roles // delete all roles
foreach (string role in userRoles) foreach (string role in userRoles)
{ {
if ((new string[] { "Admin", "Gamer", "Player" }).Contains(role) ) if ((new string[] { "Admin", "Gamer", "Player" }).Contains(role))
{ {
await _userManager.RemoveFromRoleAsync(user, role); await _userManager.RemoveFromRoleAsync(user, role);
} }
@@ -327,7 +353,7 @@ namespace gaseous_server.Controllers
await _userManager.AddToRoleAsync(user, RoleName); await _userManager.AddToRoleAsync(user, RoleName);
break; break;
} }
return Ok(); return Ok();
} }
else else
@@ -344,12 +370,12 @@ namespace gaseous_server.Controllers
if (ModelState.IsValid) if (ModelState.IsValid)
{ {
ApplicationUser? user = await _userManager.FindByIdAsync(UserId); ApplicationUser? user = await _userManager.FindByIdAsync(UserId);
if (user != null) if (user != null)
{ {
user.SecurityProfile = securityProfile; user.SecurityProfile = securityProfile;
await _userManager.UpdateAsync(user); await _userManager.UpdateAsync(user);
return Ok(); return Ok();
} }
else else
@@ -409,9 +435,119 @@ namespace gaseous_server.Controllers
{ {
user.UserPreferences = model; user.UserPreferences = model;
await _userManager.UpdateAsync(user); await _userManager.UpdateAsync(user);
return Ok(); return Ok();
} }
} }
[HttpPost]
[ProducesResponseType(StatusCodes.Status200OK)]
[RequestSizeLimit(long.MaxValue)]
[Consumes("multipart/form-data")]
[DisableRequestSizeLimit, RequestFormLimits(MultipartBodyLengthLimit = long.MaxValue, ValueLengthLimit = int.MaxValue)]
[Route("Avatar")]
public async Task<IActionResult> UploadAvatar(IFormFile file)
{
ApplicationUser? user = await _userManager.GetUserAsync(User);
if (user == null)
{
return Unauthorized();
}
else
{
Guid avatarId = Guid.Empty;
if (file.Length > 0)
{
using (var ms = new MemoryStream())
{
file.CopyTo(ms);
byte[] fileBytes = ms.ToArray();
byte[] targetBytes;
using (var image = new ImageMagick.MagickImage(fileBytes))
{
ImageMagick.MagickGeometry size = new ImageMagick.MagickGeometry(256, 256);
// This will resize the image to a fixed size without maintaining the aspect ratio.
// Normally an image will be resized to fit inside the specified size.
size.IgnoreAspectRatio = true;
image.Resize(size);
var newMs = new MemoryStream();
image.Resize(size);
image.Strip();
image.Write(newMs, ImageMagick.MagickFormat.Jpg);
targetBytes = newMs.ToArray();
}
Database db = new Database(Database.databaseType.MySql, Config.DatabaseConfiguration.ConnectionString);
UserTable<ApplicationUser> userTable = new UserTable<ApplicationUser>(db);
avatarId = userTable.SetAvatar(user, targetBytes);
}
}
return Ok(avatarId);
}
}
[HttpGet]
[Route("Avatar/{id}.jpg")]
[ProducesResponseType(typeof(FileStreamResult), StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status404NotFound)]
public ActionResult GetAvatar(Guid id)
{
if (id == Guid.Empty)
{
return NotFound();
}
else
{
Database db = new Database(Database.databaseType.MySql, Config.DatabaseConfiguration.ConnectionString);
string sql = "SELECT * FROM UserAvatars WHERE Id = @id";
Dictionary<string, object> dbDict = new Dictionary<string, object>{
{ "id", id }
};
DataTable data = db.ExecuteCMD(sql, dbDict);
if (data.Rows.Count > 0)
{
string filename = id.ToString() + ".jpg";
byte[] filedata = (byte[])data.Rows[0]["Avatar"];
string contentType = "image/jpg";
var cd = new System.Net.Mime.ContentDisposition
{
FileName = filename,
Inline = true,
};
Response.Headers.Add("Content-Disposition", cd.ToString());
Response.Headers.Add("Cache-Control", "public, max-age=604800");
return File(filedata, contentType);
}
else
{
return NotFound();
}
}
}
[HttpDelete]
[Route("Avatar/{id}.jpg")]
[ProducesResponseType(StatusCodes.Status200OK)]
public async Task<ActionResult> DeleteAvatarAsync()
{
ApplicationUser? user = await _userManager.GetUserAsync(User);
Database db = new Database(Database.databaseType.MySql, Config.DatabaseConfiguration.ConnectionString);
UserTable<ApplicationUser> userTable = new UserTable<ApplicationUser>(db);
userTable.SetAvatar(user, new byte[0]);
return Ok();
}
} }
} }

View File

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

View File

@@ -6,6 +6,12 @@ using System.Threading.Tasks;
using gaseous_server.Classes; using gaseous_server.Classes;
using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc;
using Asp.Versioning;
using Authentication;
using Microsoft.AspNetCore.Identity;
using gaseous_server.Models;
using IGDB.Models;
using gaseous_server.Classes.Metadata;
namespace gaseous_server.Controllers namespace gaseous_server.Controllers
{ {
@@ -16,6 +22,15 @@ namespace gaseous_server.Controllers
[Authorize] [Authorize]
public class BiosController : Controller public class BiosController : Controller
{ {
private readonly UserManager<ApplicationUser> _userManager;
private readonly SignInManager<ApplicationUser> _signInManager;
public BiosController(UserManager<ApplicationUser> userManager, SignInManager<ApplicationUser> signInManager)
{
_userManager = userManager;
_signInManager = signInManager;
}
[MapToApiVersion("1.0")] [MapToApiVersion("1.0")]
[MapToApiVersion("1.1")] [MapToApiVersion("1.1")]
[HttpGet] [HttpGet]
@@ -42,24 +57,51 @@ namespace gaseous_server.Controllers
[MapToApiVersion("1.1")] [MapToApiVersion("1.1")]
[HttpHead] [HttpHead]
[Route("zip/{PlatformId}")] [Route("zip/{PlatformId}")]
[Route("zip/{PlatformId}/{GameId}")]
[ProducesResponseType(typeof(FileStreamResult), StatusCodes.Status200OK)] [ProducesResponseType(typeof(FileStreamResult), StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status404NotFound)] [ProducesResponseType(StatusCodes.Status404NotFound)]
public ActionResult GetBiosCompressed(long PlatformId) public async Task<ActionResult> GetBiosCompressedAsync(long PlatformId, long GameId = -1, bool filtered = false)
{ {
try try
{ {
IGDB.Models.Platform platform = Classes.Metadata.Platforms.GetPlatform(PlatformId); Platform platform = Platforms.GetPlatform(PlatformId);
PlatformMapping.PlatformMapItem platformMap = PlatformMapping.GetPlatformMap(PlatformId);
string biosPath = Path.Combine(Config.LibraryConfiguration.LibraryBIOSDirectory, platform.Slug); List<string> biosHashes = new List<string>();
if (GameId == -1 || filtered == false)
{
// get all bios files for selected platform
biosHashes.AddRange(platformMap.EnabledBIOSHashes);
}
else
{
// get user platform map
var user = await _userManager.GetUserAsync(User);
PlatformMapping platformMapping = new PlatformMapping();
PlatformMapping.PlatformMapItem userPlatformMap = platformMapping.GetUserPlatformMap(user.Id, PlatformId, GameId);
biosHashes.AddRange(userPlatformMap.EnabledBIOSHashes);
}
// build zip file
string tempFile = Path.GetTempFileName(); string tempFile = Path.GetTempFileName();
using (FileStream zipFile = System.IO.File.Create(tempFile)) using (FileStream zipFile = System.IO.File.Create(tempFile))
using (var zipArchive = new ZipArchive(zipFile, ZipArchiveMode.Create)) using (var zipArchive = new ZipArchive(zipFile, ZipArchiveMode.Create))
{ {
foreach (string file in Directory.GetFiles(biosPath)) foreach (string hash in biosHashes)
{ {
zipArchive.CreateEntryFromFile(file, Path.GetFileName(file)); // get the bios data for the hash
foreach (PlatformMapping.PlatformMapItem.EmulatorBiosItem bios in platformMap.Bios)
{
if (bios.hash == hash)
{
// add the bios file to the zip
zipArchive.CreateEntryFromFile(Path.Combine(Config.LibraryConfiguration.LibraryFirmwareDirectory, hash + ".bios"), bios.filename);
}
}
} }
} }

View File

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

View File

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

View File

@@ -17,6 +17,8 @@ using Microsoft.AspNetCore.Identity;
using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc;
using Microsoft.CodeAnalysis.Scripting; using Microsoft.CodeAnalysis.Scripting;
using static gaseous_server.Classes.Metadata.AgeRatings; using static gaseous_server.Classes.Metadata.AgeRatings;
using Asp.Versioning;
using static gaseous_server.Models.PlatformMapping;
namespace gaseous_server.Controllers namespace gaseous_server.Controllers
{ {
@@ -29,7 +31,7 @@ namespace gaseous_server.Controllers
{ {
private readonly UserManager<ApplicationUser> _userManager; private readonly UserManager<ApplicationUser> _userManager;
private readonly SignInManager<ApplicationUser> _signInManager; private readonly SignInManager<ApplicationUser> _signInManager;
public GamesController( public GamesController(
UserManager<ApplicationUser> userManager, UserManager<ApplicationUser> userManager,
SignInManager<ApplicationUser> signInManager SignInManager<ApplicationUser> signInManager
@@ -42,7 +44,7 @@ namespace gaseous_server.Controllers
[MapToApiVersion("1.0")] [MapToApiVersion("1.0")]
[HttpGet] [HttpGet]
[ProducesResponseType(typeof(List<Game>), StatusCodes.Status200OK)] [ProducesResponseType(typeof(List<Game>), StatusCodes.Status200OK)]
public ActionResult Game( public async Task<ActionResult> Game(
string name = "", string name = "",
string platform = "", string platform = "",
string genre = "", string genre = "",
@@ -52,7 +54,7 @@ namespace gaseous_server.Controllers
int minrating = -1, int minrating = -1,
int maxrating = -1, int maxrating = -1,
bool sortdescending = false) bool sortdescending = false)
{ {
return Ok(GetGames(name, platform, genre, gamemode, playerperspective, theme, minrating, maxrating, "Adult", true, true, sortdescending)); return Ok(GetGames(name, platform, genre, gamemode, playerperspective, theme, minrating, maxrating, "Adult", true, true, sortdescending));
} }
@@ -103,7 +105,7 @@ namespace gaseous_server.Controllers
if (platform.Length > 0) if (platform.Length > 0)
{ {
tempVal = "Games_Roms.PlatformId IN ("; tempVal = "view_Games_Roms.PlatformId IN (";
string[] platformClauseItems = platform.Split(","); string[] platformClauseItems = platform.Split(",");
for (int i = 0; i < platformClauseItems.Length; i++) for (int i = 0; i < platformClauseItems.Length; i++)
{ {
@@ -279,9 +281,7 @@ namespace gaseous_server.Controllers
} }
Database db = new Database(Database.databaseType.MySql, Config.DatabaseConfiguration.ConnectionString); Database db = new Database(Database.databaseType.MySql, Config.DatabaseConfiguration.ConnectionString);
//string sql = "SELECT DISTINCT Games_Roms.GameId AS ROMGameId, Game.* FROM Games_Roms LEFT JOIN Game ON Game.Id = Games_Roms.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 " + whereClause + " " + havingClause + " " + orderByClause; string sql = "SELECT DISTINCT view_Games_Roms.GameId AS ROMGameId, Game.*, case when Game.`Name` like 'The %' then CONCAT(trim(substr(Game.`Name` from 4)), ', The') else Game.`Name` end as NameThe FROM view_Games_Roms LEFT JOIN Game ON Game.Id = view_Games_Roms.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 (SELECT Relation_Game_AgeRatings.GameId, AgeRating.* FROM Relation_Game_AgeRatings JOIN AgeRating ON Relation_Game_AgeRatings.AgeRatingsId = AgeRating.Id) view_AgeRatings ON Game.Id = view_AgeRatings.GameId " + whereClause + " " + havingClause + " " + orderByClause;
string sql = "SELECT DISTINCT Games_Roms.GameId AS ROMGameId, Game.*, case when Game.`Name` like 'The %' then CONCAT(trim(substr(Game.`Name` from 4)), ', The') else Game.`Name` end as NameThe FROM Games_Roms LEFT JOIN Game ON Game.Id = Games_Roms.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 (SELECT Relation_Game_AgeRatings.GameId, AgeRating.* FROM Relation_Game_AgeRatings JOIN AgeRating ON Relation_Game_AgeRatings.AgeRatingsId = AgeRating.Id) view_AgeRatings ON Game.Id = view_AgeRatings.GameId " + whereClause + " " + havingClause + " " + orderByClause;
List<IGDB.Models.Game> RetVal = new List<IGDB.Models.Game>(); List<IGDB.Models.Game> RetVal = new List<IGDB.Models.Game>();
@@ -302,7 +302,7 @@ namespace gaseous_server.Controllers
[ProducesResponseType(typeof(Game), StatusCodes.Status200OK)] [ProducesResponseType(typeof(Game), StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status404NotFound)] [ProducesResponseType(StatusCodes.Status404NotFound)]
[ResponseCache(CacheProfileName = "5Minute")] [ResponseCache(CacheProfileName = "5Minute")]
public ActionResult Game(long GameId) public async Task<ActionResult> Game(long GameId)
{ {
try try
{ {
@@ -330,7 +330,7 @@ namespace gaseous_server.Controllers
[ProducesResponseType(typeof(List<AlternativeName>), StatusCodes.Status200OK)] [ProducesResponseType(typeof(List<AlternativeName>), StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status404NotFound)] [ProducesResponseType(StatusCodes.Status404NotFound)]
[ResponseCache(CacheProfileName = "7Days")] [ResponseCache(CacheProfileName = "7Days")]
public ActionResult GameAlternativeNames(long GameId) public async Task<ActionResult> GameAlternativeNames(long GameId)
{ {
try try
{ {
@@ -363,7 +363,7 @@ namespace gaseous_server.Controllers
[ProducesResponseType(typeof(List<AgeRatings.GameAgeRating>), StatusCodes.Status200OK)] [ProducesResponseType(typeof(List<AgeRatings.GameAgeRating>), StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status404NotFound)] [ProducesResponseType(StatusCodes.Status404NotFound)]
[ResponseCache(CacheProfileName = "7Days")] [ResponseCache(CacheProfileName = "7Days")]
public ActionResult GameAgeClassification(long GameId) public async Task<ActionResult> GameAgeClassification(long GameId)
{ {
try try
{ {
@@ -396,7 +396,7 @@ namespace gaseous_server.Controllers
[ProducesResponseType(typeof(List<Artwork>), StatusCodes.Status200OK)] [ProducesResponseType(typeof(List<Artwork>), StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status404NotFound)] [ProducesResponseType(StatusCodes.Status404NotFound)]
[ResponseCache(CacheProfileName = "7Days")] [ResponseCache(CacheProfileName = "7Days")]
public ActionResult GameArtwork(long GameId) public async Task<ActionResult> GameArtwork(long GameId)
{ {
try try
{ {
@@ -427,7 +427,7 @@ namespace gaseous_server.Controllers
[ProducesResponseType(typeof(Artwork), StatusCodes.Status200OK)] [ProducesResponseType(typeof(Artwork), StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status404NotFound)] [ProducesResponseType(StatusCodes.Status404NotFound)]
[ResponseCache(CacheProfileName = "7Days")] [ResponseCache(CacheProfileName = "7Days")]
public ActionResult GameArtwork(long GameId, long ArtworkId) public async Task<ActionResult> GameArtwork(long GameId, long ArtworkId)
{ {
try try
{ {
@@ -456,73 +456,6 @@ namespace gaseous_server.Controllers
} }
} }
[MapToApiVersion("1.0")]
[MapToApiVersion("1.1")]
[HttpGet]
[Route("{GameId}/artwork/{ArtworkId}/image/{size}")]
[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)
{
try
{
IGDB.Models.Game gameObject = Classes.Metadata.Games.GetGame(GameId, false, false, false);
try
{
IGDB.Models.Artwork artworkObject = Artworks.GetArtwork(ArtworkId, Config.LibraryConfiguration.LibraryMetadataDirectory_Game(gameObject), true);
if (artworkObject != null) {
//string coverFilePath = Path.Combine(Config.LibraryConfiguration.LibraryMetadataDirectory_Game(gameObject), "Artwork", size.ToString(), artworkObject.ImageId + ".jpg");
string basePath = Path.Combine(Config.LibraryConfiguration.LibraryMetadataDirectory_Game(gameObject), "Artwork");
Communications comms = new Communications();
Task<string> ImgFetch = comms.GetSpecificImageFromServer(basePath, artworkObject.ImageId, size, new List<Communications.IGDBAPI_ImageSize>{ Communications.IGDBAPI_ImageSize.original });
string coverFilePath = ImgFetch.Result;
if (System.IO.File.Exists(coverFilePath))
{
string filename = artworkObject.ImageId + ".jpg";
string filepath = coverFilePath;
byte[] filedata = System.IO.File.ReadAllBytes(filepath);
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();
}
}
else
{
return NotFound();
}
}
catch
{
return NotFound();
}
}
catch
{
return NotFound();
}
}
[MapToApiVersion("1.0")] [MapToApiVersion("1.0")]
[MapToApiVersion("1.1")] [MapToApiVersion("1.1")]
[HttpGet] [HttpGet]
@@ -530,7 +463,7 @@ namespace gaseous_server.Controllers
[ProducesResponseType(typeof(Cover), StatusCodes.Status200OK)] [ProducesResponseType(typeof(Cover), StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status404NotFound)] [ProducesResponseType(StatusCodes.Status404NotFound)]
[ResponseCache(CacheProfileName = "7Days")] [ResponseCache(CacheProfileName = "7Days")]
public ActionResult GameCover(long GameId) public async Task<ActionResult> GameCover(long GameId)
{ {
try try
{ {
@@ -561,47 +494,126 @@ namespace gaseous_server.Controllers
[MapToApiVersion("1.0")] [MapToApiVersion("1.0")]
[MapToApiVersion("1.1")] [MapToApiVersion("1.1")]
[HttpGet] [HttpGet]
[Route("{GameId}/cover/image/{size}")] [Route("{GameId}/{ImageType}/{ImageId}/image/{size}")]
[Route("{GameId}/cover/image/{size}/{imagename}")] [Route("{GameId}/{ImageType}/{ImageId}/image/{size}/{imagename}")]
[ProducesResponseType(typeof(FileStreamResult), StatusCodes.Status200OK)] [ProducesResponseType(typeof(FileStreamResult), StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status404NotFound)] [ProducesResponseType(StatusCodes.Status404NotFound)]
public ActionResult GameCoverImage(long GameId, Communications.IGDBAPI_ImageSize size, string imagename = "") public async Task<ActionResult> GameImage(long GameId, MetadataImageType imageType, long ImageId, Communications.IGDBAPI_ImageSize size, string imagename = "")
{ {
try try
{ {
IGDB.Models.Game gameObject = Classes.Metadata.Games.GetGame(GameId, false, false, false); IGDB.Models.Game gameObject = Classes.Metadata.Games.GetGame(GameId, false, false, false);
if (gameObject.Cover != null) string? imageId = null;
string? imageTypePath = null;
switch (imageType)
{ {
if (gameObject.Cover.Id != null) case MetadataImageType.cover:
{ if (gameObject.Cover != null)
IGDB.Models.Cover cover = Classes.Metadata.Covers.GetCover(gameObject.Cover.Id, Config.LibraryConfiguration.LibraryMetadataDirectory_Game(gameObject), false); {
string basePath = Path.Combine(Config.LibraryConfiguration.LibraryMetadataDirectory_Game(gameObject), "Covers"); if (gameObject.Cover.Id != null)
Communications comms = new Communications();
Task<string> ImgFetch = comms.GetSpecificImageFromServer(basePath, cover.ImageId, size, new List<Communications.IGDBAPI_ImageSize>{ Communications.IGDBAPI_ImageSize.cover_big, Communications.IGDBAPI_ImageSize.original });
string coverFilePath = ImgFetch.Result;
if (System.IO.File.Exists(coverFilePath)) {
string filename = cover.ImageId + ".jpg";
string filepath = coverFilePath;
byte[] filedata = System.IO.File.ReadAllBytes(filepath);
string contentType = "image/jpg";
var cd = new System.Net.Mime.ContentDisposition
{ {
FileName = filename, IGDB.Models.Cover cover = Classes.Metadata.Covers.GetCover(gameObject.Cover.Id, Config.LibraryConfiguration.LibraryMetadataDirectory_Game(gameObject), false);
Inline = true, imageId = cover.ImageId;
}; imageTypePath = "Covers";
}
}
else
{
return NotFound();
}
break;
Response.Headers.Add("Content-Disposition", cd.ToString()); case MetadataImageType.screenshots:
Response.Headers.Add("Cache-Control", "public, max-age=604800"); if (gameObject.Screenshots != null)
{
if (gameObject.Screenshots.Ids.Contains(ImageId))
{
IGDB.Models.Screenshot imageObject = Screenshots.GetScreenshot(ImageId, Config.LibraryConfiguration.LibraryMetadataDirectory_Game(gameObject), true);
return File(filedata, contentType); imageId = imageObject.ImageId;
imageTypePath = "Screenshots";
}
}
else
{
return NotFound();
}
break;
case MetadataImageType.artwork:
if (gameObject.Artworks != null)
{
if (gameObject.Artworks.Ids.Contains(ImageId))
{
IGDB.Models.Artwork imageObject = Artworks.GetArtwork(ImageId, Config.LibraryConfiguration.LibraryMetadataDirectory_Game(gameObject), true);
imageId = imageObject.ImageId;
imageTypePath = "Artwork";
}
}
else
{
return NotFound();
}
break;
default:
return NotFound();
}
if (imageId == null)
{
return NotFound();
}
string basePath = Path.Combine(Config.LibraryConfiguration.LibraryMetadataDirectory_Game(gameObject), imageTypePath);
string imagePath = Path.Combine(Config.LibraryConfiguration.LibraryMetadataDirectory_Game(gameObject), imageTypePath, size.ToString(), imageId + ".jpg");
if (!System.IO.File.Exists(imagePath))
{
Communications comms = new Communications();
Task<string> ImgFetch = comms.GetSpecificImageFromServer(Path.Combine(Config.LibraryConfiguration.LibraryMetadataDirectory_Game(gameObject), imageTypePath), imageId, size, new List<Communications.IGDBAPI_ImageSize> { Communications.IGDBAPI_ImageSize.cover_big, Communications.IGDBAPI_ImageSize.original });
imagePath = ImgFetch.Result;
}
if (!System.IO.File.Exists(imagePath))
{
Communications comms = new Communications();
Task<string> ImgFetch = comms.GetSpecificImageFromServer(basePath, imageId, size, new List<Communications.IGDBAPI_ImageSize> { Communications.IGDBAPI_ImageSize.cover_big, Communications.IGDBAPI_ImageSize.original });
imagePath = ImgFetch.Result;
}
if (System.IO.File.Exists(imagePath))
{
string filename = imageId + ".jpg";
string filepath = imagePath;
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");
byte[] filedata = null;
using (FileStream fs = System.IO.File.OpenRead(filepath))
{
using (BinaryReader binaryReader = new BinaryReader(fs))
{
filedata = binaryReader.ReadBytes((int)fs.Length);
} }
} }
return File(filedata, contentType);
} }
return NotFound(); return NotFound();
} }
catch catch
@@ -610,6 +622,88 @@ namespace gaseous_server.Controllers
} }
} }
public enum MetadataImageType
{
cover,
screenshots,
artwork
}
[MapToApiVersion("1.0")]
[MapToApiVersion("1.1")]
[HttpGet]
[Route("{GameId}/favourite")]
[ProducesResponseType(typeof(bool), StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status404NotFound)]
public async Task<ActionResult> GameGetFavouriteAsync(long GameId)
{
try
{
IGDB.Models.Game gameObject = Classes.Metadata.Games.GetGame(GameId, false, false, false);
if (gameObject != null)
{
var user = await _userManager.GetUserAsync(User);
if (user != null)
{
Favourites favourites = new Favourites();
return Ok(favourites.GetFavourite(user.Id, GameId));
}
else
{
return NotFound();
}
}
else
{
return NotFound();
}
}
catch
{
return NotFound();
}
}
[MapToApiVersion("1.0")]
[MapToApiVersion("1.1")]
[HttpPost]
[Route("{GameId}/favourite")]
[ProducesResponseType(typeof(bool), StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status404NotFound)]
public async Task<ActionResult> GameSetFavouriteAsync(long GameId, bool favourite)
{
try
{
IGDB.Models.Game gameObject = Classes.Metadata.Games.GetGame(GameId, false, false, false);
if (gameObject != null)
{
var user = await _userManager.GetUserAsync(User);
if (user != null)
{
Favourites favourites = new Favourites();
return Ok(favourites.SetFavourite(user.Id, GameId, favourite));
}
else
{
return NotFound();
}
}
else
{
return NotFound();
}
}
catch
{
return NotFound();
}
}
[MapToApiVersion("1.0")] [MapToApiVersion("1.0")]
[MapToApiVersion("1.1")] [MapToApiVersion("1.1")]
[HttpGet] [HttpGet]
@@ -617,7 +711,7 @@ namespace gaseous_server.Controllers
[ProducesResponseType(typeof(List<Genre>), StatusCodes.Status200OK)] [ProducesResponseType(typeof(List<Genre>), StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status404NotFound)] [ProducesResponseType(StatusCodes.Status404NotFound)]
[ResponseCache(CacheProfileName = "7Days")] [ResponseCache(CacheProfileName = "7Days")]
public ActionResult GameGenre(long GameId) public async Task<ActionResult> GameGenre(long GameId)
{ {
try try
{ {
@@ -655,7 +749,7 @@ namespace gaseous_server.Controllers
[ProducesResponseType(typeof(List<Dictionary<string, object>>), StatusCodes.Status200OK)] [ProducesResponseType(typeof(List<Dictionary<string, object>>), StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status404NotFound)] [ProducesResponseType(StatusCodes.Status404NotFound)]
[ResponseCache(CacheProfileName = "7Days")] [ResponseCache(CacheProfileName = "7Days")]
public ActionResult GameInvolvedCompanies(long GameId) public async Task<ActionResult> GameInvolvedCompanies(long GameId)
{ {
try try
{ {
@@ -700,7 +794,7 @@ namespace gaseous_server.Controllers
[ProducesResponseType(typeof(Dictionary<string, object>), StatusCodes.Status200OK)] [ProducesResponseType(typeof(Dictionary<string, object>), StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status404NotFound)] [ProducesResponseType(StatusCodes.Status404NotFound)]
[ResponseCache(CacheProfileName = "7Days")] [ResponseCache(CacheProfileName = "7Days")]
public ActionResult GameInvolvedCompanies(long GameId, long CompanyId) public async Task<ActionResult> GameInvolvedCompanies(long GameId, long CompanyId)
{ {
try try
{ {
@@ -720,7 +814,8 @@ namespace gaseous_server.Controllers
companyData.Add("company", company); companyData.Add("company", company);
return Ok(companyData); return Ok(companyData);
} else }
else
{ {
return NotFound(); return NotFound();
} }
@@ -742,7 +837,7 @@ namespace gaseous_server.Controllers
[Route("{GameId}/companies/{CompanyId}/image")] [Route("{GameId}/companies/{CompanyId}/image")]
[ProducesResponseType(typeof(FileStreamResult), StatusCodes.Status200OK)] [ProducesResponseType(typeof(FileStreamResult), StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status404NotFound)] [ProducesResponseType(StatusCodes.Status404NotFound)]
public ActionResult GameCompanyImage(long GameId, long CompanyId) public async Task<ActionResult> GameCompanyImage(long GameId, long CompanyId)
{ {
try try
{ {
@@ -784,14 +879,143 @@ namespace gaseous_server.Controllers
[MapToApiVersion("1.0")] [MapToApiVersion("1.0")]
[MapToApiVersion("1.1")] [MapToApiVersion("1.1")]
[HttpGet] [HttpGet]
[Route("{GameId}/platforms")] [Route("{GameId}/emulatorconfiguration/{PlatformId}")]
[ProducesResponseType(typeof(List<KeyValuePair<long, string>>), StatusCodes.Status200OK)] [Authorize]
[ProducesResponseType(typeof(UserEmulatorConfiguration), StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status404NotFound)] [ProducesResponseType(StatusCodes.Status404NotFound)]
public ActionResult GamePlatforms(long GameId) public async Task<ActionResult> GetGameEmulator(long GameId, long PlatformId)
{ {
try try
{ {
return Ok(Games.GetAvailablePlatforms(GameId)); IGDB.Models.Game gameObject = Classes.Metadata.Games.GetGame(GameId, false, false, false);
if (gameObject != null)
{
IGDB.Models.Platform platformObject = Classes.Metadata.Platforms.GetPlatform(PlatformId);
if (platformObject != null)
{
var user = await _userManager.GetUserAsync(User);
if (user != null)
{
PlatformMapping platformMapping = new PlatformMapping();
UserEmulatorConfiguration platformMappingObject = platformMapping.GetUserEmulator(user.Id, GameId, PlatformId);
if (platformMappingObject != null)
{
return Ok(platformMappingObject);
}
}
}
}
return NotFound();
}
catch
{
return NotFound();
}
}
[MapToApiVersion("1.0")]
[MapToApiVersion("1.1")]
[HttpPost]
[Route("{GameId}/emulatorconfiguration/{PlatformId}")]
[Authorize]
[ProducesResponseType(StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status404NotFound)]
public async Task<ActionResult> SetGameEmulator(long GameId, long PlatformId, UserEmulatorConfiguration configuration)
{
try
{
IGDB.Models.Game gameObject = Classes.Metadata.Games.GetGame(GameId, false, false, false);
if (gameObject != null)
{
IGDB.Models.Platform platformObject = Classes.Metadata.Platforms.GetPlatform(PlatformId);
if (platformObject != null)
{
var user = await _userManager.GetUserAsync(User);
if (user != null)
{
PlatformMapping platformMapping = new PlatformMapping();
platformMapping.SetUserEmulator(user.Id, GameId, PlatformId, configuration);
return Ok();
}
}
}
return NotFound();
}
catch
{
return NotFound();
}
}
[MapToApiVersion("1.0")]
[MapToApiVersion("1.1")]
[HttpDelete]
[Route("{GameId}/emulatorconfiguration/{PlatformId}")]
[Authorize]
[ProducesResponseType(StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status404NotFound)]
public async Task<ActionResult> DeleteGameEmulator(long GameId, long PlatformId)
{
try
{
IGDB.Models.Game gameObject = Classes.Metadata.Games.GetGame(GameId, false, false, false);
if (gameObject != null)
{
IGDB.Models.Platform platformObject = Classes.Metadata.Platforms.GetPlatform(PlatformId);
if (platformObject != null)
{
var user = await _userManager.GetUserAsync(User);
if (user != null)
{
PlatformMapping platformMapping = new PlatformMapping();
platformMapping.DeleteUserEmulator(user.Id, GameId, PlatformId);
return Ok();
}
}
}
return NotFound();
}
catch
{
return NotFound();
}
}
[MapToApiVersion("1.0")]
[MapToApiVersion("1.1")]
[HttpGet]
[Route("{GameId}/platforms")]
[ProducesResponseType(typeof(List<Games.AvailablePlatformItem>), StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status404NotFound)]
public async Task<ActionResult> GamePlatforms(long GameId)
{
try
{
var user = await _userManager.GetUserAsync(User);
if (user != null)
{
return Ok(Games.GetAvailablePlatforms(user.Id, GameId));
}
else
{
return NotFound();
}
} }
catch catch
{ {
@@ -806,7 +1030,7 @@ namespace gaseous_server.Controllers
[ProducesResponseType(typeof(List<ReleaseDate>), StatusCodes.Status200OK)] [ProducesResponseType(typeof(List<ReleaseDate>), StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status404NotFound)] [ProducesResponseType(StatusCodes.Status404NotFound)]
[ResponseCache(CacheProfileName = "7Days")] [ResponseCache(CacheProfileName = "7Days")]
public ActionResult GameReleaseDates(long GameId) public async Task<ActionResult> GameReleaseDates(long GameId)
{ {
try try
{ {
@@ -819,7 +1043,7 @@ namespace gaseous_server.Controllers
foreach (long icId in gameObject.ReleaseDates.Ids) foreach (long icId in gameObject.ReleaseDates.Ids)
{ {
ReleaseDate releaseDate = Classes.Metadata.ReleaseDates.GetReleaseDates(icId); ReleaseDate releaseDate = Classes.Metadata.ReleaseDates.GetReleaseDates(icId);
rdObjects.Add(releaseDate); rdObjects.Add(releaseDate);
} }
} }
@@ -847,7 +1071,7 @@ namespace gaseous_server.Controllers
public async Task<ActionResult> GameRomAsync(long GameId, int pageNumber = 0, int pageSize = 0, long PlatformId = -1, string NameSearch = "") public async Task<ActionResult> GameRomAsync(long GameId, int pageNumber = 0, int pageSize = 0, long PlatformId = -1, string NameSearch = "")
{ {
var user = await _userManager.GetUserAsync(User); var user = await _userManager.GetUserAsync(User);
try try
{ {
Game gameObject = Classes.Metadata.Games.GetGame(GameId, false, false, false); Game gameObject = Classes.Metadata.Games.GetGame(GameId, false, false, false);
@@ -867,7 +1091,7 @@ namespace gaseous_server.Controllers
[ProducesResponseType(typeof(Classes.Roms.GameRomItem), StatusCodes.Status200OK)] [ProducesResponseType(typeof(Classes.Roms.GameRomItem), StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status404NotFound)] [ProducesResponseType(StatusCodes.Status404NotFound)]
//[ResponseCache(CacheProfileName = "5Minute")] //[ResponseCache(CacheProfileName = "5Minute")]
public ActionResult GameRom(long GameId, long RomId) public async Task<ActionResult> GameRom(long GameId, long RomId)
{ {
try try
{ {
@@ -896,7 +1120,7 @@ namespace gaseous_server.Controllers
[Route("{GameId}/roms/{RomId}")] [Route("{GameId}/roms/{RomId}")]
[ProducesResponseType(typeof(Classes.Roms.GameRomItem), StatusCodes.Status200OK)] [ProducesResponseType(typeof(Classes.Roms.GameRomItem), StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status404NotFound)] [ProducesResponseType(StatusCodes.Status404NotFound)]
public ActionResult GameRomRename(long GameId, long RomId, long NewPlatformId, long NewGameId) public async Task<ActionResult> GameRomRename(long GameId, long RomId, long NewPlatformId, long NewGameId)
{ {
try try
{ {
@@ -926,7 +1150,7 @@ namespace gaseous_server.Controllers
[Route("{GameId}/roms/{RomId}")] [Route("{GameId}/roms/{RomId}")]
[ProducesResponseType(typeof(Classes.Roms.GameRomItem), StatusCodes.Status200OK)] [ProducesResponseType(typeof(Classes.Roms.GameRomItem), StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status404NotFound)] [ProducesResponseType(StatusCodes.Status404NotFound)]
public ActionResult GameRomDelete(long GameId, long RomId) public async Task<ActionResult> GameRomDelete(long GameId, long RomId)
{ {
try try
{ {
@@ -949,6 +1173,67 @@ namespace gaseous_server.Controllers
} }
} }
[MapToApiVersion("1.0")]
[MapToApiVersion("1.1")]
[HttpPost]
[Route("{GameId}/roms/{RomId}/{PlatformId}/favourite")]
[ProducesResponseType(StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status404NotFound)]
public async Task<ActionResult> GameRomFavourite(long GameId, long RomId, long PlatformId, bool IsMediaGroup, bool favourite)
{
try
{
ApplicationUser? user = await _userManager.GetUserAsync(User);
Game gameObject = Classes.Metadata.Games.GetGame(GameId, false, false, false);
if (IsMediaGroup == false)
{
Classes.Roms.GameRomItem rom = Classes.Roms.GetRom(RomId);
if (rom.GameId == GameId)
{
if (favourite == true)
{
Classes.Metadata.Games.GameSetFavouriteRom(user.Id, GameId, PlatformId, RomId, IsMediaGroup);
}
else
{
Classes.Metadata.Games.GameClearFavouriteRom(user.Id, GameId, PlatformId);
}
return Ok();
}
else
{
return NotFound();
}
}
else
{
Classes.RomMediaGroup.GameRomMediaGroupItem rom = Classes.RomMediaGroup.GetMediaGroup(RomId, user.Id);
if (rom.GameId == GameId)
{
if (favourite == true)
{
Classes.Metadata.Games.GameSetFavouriteRom(user.Id, GameId, PlatformId, RomId, IsMediaGroup);
}
else
{
Classes.Metadata.Games.GameClearFavouriteRom(user.Id, GameId, PlatformId);
}
return Ok();
}
else
{
return NotFound();
}
}
}
catch
{
return NotFound();
}
}
[MapToApiVersion("1.0")] [MapToApiVersion("1.0")]
[MapToApiVersion("1.1")] [MapToApiVersion("1.1")]
[HttpGet] [HttpGet]
@@ -958,7 +1243,7 @@ namespace gaseous_server.Controllers
[Route("{GameId}/roms/{RomId}/file")] [Route("{GameId}/roms/{RomId}/file")]
[ProducesResponseType(typeof(FileStreamResult), StatusCodes.Status200OK)] [ProducesResponseType(typeof(FileStreamResult), StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status404NotFound)] [ProducesResponseType(StatusCodes.Status404NotFound)]
public ActionResult GameRomFile(long GameId, long RomId) public async Task<ActionResult> GameRomFile(long GameId, long RomId)
{ {
try try
{ {
@@ -997,7 +1282,7 @@ namespace gaseous_server.Controllers
[Route("{GameId}/roms/{RomId}/{FileName}")] [Route("{GameId}/roms/{RomId}/{FileName}")]
[ProducesResponseType(typeof(FileStreamResult), StatusCodes.Status200OK)] [ProducesResponseType(typeof(FileStreamResult), StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status404NotFound)] [ProducesResponseType(StatusCodes.Status404NotFound)]
public ActionResult GameRomFile(long GameId, long RomId, string FileName) public async Task<ActionResult> GameRomFile(long GameId, long RomId, string FileName)
{ {
try try
{ {
@@ -1037,7 +1322,7 @@ namespace gaseous_server.Controllers
public async Task<ActionResult> GameRomGroupAsync(long GameId, long RomGroupId) public async Task<ActionResult> GameRomGroupAsync(long GameId, long RomGroupId)
{ {
var user = await _userManager.GetUserAsync(User); var user = await _userManager.GetUserAsync(User);
try try
{ {
Game gameObject = Classes.Metadata.Games.GetGame(GameId, false, false, false); Game gameObject = Classes.Metadata.Games.GetGame(GameId, false, false, false);
@@ -1061,21 +1346,20 @@ namespace gaseous_server.Controllers
[MapToApiVersion("1.0")] [MapToApiVersion("1.0")]
[MapToApiVersion("1.1")] [MapToApiVersion("1.1")]
[HttpGet] [HttpGet]
[Authorize(Roles = "Admin,Gamer")]
[Route("{GameId}/romgroup")] [Route("{GameId}/romgroup")]
[ProducesResponseType(typeof(List<RomMediaGroup.GameRomMediaGroupItem>), StatusCodes.Status200OK)] [ProducesResponseType(typeof(List<RomMediaGroup.GameRomMediaGroupItem>), StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status404NotFound)] [ProducesResponseType(StatusCodes.Status404NotFound)]
public async Task<ActionResult> GetGameRomGroupAsync(long GameId) public async Task<ActionResult> GetGameRomGroupAsync(long GameId, long? PlatformId = null)
{ {
var user = await _userManager.GetUserAsync(User); var user = await _userManager.GetUserAsync(User);
try try
{ {
Game gameObject = Classes.Metadata.Games.GetGame(GameId, false, false, false); Game gameObject = Classes.Metadata.Games.GetGame(GameId, false, false, false);
try try
{ {
return Ok(RomMediaGroup.GetMediaGroupsFromGameId(GameId, user.Id)); return Ok(RomMediaGroup.GetMediaGroupsFromGameId(GameId, user.Id, PlatformId));
} }
catch (Exception ex) catch (Exception ex)
{ {
@@ -1096,7 +1380,7 @@ namespace gaseous_server.Controllers
[Route("{GameId}/romgroup")] [Route("{GameId}/romgroup")]
[ProducesResponseType(typeof(Classes.RomMediaGroup.GameRomMediaGroupItem), StatusCodes.Status200OK)] [ProducesResponseType(typeof(Classes.RomMediaGroup.GameRomMediaGroupItem), StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status404NotFound)] [ProducesResponseType(StatusCodes.Status404NotFound)]
public ActionResult NewGameRomGroup(long GameId, long PlatformId, [FromBody] List<long> RomIds) public async Task<ActionResult> NewGameRomGroup(long GameId, long PlatformId, [FromBody] List<long> RomIds)
{ {
try try
{ {
@@ -1128,7 +1412,7 @@ namespace gaseous_server.Controllers
public async Task<ActionResult> GameRomGroupMembersAsync(long GameId, long RomGroupId, [FromBody] List<long> RomIds) public async Task<ActionResult> GameRomGroupMembersAsync(long GameId, long RomGroupId, [FromBody] List<long> RomIds)
{ {
var user = await _userManager.GetUserAsync(User); var user = await _userManager.GetUserAsync(User);
try try
{ {
Game gameObject = Classes.Metadata.Games.GetGame(GameId, false, false, false); Game gameObject = Classes.Metadata.Games.GetGame(GameId, false, false, false);
@@ -1157,7 +1441,7 @@ namespace gaseous_server.Controllers
[Route("{GameId}/romgroup/{RomGroupId}")] [Route("{GameId}/romgroup/{RomGroupId}")]
[ProducesResponseType(typeof(Classes.RomMediaGroup.GameRomMediaGroupItem), StatusCodes.Status200OK)] [ProducesResponseType(typeof(Classes.RomMediaGroup.GameRomMediaGroupItem), StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status404NotFound)] [ProducesResponseType(StatusCodes.Status404NotFound)]
public ActionResult GameRomGroupDelete(long GameId, long RomGroupId) public async Task<ActionResult> GameRomGroupDelete(long GameId, long RomGroupId)
{ {
try try
{ {
@@ -1190,7 +1474,7 @@ namespace gaseous_server.Controllers
[Route("{GameId}/romgroup/{RomGroupId}/{filename}")] [Route("{GameId}/romgroup/{RomGroupId}/{filename}")]
[ProducesResponseType(typeof(FileStreamResult), StatusCodes.Status200OK)] [ProducesResponseType(typeof(FileStreamResult), StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status404NotFound)] [ProducesResponseType(StatusCodes.Status404NotFound)]
public ActionResult GameRomGroupFile(long GameId, long RomGroupId, string filename = "") public async Task<ActionResult> GameRomGroupFile(long GameId, long RomGroupId, string filename = "")
{ {
try try
{ {
@@ -1235,7 +1519,7 @@ namespace gaseous_server.Controllers
[Route("search")] [Route("search")]
[ProducesResponseType(typeof(List<Game>), StatusCodes.Status200OK)] [ProducesResponseType(typeof(List<Game>), StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status404NotFound)] [ProducesResponseType(StatusCodes.Status404NotFound)]
public ActionResult GameSearch(long RomId = 0, string SearchString = "") public async Task<ActionResult> GameSearch(long RomId = 0, string SearchString = "")
{ {
try try
{ {
@@ -1276,7 +1560,7 @@ namespace gaseous_server.Controllers
[ProducesResponseType(typeof(List<Screenshot>), StatusCodes.Status200OK)] [ProducesResponseType(typeof(List<Screenshot>), StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status404NotFound)] [ProducesResponseType(StatusCodes.Status404NotFound)]
[ResponseCache(CacheProfileName = "7Days")] [ResponseCache(CacheProfileName = "7Days")]
public ActionResult GameScreenshot(long GameId) public async Task<ActionResult> GameScreenshot(long GameId)
{ {
try try
{ {
@@ -1307,12 +1591,13 @@ namespace gaseous_server.Controllers
[ProducesResponseType(typeof(Screenshot), StatusCodes.Status200OK)] [ProducesResponseType(typeof(Screenshot), StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status404NotFound)] [ProducesResponseType(StatusCodes.Status404NotFound)]
[ResponseCache(CacheProfileName = "7Days")] [ResponseCache(CacheProfileName = "7Days")]
public ActionResult GameScreenshot(long GameId, long ScreenshotId) public async Task<ActionResult> GameScreenshot(long GameId, long ScreenshotId)
{ {
try try
{ {
IGDB.Models.Game gameObject = Classes.Metadata.Games.GetGame(GameId, false, false, false); IGDB.Models.Game gameObject = Classes.Metadata.Games.GetGame(GameId, false, false, false);
if (gameObject != null) { if (gameObject != null)
{
IGDB.Models.Screenshot screenshotObject = Screenshots.GetScreenshot(ScreenshotId, Config.LibraryConfiguration.LibraryMetadataDirectory_Game(gameObject), false); IGDB.Models.Screenshot screenshotObject = Screenshots.GetScreenshot(ScreenshotId, Config.LibraryConfiguration.LibraryMetadataDirectory_Game(gameObject), false);
if (screenshotObject != null) if (screenshotObject != null)
{ {
@@ -1334,56 +1619,56 @@ namespace gaseous_server.Controllers
} }
} }
[MapToApiVersion("1.0")] // [MapToApiVersion("1.0")]
[MapToApiVersion("1.1")] // [MapToApiVersion("1.1")]
[HttpGet] // [HttpGet]
[Route("{GameId}/screenshots/{ScreenshotId}/image/{size}")] // [Route("{GameId}/screenshots/{ScreenshotId}/image/{size}")]
[Route("{GameId}/screenshots/{ScreenshotId}/image/{size}/{ImageName}")] // [Route("{GameId}/screenshots/{ScreenshotId}/image/{size}/{ImageName}")]
[ProducesResponseType(typeof(FileStreamResult), StatusCodes.Status200OK)] // [ProducesResponseType(typeof(FileStreamResult), StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status404NotFound)] // [ProducesResponseType(StatusCodes.Status404NotFound)]
public ActionResult GameScreenshotImage(long GameId, long ScreenshotId, Communications.IGDBAPI_ImageSize Size, string ImageName) // public async Task<ActionResult> GameScreenshotImage(long GameId, long ScreenshotId, Communications.IGDBAPI_ImageSize Size, string ImageName)
{ // {
try // try
{ // {
IGDB.Models.Game gameObject = Classes.Metadata.Games.GetGame(GameId, false, false, false); // IGDB.Models.Game gameObject = Classes.Metadata.Games.GetGame(GameId, false, false, false);
IGDB.Models.Screenshot screenshotObject = Screenshots.GetScreenshot(ScreenshotId, Config.LibraryConfiguration.LibraryMetadataDirectory_Game(gameObject), true); // IGDB.Models.Screenshot screenshotObject = Screenshots.GetScreenshot(ScreenshotId, Config.LibraryConfiguration.LibraryMetadataDirectory_Game(gameObject), true);
string basePath = Path.Combine(Config.LibraryConfiguration.LibraryMetadataDirectory_Game(gameObject), "Screenshots"); // string basePath = Path.Combine(Config.LibraryConfiguration.LibraryMetadataDirectory_Game(gameObject), "Screenshots");
Communications comms = new Communications(); // Communications comms = new Communications();
Task<string> ImgFetch = comms.GetSpecificImageFromServer(basePath, screenshotObject.ImageId, Size, new List<Communications.IGDBAPI_ImageSize>{ Communications.IGDBAPI_ImageSize.original }); // Task<string> ImgFetch = comms.GetSpecificImageFromServer(basePath, screenshotObject.ImageId, Size, new List<Communications.IGDBAPI_ImageSize> { Communications.IGDBAPI_ImageSize.original });
string coverFilePath = ImgFetch.Result; // string coverFilePath = ImgFetch.Result;
if (System.IO.File.Exists(coverFilePath)) // if (System.IO.File.Exists(coverFilePath))
{ // {
string filename = screenshotObject.ImageId + ".jpg"; // string filename = screenshotObject.ImageId + ".jpg";
string filepath = coverFilePath; // string filepath = coverFilePath;
byte[] filedata = System.IO.File.ReadAllBytes(filepath); // byte[] filedata = System.IO.File.ReadAllBytes(filepath);
string contentType = "image/jpg"; // string contentType = "image/jpg";
var cd = new System.Net.Mime.ContentDisposition // var cd = new System.Net.Mime.ContentDisposition
{ // {
FileName = filename, // FileName = filename,
Inline = true, // Inline = true,
}; // };
Response.Headers.Add("Content-Disposition", cd.ToString()); // Response.Headers.Add("Content-Disposition", cd.ToString());
Response.Headers.Add("Cache-Control", "public, max-age=604800"); // Response.Headers.Add("Cache-Control", "public, max-age=604800");
return File(filedata, contentType); // return File(filedata, contentType);
} // }
else // else
{ // {
return NotFound(); // return NotFound();
} // }
} // }
catch // catch
{ // {
return NotFound(); // return NotFound();
} // }
} // }
[MapToApiVersion("1.0")] [MapToApiVersion("1.0")]
[MapToApiVersion("1.1")] [MapToApiVersion("1.1")]
@@ -1392,7 +1677,7 @@ namespace gaseous_server.Controllers
[ProducesResponseType(typeof(List<GameVideo>), StatusCodes.Status200OK)] [ProducesResponseType(typeof(List<GameVideo>), StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status404NotFound)] [ProducesResponseType(StatusCodes.Status404NotFound)]
[ResponseCache(CacheProfileName = "7Days")] [ResponseCache(CacheProfileName = "7Days")]
public ActionResult GameVideo(long GameId) public async Task<ActionResult> GameVideo(long GameId)
{ {
try try
{ {

View File

@@ -5,6 +5,7 @@ using System.Linq;
using System.Threading.Tasks; using System.Threading.Tasks;
using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc;
using Asp.Versioning;
namespace gaseous_server.Controllers namespace gaseous_server.Controllers
{ {
@@ -85,5 +86,24 @@ namespace gaseous_server.Controllers
return NotFound(exLNF.ToString()); return NotFound(exLNF.ToString());
} }
} }
[MapToApiVersion("1.0")]
[MapToApiVersion("1.1")]
[HttpPost("{LibraryId}/Scan")]
[ProducesResponseType(StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status400BadRequest)]
[ProducesResponseType(StatusCodes.Status404NotFound)]
public ActionResult ScanLibrary(int LibraryId)
{
try
{
GameLibrary.ScanLibrary(LibraryId);
return Ok();
}
catch (GameLibrary.LibraryNotFound exLNF)
{
return NotFound(exLNF.ToString());
}
}
} }
} }

View File

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

View File

@@ -14,6 +14,7 @@ using Microsoft.AspNetCore.Mvc;
using Microsoft.CodeAnalysis.Scripting; using Microsoft.CodeAnalysis.Scripting;
using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Authorization;
using System.Text; using System.Text;
using Asp.Versioning;
namespace gaseous_server.Controllers namespace gaseous_server.Controllers
{ {
@@ -148,35 +149,6 @@ namespace gaseous_server.Controllers
return Ok(new { count = files.Count, size }); return Ok(new { count = files.Count, size });
} }
// [MapToApiVersion("1.0")]
[MapToApiVersion("1.1")]
[HttpPost]
// [Route("{PlatformId}")]
// [ProducesResponseType(typeof(PlatformMapping.PlatformMapItem), StatusCodes.Status200OK)]
// [ProducesResponseType(StatusCodes.Status404NotFound)]
// [ProducesResponseType(StatusCodes.Status409Conflict)]
// public ActionResult NewPlatformMap(long PlatformId, PlatformMapping.PlatformMapItem Map)
// {
// try
// {
// PlatformMapping.PlatformMapItem platformMapItem = PlatformMapping.GetPlatformMap(PlatformId);
// if (platformMapItem != null)
// {
// return Conflict();
// }
// else
// {
// PlatformMapping.WritePlatformMap(Map, false, false);
// return Ok(PlatformMapping.GetPlatformMap(PlatformId));
// }
// }
// catch
// {
// return NotFound();
// }
// }
[MapToApiVersion("1.0")] [MapToApiVersion("1.0")]
[MapToApiVersion("1.1")] [MapToApiVersion("1.1")]
[HttpPatch] [HttpPatch]

View File

@@ -13,6 +13,7 @@ using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc;
using Microsoft.CodeAnalysis.Scripting; using Microsoft.CodeAnalysis.Scripting;
using Asp.Versioning;
namespace gaseous_server.Controllers namespace gaseous_server.Controllers
{ {
@@ -36,7 +37,7 @@ namespace gaseous_server.Controllers
{ {
Database db = new Database(Database.databaseType.MySql, Config.DatabaseConfiguration.ConnectionString); Database db = new Database(Database.databaseType.MySql, Config.DatabaseConfiguration.ConnectionString);
string sql = "SELECT * FROM Platform WHERE Id IN (SELECT DISTINCT PlatformId FROM Games_Roms) ORDER BY `Name` ASC;"; string sql = "SELECT * FROM Platform WHERE Id IN (SELECT DISTINCT PlatformId FROM view_Games_Roms) ORDER BY `Name` ASC;";
List<Platform> RetVal = new List<Platform>(); List<Platform> RetVal = new List<Platform>();
@@ -113,22 +114,64 @@ namespace gaseous_server.Controllers
[MapToApiVersion("1.0")] [MapToApiVersion("1.0")]
[MapToApiVersion("1.1")] [MapToApiVersion("1.1")]
[HttpGet] [HttpGet]
[Route("{PlatformId}/platformlogo/image")] [Route("{PlatformId}/platformlogo/{size}/logo.png")]
[ProducesResponseType(typeof(FileStreamResult), StatusCodes.Status200OK)] [ProducesResponseType(typeof(FileStreamResult), StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status404NotFound)] [ProducesResponseType(StatusCodes.Status404NotFound)]
public ActionResult PlatformLogoImage(long PlatformId) public async Task<ActionResult> GameImage(long PlatformId, Communications.IGDBAPI_ImageSize size)
{ {
try try
{ {
IGDB.Models.Platform platformObject = Classes.Metadata.Platforms.GetPlatform(PlatformId); IGDB.Models.Platform platformObject = Classes.Metadata.Platforms.GetPlatform(PlatformId);
IGDB.Models.PlatformLogo? logoObject = null;
string logoFilePath = Path.Combine(Config.LibraryConfiguration.LibraryMetadataDirectory_Platform(platformObject), "Logo_Medium.png"); try
if (System.IO.File.Exists(logoFilePath))
{ {
string filename = "Logo.png"; logoObject = PlatformLogos.GetPlatformLogo(platformObject.PlatformLogo.Id, Config.LibraryConfiguration.LibraryMetadataDirectory_Platform(platformObject));
string filepath = logoFilePath; }
byte[] filedata = System.IO.File.ReadAllBytes(filepath); catch
string contentType = "image/png"; {
// getting the logo failed, so we'll try a platform variant if available
if (platformObject.Versions != null)
{
if (platformObject.Versions.Ids.Length > 0)
{
IGDB.Models.PlatformVersion platformVersion = Classes.Metadata.PlatformVersions.GetPlatformVersion(platformObject.Versions.Ids[0], platformObject);
logoObject = PlatformLogos.GetPlatformLogo(platformVersion.PlatformLogo.Id, Config.LibraryConfiguration.LibraryMetadataDirectory_Platform(platformObject));
}
else
{
return NotFound();
}
}
else
{
return NotFound();
}
}
string basePath = Path.Combine(Config.LibraryConfiguration.LibraryMetadataDirectory_Platform(platformObject));
string imagePath = Path.Combine(basePath, size.ToString(), logoObject.ImageId + ".jpg");
if (!System.IO.File.Exists(imagePath))
{
Communications comms = new Communications();
Task<string> ImgFetch = comms.GetSpecificImageFromServer(Path.Combine(Config.LibraryConfiguration.LibraryMetadataDirectory_Platform(platformObject)), logoObject.ImageId, size, new List<Communications.IGDBAPI_ImageSize> { Communications.IGDBAPI_ImageSize.cover_big, Communications.IGDBAPI_ImageSize.original });
imagePath = ImgFetch.Result;
}
if (!System.IO.File.Exists(imagePath))
{
Communications comms = new Communications();
Task<string> ImgFetch = comms.GetSpecificImageFromServer(basePath, logoObject.ImageId, size, new List<Communications.IGDBAPI_ImageSize> { Communications.IGDBAPI_ImageSize.cover_big, Communications.IGDBAPI_ImageSize.original });
imagePath = ImgFetch.Result;
}
if (System.IO.File.Exists(imagePath))
{
string filename = logoObject.ImageId + ".jpg";
string filepath = imagePath;
string contentType = "image/jpg";
var cd = new System.Net.Mime.ContentDisposition var cd = new System.Net.Mime.ContentDisposition
{ {
@@ -137,13 +180,21 @@ namespace gaseous_server.Controllers
}; };
Response.Headers.Add("Content-Disposition", cd.ToString()); Response.Headers.Add("Content-Disposition", cd.ToString());
Response.Headers.Add("Cache-Control", "public, max-age=604800");
byte[] filedata = null;
using (FileStream fs = System.IO.File.OpenRead(filepath))
{
using (BinaryReader binaryReader = new BinaryReader(fs))
{
filedata = binaryReader.ReadBytes((int)fs.Length);
}
}
return File(filedata, contentType); return File(filedata, contentType);
} }
else
{ return NotFound();
return NotFound();
}
} }
catch catch
{ {

View File

@@ -14,6 +14,7 @@ using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc;
using Microsoft.CodeAnalysis.Scripting; using Microsoft.CodeAnalysis.Scripting;
using static gaseous_server.Classes.Metadata.AgeRatings; using static gaseous_server.Classes.Metadata.AgeRatings;
using Asp.Versioning;
namespace gaseous_server.Controllers namespace gaseous_server.Controllers
{ {
@@ -30,63 +31,70 @@ namespace gaseous_server.Controllers
[Authorize(Roles = "Admin,Gamer")] [Authorize(Roles = "Admin,Gamer")]
[ProducesResponseType(typeof(List<IFormFile>), StatusCodes.Status200OK)] [ProducesResponseType(typeof(List<IFormFile>), StatusCodes.Status200OK)]
[RequestSizeLimit(long.MaxValue)] [RequestSizeLimit(long.MaxValue)]
[Consumes("multipart/form-data")]
[DisableRequestSizeLimit, RequestFormLimits(MultipartBodyLengthLimit = long.MaxValue, ValueLengthLimit = int.MaxValue)] [DisableRequestSizeLimit, RequestFormLimits(MultipartBodyLengthLimit = long.MaxValue, ValueLengthLimit = int.MaxValue)]
public async Task<IActionResult> UploadRom(List<IFormFile> files, long? OverridePlatformId = null) public async Task<IActionResult> UploadRom(IFormFile file, long? OverridePlatformId = null)
{ {
Guid sessionid = Guid.NewGuid(); Guid sessionid = Guid.NewGuid();
string workPath = Path.Combine(Config.LibraryConfiguration.LibraryUploadDirectory, sessionid.ToString()); string workPath = Path.Combine(Config.LibraryConfiguration.LibraryUploadDirectory, sessionid.ToString());
long size = files.Sum(f => f.Length); if (file.Length > 0)
List<Dictionary<string, object>> UploadedFiles = new List<Dictionary<string, object>>();
foreach (IFormFile formFile in files)
{ {
if (formFile.Length > 0) Guid FileId = Guid.NewGuid();
string filePath = Path.Combine(workPath, Path.GetFileName(file.FileName));
if (!Directory.Exists(workPath))
{ {
Guid FileId = Guid.NewGuid(); Directory.CreateDirectory(workPath);
string filePath = Path.Combine(workPath, Path.GetFileName(formFile.FileName));
if (!Directory.Exists(workPath))
{
Directory.CreateDirectory(workPath);
}
using (var stream = System.IO.File.Create(filePath))
{
await formFile.CopyToAsync(stream);
Dictionary<string, object> UploadedFile = new Dictionary<string, object>();
UploadedFile.Add("id", FileId.ToString());
UploadedFile.Add("originalname", Path.GetFileName(formFile.FileName));
UploadedFile.Add("fullpath", filePath);
UploadedFiles.Add(UploadedFile);
}
} }
}
// get override platform if specified Dictionary<string, object> UploadedFile = new Dictionary<string, object>();
IGDB.Models.Platform? OverridePlatform = null;
if (OverridePlatformId != null)
{
OverridePlatform = Platforms.GetPlatform((long)OverridePlatformId);
}
// Process uploaded files using (var stream = System.IO.File.Create(filePath))
foreach (Dictionary<string, object> UploadedFile in UploadedFiles) {
{ await file.CopyToAsync(stream);
UploadedFile.Add("id", FileId.ToString());
UploadedFile.Add("originalname", Path.GetFileName(file.FileName));
UploadedFile.Add("fullpath", filePath);
}
// get override platform if specified
IGDB.Models.Platform? OverridePlatform = null;
if (OverridePlatformId != null)
{
OverridePlatform = Platforms.GetPlatform((long)OverridePlatformId);
}
// Process uploaded file
Classes.ImportGame uploadImport = new ImportGame(); Classes.ImportGame uploadImport = new ImportGame();
uploadImport.ImportGameFile((string)UploadedFile["fullpath"], OverridePlatform); Dictionary<string, object> RetVal = uploadImport.ImportGameFile((string)UploadedFile["fullpath"], OverridePlatform);
switch (RetVal["type"])
{
case "rom":
if (RetVal["status"] == "imported")
{
IGDB.Models.Game? game = (IGDB.Models.Game)RetVal["game"];
if (game.Id == null)
{
RetVal["game"] = Games.GetGame(0, false, false, false);
}
}
break;
}
if (Directory.Exists(workPath))
{
Directory.Delete(workPath, true);
}
return Ok(RetVal);
} }
if (Directory.Exists(workPath)) return Ok();
{
Directory.Delete(workPath, true);
}
return Ok(new { count = files.Count, size });
} }
} }
} }

View File

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

View File

@@ -8,6 +8,8 @@ using gaseous_server.Classes;
using gaseous_signature_parser.models.RomSignatureObject; using gaseous_signature_parser.models.RomSignatureObject;
using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc;
using Asp.Versioning;
using gaseous_server.Models;
// For more information on enabling MVC for empty projects, visit https://go.microsoft.com/fwlink/?LinkID=397860 // For more information on enabling MVC for empty projects, visit https://go.microsoft.com/fwlink/?LinkID=397860
@@ -53,11 +55,34 @@ namespace gaseous_server.Controllers
{ {
SignatureManagement signatureManagement = new SignatureManagement(); SignatureManagement signatureManagement = new SignatureManagement();
return signatureManagement.GetByTosecName(TosecName); return signatureManagement.GetByTosecName(TosecName);
} else }
else
{ {
return null; return null;
} }
} }
[MapToApiVersion("1.0")]
[MapToApiVersion("1.1")]
[HttpGet]
[ProducesResponseType(StatusCodes.Status200OK)]
public List<Signatures_Sources> GetSignatureSources()
{
SignatureManagement signatureManagement = new SignatureManagement();
return signatureManagement.GetSources();
}
[MapToApiVersion("1.0")]
[MapToApiVersion("1.1")]
[HttpDelete]
[ProducesResponseType(StatusCodes.Status200OK)]
public IActionResult DeleteSignatureSource(int Id)
{
SignatureManagement signatureManagement = new SignatureManagement();
signatureManagement.DeleteSource(Id);
return Ok();
}
} }
} }

View File

@@ -12,6 +12,8 @@ using gaseous_server.Classes.Metadata;
using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Razor.Hosting; using Microsoft.AspNetCore.Razor.Hosting;
using RestEase;
using Asp.Versioning;
namespace gaseous_server.Controllers namespace gaseous_server.Controllers
{ {
@@ -36,7 +38,10 @@ namespace gaseous_server.Controllers
List<SystemInfo.PathItem> Disks = new List<SystemInfo.PathItem>(); List<SystemInfo.PathItem> Disks = new List<SystemInfo.PathItem>();
foreach (GameLibrary.LibraryItem libraryItem in GameLibrary.GetLibraries) foreach (GameLibrary.LibraryItem libraryItem in GameLibrary.GetLibraries)
{ {
Disks.Add(GetDisk(libraryItem.Path)); SystemInfo.PathItem pathItem = GetDisk(libraryItem.Path);
pathItem.Name = libraryItem.Name;
Disks.Add(pathItem);
} }
ReturnValue.Paths = Disks; ReturnValue.Paths = Disks;
@@ -46,7 +51,7 @@ namespace gaseous_server.Controllers
ReturnValue.DatabaseSize = (long)(System.Decimal)dbResponse.Rows[0][1]; ReturnValue.DatabaseSize = (long)(System.Decimal)dbResponse.Rows[0][1];
// platform statistics // platform statistics
sql = "SELECT Platform.`name`, grc.Count, grs.Size FROM Platform INNER JOIN (SELECT Platform.`name` AS `Name`, SUM(grs.Size) AS Size FROM Platform JOIN Games_Roms AS grs ON (grs.PlatformId = Platform.Id) GROUP BY Platform.`name`) grs ON (grs.`Name` = Platform.`name`) INNER JOIN (SELECT Platform.`name` AS `Name`, COUNT(grc.Size) AS Count FROM Platform JOIN Games_Roms AS grc ON (grc.PlatformId = Platform.Id) GROUP BY Platform.`name`) grc ON (grc.`Name` = Platform.`name`) ORDER BY Platform.`name`;"; sql = "SELECT Platform.`name`, grc.Count, grs.Size FROM Platform INNER JOIN (SELECT Platform.`name` AS `Name`, SUM(grs.Size) AS Size FROM Platform JOIN view_Games_Roms AS grs ON (grs.PlatformId = Platform.Id) GROUP BY Platform.`name`) grs ON (grs.`Name` = Platform.`name`) INNER JOIN (SELECT Platform.`name` AS `Name`, COUNT(grc.Size) AS Count FROM Platform JOIN view_Games_Roms AS grc ON (grc.PlatformId = Platform.Id) GROUP BY Platform.`name`) grc ON (grc.`Name` = Platform.`name`) ORDER BY Platform.`name`;";
dbResponse = db.ExecuteCMD(sql); dbResponse = db.ExecuteCMD(sql);
ReturnValue.PlatformStatistics = new List<SystemInfo.PlatformStatisticsItem>(); ReturnValue.PlatformStatistics = new List<SystemInfo.PlatformStatisticsItem>();
foreach (DataRow dr in dbResponse.Rows) foreach (DataRow dr in dbResponse.Rows)
@@ -68,7 +73,8 @@ namespace gaseous_server.Controllers
[HttpGet] [HttpGet]
[Route("Version")] [Route("Version")]
[ProducesResponseType(StatusCodes.Status200OK)] [ProducesResponseType(StatusCodes.Status200OK)]
public Version GetSystemVersion() { public Version GetSystemVersion()
{
return Assembly.GetExecutingAssembly().GetName().Version; return Assembly.GetExecutingAssembly().GetName().Version;
} }
@@ -78,34 +84,39 @@ namespace gaseous_server.Controllers
[Route("VersionFile")] [Route("VersionFile")]
[AllowAnonymous] [AllowAnonymous]
[ProducesResponseType(StatusCodes.Status200OK)] [ProducesResponseType(StatusCodes.Status200OK)]
public FileContentResult GetSystemVersionAsFile() { public FileContentResult GetSystemVersionAsFile()
{
Database db = new Database(Database.databaseType.MySql, Config.DatabaseConfiguration.ConnectionString); Database db = new Database(Database.databaseType.MySql, Config.DatabaseConfiguration.ConnectionString);
// get age ratings dictionary // get age ratings dictionary
Dictionary<int, string> ClassificationBoardsStrings = new Dictionary<int, string>(); Dictionary<int, string> ClassificationBoardsStrings = new Dictionary<int, string>();
foreach(IGDB.Models.AgeRatingCategory ageRatingCategory in Enum.GetValues(typeof(IGDB.Models.AgeRatingCategory)) ) foreach (IGDB.Models.AgeRatingCategory ageRatingCategory in Enum.GetValues(typeof(IGDB.Models.AgeRatingCategory)))
{ {
ClassificationBoardsStrings.Add((int)ageRatingCategory, ageRatingCategory.ToString()); ClassificationBoardsStrings.Add((int)ageRatingCategory, ageRatingCategory.ToString());
} }
Dictionary<int, string> AgeRatingsStrings = new Dictionary<int, string>(); Dictionary<int, string> AgeRatingsStrings = new Dictionary<int, string>();
foreach(IGDB.Models.AgeRatingTitle ageRatingTitle in Enum.GetValues(typeof(IGDB.Models.AgeRatingTitle)) ) foreach (IGDB.Models.AgeRatingTitle ageRatingTitle in Enum.GetValues(typeof(IGDB.Models.AgeRatingTitle)))
{ {
AgeRatingsStrings.Add((int)ageRatingTitle, ageRatingTitle.ToString()); AgeRatingsStrings.Add((int)ageRatingTitle, ageRatingTitle.ToString());
} }
string ver = "var AppVersion = \"" + Assembly.GetExecutingAssembly().GetName().Version.ToString() + "\";" + Environment.NewLine + string ver = "var AppVersion = \"" + Assembly.GetExecutingAssembly().GetName().Version.ToString() + "\";" + Environment.NewLine +
"var DBSchemaVersion = \"" + db.GetDatabaseSchemaVersion() + "\";" + Environment.NewLine + "var DBSchemaVersion = \"" + db.GetDatabaseSchemaVersion() + "\";" + Environment.NewLine +
"var FirstRunStatus = " + Config.ReadSetting("FirstRunStatus", "0") + ";" + Environment.NewLine + "var FirstRunStatus = \"" + Config.ReadSetting<string>("FirstRunStatus", "0") + "\";" + Environment.NewLine +
"var AgeRatingBoardsStrings = " + JsonSerializer.Serialize(ClassificationBoardsStrings, new JsonSerializerOptions{ "var AgeRatingBoardsStrings = " + JsonSerializer.Serialize(ClassificationBoardsStrings, new JsonSerializerOptions
{
WriteIndented = true WriteIndented = true
}) + ";" + Environment.NewLine + }) + ";" + Environment.NewLine +
"var AgeRatingStrings = " + JsonSerializer.Serialize(AgeRatingsStrings, new JsonSerializerOptions{ "var AgeRatingStrings = " + JsonSerializer.Serialize(AgeRatingsStrings, new JsonSerializerOptions
{
WriteIndented = true WriteIndented = true
}) + ";" + Environment.NewLine + }) + ";" + Environment.NewLine +
"var AgeRatingGroups = " + JsonSerializer.Serialize(AgeGroups.AgeGroupingsFlat, new JsonSerializerOptions{ "var AgeRatingGroups = " + JsonSerializer.Serialize(AgeGroups.AgeGroupingsFlat, new JsonSerializerOptions
{
WriteIndented = true WriteIndented = true
}) + ";"; }) + ";" + Environment.NewLine +
"var emulatorDebugMode = " + Config.ReadSetting<string>("emulatorDebugMode", false.ToString()).ToLower() + ";";
byte[] bytes = Encoding.UTF8.GetBytes(ver); byte[] bytes = Encoding.UTF8.GetBytes(ver);
return File(bytes, "text/javascript"); return File(bytes, "text/javascript");
} }
@@ -113,19 +124,23 @@ namespace gaseous_server.Controllers
[MapToApiVersion("1.0")] [MapToApiVersion("1.0")]
[MapToApiVersion("1.1")] [MapToApiVersion("1.1")]
[HttpGet] [HttpGet]
[Route("Settings/BackgroundTasks/Intervals")] [Route("Settings/BackgroundTasks/Configuration")]
[Authorize(Roles = "Admin")] [Authorize(Roles = "Admin")]
[ProducesResponseType(StatusCodes.Status200OK)] [ProducesResponseType(StatusCodes.Status200OK)]
public ActionResult GetBackgroundTasks() public ActionResult GetBackgroundTasks()
{ {
Dictionary<string, BackgroundTaskItem> Intervals = new Dictionary<string, BackgroundTaskItem>(); Dictionary<string, BackgroundTaskItem> Intervals = new Dictionary<string, BackgroundTaskItem>();
Intervals.Add(ProcessQueue.QueueItemType.SignatureIngestor.ToString(), new BackgroundTaskItem(ProcessQueue.QueueItemType.SignatureIngestor)); foreach (ProcessQueue.QueueItemType itemType in Enum.GetValues(typeof(ProcessQueue.QueueItemType)))
Intervals.Add(ProcessQueue.QueueItemType.TitleIngestor.ToString(), new BackgroundTaskItem(ProcessQueue.QueueItemType.TitleIngestor)); {
Intervals.Add(ProcessQueue.QueueItemType.MetadataRefresh.ToString(), new BackgroundTaskItem(ProcessQueue.QueueItemType.MetadataRefresh)); BackgroundTaskItem taskItem = new BackgroundTaskItem(itemType);
Intervals.Add(ProcessQueue.QueueItemType.OrganiseLibrary.ToString(), new BackgroundTaskItem(ProcessQueue.QueueItemType.OrganiseLibrary)); if (taskItem.UserManageable == true)
Intervals.Add(ProcessQueue.QueueItemType.LibraryScan.ToString(), new BackgroundTaskItem(ProcessQueue.QueueItemType.LibraryScan)); {
Intervals.Add(ProcessQueue.QueueItemType.Rematcher.ToString(), new BackgroundTaskItem(ProcessQueue.QueueItemType.Rematcher)); if (!Intervals.ContainsKey(itemType.ToString()))
Intervals.Add(ProcessQueue.QueueItemType.Maintainer.ToString(), new BackgroundTaskItem(ProcessQueue.QueueItemType.Maintainer)); {
Intervals.Add(itemType.ToString(), taskItem);
}
}
}
return Ok(Intervals); return Ok(Intervals);
} }
@@ -133,45 +148,102 @@ namespace gaseous_server.Controllers
[MapToApiVersion("1.0")] [MapToApiVersion("1.0")]
[MapToApiVersion("1.1")] [MapToApiVersion("1.1")]
[HttpPost] [HttpPost]
[Route("Settings/BackgroundTasks/Intervals")] [Route("Settings/BackgroundTasks/Configuration")]
[Authorize(Roles = "Admin")] [Authorize(Roles = "Admin")]
[ProducesResponseType(StatusCodes.Status200OK)] [ProducesResponseType(StatusCodes.Status200OK)]
public ActionResult SetBackgroundTasks(Dictionary<string, int> Intervals) public ActionResult SetBackgroundTasks([FromBody] List<BackgroundTaskSettingsItem> model)
{ {
foreach (KeyValuePair<string, int> Interval in Intervals) foreach (BackgroundTaskSettingsItem TaskConfiguration in model)
{ {
if (Enum.IsDefined(typeof(ProcessQueue.QueueItemType), Interval.Key)) if (Enum.IsDefined(typeof(ProcessQueue.QueueItemType), TaskConfiguration.Task))
{ {
try try
{ {
BackgroundTaskItem taskItem = new BackgroundTaskItem( BackgroundTaskItem taskItem = new BackgroundTaskItem(
(ProcessQueue.QueueItemType)Enum.Parse(typeof(ProcessQueue.QueueItemType), Interval.Key) (ProcessQueue.QueueItemType)Enum.Parse(typeof(ProcessQueue.QueueItemType), TaskConfiguration.Task)
); );
if (Interval.Value >= taskItem.MinimumAllowedValue) if (taskItem.UserManageable == true)
{ {
Logging.Log(Logging.LogType.Information, "Update Background Task", "Updating task " + Interval.Key + " with new interval " + Interval.Value); // update task enabled
Logging.Log(Logging.LogType.Information, "Update Background Task", "Updating task " + TaskConfiguration.Task + " with enabled value " + TaskConfiguration.Enabled.ToString());
Config.SetSetting("Interval_" + Interval.Key, Interval.Value.ToString());
Config.SetSetting<string>("Enabled_" + TaskConfiguration.Task, TaskConfiguration.Enabled.ToString());
// update existing process // update existing process
foreach (ProcessQueue.QueueItem item in ProcessQueue.QueueItems) foreach (ProcessQueue.QueueItem item in ProcessQueue.QueueItems)
{ {
if (item.ItemType.ToString().ToLower() == Interval.Key.ToLower()) if (item.ItemType.ToString().ToLower() == TaskConfiguration.Task.ToLower())
{ {
item.Interval = Interval.Value; item.Enabled(Boolean.Parse(TaskConfiguration.Enabled.ToString()));
} }
} }
// update task interval
if (TaskConfiguration.Interval >= taskItem.MinimumAllowedInterval)
{
Logging.Log(Logging.LogType.Information, "Update Background Task", "Updating task " + TaskConfiguration.Task + " with new interval " + TaskConfiguration.Interval);
Config.SetSetting<string>("Interval_" + TaskConfiguration.Task, TaskConfiguration.Interval.ToString());
// update existing process
foreach (ProcessQueue.QueueItem item in ProcessQueue.QueueItems)
{
if (item.ItemType.ToString().ToLower() == TaskConfiguration.Task.ToLower())
{
item.Interval = TaskConfiguration.Interval;
}
}
}
else
{
Logging.Log(Logging.LogType.Warning, "Update Background Task", "Interval " + TaskConfiguration.Interval.ToString() + " for task " + TaskConfiguration.Task + " is below the minimum allowed value of " + taskItem.MinimumAllowedInterval + ". Skipping.");
}
// update task weekdays
Logging.Log(Logging.LogType.Information, "Update Background Task", "Updating task " + TaskConfiguration.Task + " with new weekdays " + String.Join(", ", TaskConfiguration.AllowedDays));
Config.SetSetting<string>("AllowedDays_" + TaskConfiguration.Task, Newtonsoft.Json.JsonConvert.SerializeObject(TaskConfiguration.AllowedDays));
// update existing process
foreach (ProcessQueue.QueueItem item in ProcessQueue.QueueItems)
{
if (item.ItemType.ToString().ToLower() == TaskConfiguration.Task.ToLower())
{
item.AllowedDays = TaskConfiguration.AllowedDays;
}
}
// update task hours
Logging.Log(Logging.LogType.Information, "Update Background Task", "Updating task " + TaskConfiguration.Task + " with new hours " + TaskConfiguration.AllowedStartHours + ":" + TaskConfiguration.AllowedStartMinutes.ToString("00") + " to " + TaskConfiguration.AllowedEndHours + ":" + TaskConfiguration.AllowedEndMinutes.ToString("00"));
Config.SetSetting<string>("AllowedStartHours_" + TaskConfiguration.Task, TaskConfiguration.AllowedStartHours.ToString());
Config.SetSetting<string>("AllowedStartMinutes_" + TaskConfiguration.Task, TaskConfiguration.AllowedStartMinutes.ToString());
Config.SetSetting<string>("AllowedEndHours_" + TaskConfiguration.Task, TaskConfiguration.AllowedEndHours.ToString());
Config.SetSetting<string>("AllowedEndMinutes_" + TaskConfiguration.Task, TaskConfiguration.AllowedEndMinutes.ToString());
// update existing process
foreach (ProcessQueue.QueueItem item in ProcessQueue.QueueItems)
{
if (item.ItemType.ToString().ToLower() == TaskConfiguration.Task.ToLower())
{
item.AllowedStartHours = TaskConfiguration.AllowedStartHours;
item.AllowedStartMinutes = TaskConfiguration.AllowedStartMinutes;
item.AllowedEndHours = TaskConfiguration.AllowedEndHours;
item.AllowedEndMinutes = TaskConfiguration.AllowedEndMinutes;
}
}
} }
else else
{ {
Logging.Log(Logging.LogType.Warning, "Update Background Task", "Interval " + Interval.Value + " for task " + Interval.Key + " is below the minimum allowed value of " + taskItem.MinimumAllowedValue + ". Skipping."); Logging.Log(Logging.LogType.Warning, "Update Background Task", "Unable to update non-user manageable task " + TaskConfiguration.Task + ". Skipping.");
} }
} }
catch catch
{ {
// task name not defined // task name not defined
Logging.Log(Logging.LogType.Warning, "Update Background Task", "Task " + Interval.Key + " is not user definable. Skipping."); Logging.Log(Logging.LogType.Warning, "Update Background Task", "Task " + TaskConfiguration.Task + " is not user definable. Skipping.");
} }
} }
} }
@@ -187,9 +259,18 @@ namespace gaseous_server.Controllers
[ProducesResponseType(StatusCodes.Status200OK)] [ProducesResponseType(StatusCodes.Status200OK)]
public ActionResult GetSystemSettings() public ActionResult GetSystemSettings()
{ {
SystemSettingsModel systemSettingsModel = new SystemSettingsModel{ SystemSettingsModel systemSettingsModel = new SystemSettingsModel
{
AlwaysLogToDisk = Config.LoggingConfiguration.AlwaysLogToDisk, AlwaysLogToDisk = Config.LoggingConfiguration.AlwaysLogToDisk,
MinimumLogRetentionPeriod = Config.LoggingConfiguration.LogRetention MinimumLogRetentionPeriod = Config.LoggingConfiguration.LogRetention,
EmulatorDebugMode = Boolean.Parse(Config.ReadSetting<string>("emulatorDebugMode", false.ToString())),
SignatureSource = new SystemSettingsModel.SignatureSourceItem()
{
Source = Config.MetadataConfiguration.SignatureSource,
HasheousHost = Config.MetadataConfiguration.HasheousHost,
HasheousSubmitFixes = (bool)Config.MetadataConfiguration.HasheousSubmitFixes,
HasheousAPIKey = Config.MetadataConfiguration.HasheousAPIKey
}
}; };
return Ok(systemSettingsModel); return Ok(systemSettingsModel);
@@ -207,6 +288,11 @@ namespace gaseous_server.Controllers
{ {
Config.LoggingConfiguration.AlwaysLogToDisk = model.AlwaysLogToDisk; Config.LoggingConfiguration.AlwaysLogToDisk = model.AlwaysLogToDisk;
Config.LoggingConfiguration.LogRetention = model.MinimumLogRetentionPeriod; Config.LoggingConfiguration.LogRetention = model.MinimumLogRetentionPeriod;
Config.SetSetting<string>("emulatorDebugMode", model.EmulatorDebugMode.ToString());
Config.MetadataConfiguration.SignatureSource = model.SignatureSource.Source;
Config.MetadataConfiguration.HasheousHost = model.SignatureSource.HasheousHost;
Config.MetadataConfiguration.HasheousAPIKey = model.SignatureSource.HasheousAPIKey;
Config.MetadataConfiguration.HasheousSubmitFixes = model.SignatureSource.HasheousSubmitFixes;
Config.UpdateConfig(); Config.UpdateConfig();
} }
@@ -215,7 +301,8 @@ namespace gaseous_server.Controllers
private SystemInfo.PathItem GetDisk(string Path) private SystemInfo.PathItem GetDisk(string Path)
{ {
SystemInfo.PathItem pathItem = new SystemInfo.PathItem { SystemInfo.PathItem pathItem = new SystemInfo.PathItem
{
LibraryPath = Path, LibraryPath = Path,
SpaceUsed = Common.DirSize(new DirectoryInfo(Path)), SpaceUsed = Common.DirSize(new DirectoryInfo(Path)),
SpaceAvailable = new DriveInfo(Path).AvailableFreeSpace, SpaceAvailable = new DriveInfo(Path).AvailableFreeSpace,
@@ -227,11 +314,12 @@ namespace gaseous_server.Controllers
public class SystemInfo public class SystemInfo
{ {
public Version ApplicationVersion { public Version ApplicationVersion
{
get get
{ {
return Assembly.GetExecutingAssembly().GetName().Version; return Assembly.GetExecutingAssembly().GetName().Version;
} }
} }
public List<PathItem>? Paths { get; set; } public List<PathItem>? Paths { get; set; }
public long DatabaseSize { get; set; } public long DatabaseSize { get; set; }
@@ -239,6 +327,7 @@ namespace gaseous_server.Controllers
public class PathItem public class PathItem
{ {
public string Name { get; set; }
public string LibraryPath { get; set; } public string LibraryPath { get; set; }
public long SpaceUsed { get; set; } public long SpaceUsed { get; set; }
public long SpaceAvailable { get; set; } public long SpaceAvailable { get; set; }
@@ -256,66 +345,403 @@ namespace gaseous_server.Controllers
public class BackgroundTaskItem public class BackgroundTaskItem
{ {
public BackgroundTaskItem()
{
}
public BackgroundTaskItem(ProcessQueue.QueueItemType TaskName) public BackgroundTaskItem(ProcessQueue.QueueItemType TaskName)
{ {
this.Task = TaskName.ToString(); this.Task = TaskName.ToString();
this.TaskEnum = TaskName;
switch (TaskName) switch (TaskName)
{ {
case ProcessQueue.QueueItemType.SignatureIngestor: case ProcessQueue.QueueItemType.SignatureIngestor:
this._UserManageable = true;
this.DefaultInterval = 60; this.DefaultInterval = 60;
this.MinimumAllowedValue = 20; this.MinimumAllowedInterval = 20;
this.DefaultAllowedDays = new List<DayOfWeek>{
DayOfWeek.Sunday,
DayOfWeek.Monday,
DayOfWeek.Tuesday,
DayOfWeek.Wednesday,
DayOfWeek.Thursday,
DayOfWeek.Friday,
DayOfWeek.Saturday
};
this.DefaultAllowedStartHours = 0;
this.DefaultAllowedStartMinutes = 0;
this.DefaultAllowedEndHours = 23;
this.DefaultAllowedEndMinutes = 59;
break; break;
case ProcessQueue.QueueItemType.TitleIngestor: case ProcessQueue.QueueItemType.TitleIngestor:
this._UserManageable = true;
this.DefaultInterval = 1; this.DefaultInterval = 1;
this.MinimumAllowedValue = 1; this.MinimumAllowedInterval = 1;
this.DefaultAllowedDays = new List<DayOfWeek>{
DayOfWeek.Sunday,
DayOfWeek.Monday,
DayOfWeek.Tuesday,
DayOfWeek.Wednesday,
DayOfWeek.Thursday,
DayOfWeek.Friday,
DayOfWeek.Saturday
};
this.DefaultAllowedStartHours = 0;
this.DefaultAllowedStartMinutes = 0;
this.DefaultAllowedEndHours = 23;
this.DefaultAllowedEndMinutes = 59;
this._Blocks = new List<ProcessQueue.QueueItemType>{
ProcessQueue.QueueItemType.OrganiseLibrary,
ProcessQueue.QueueItemType.LibraryScan,
ProcessQueue.QueueItemType.LibraryScanWorker
};
break; break;
case ProcessQueue.QueueItemType.MetadataRefresh: case ProcessQueue.QueueItemType.MetadataRefresh:
this._UserManageable = true;
this.DefaultInterval = 360; this.DefaultInterval = 360;
this.MinimumAllowedValue = 360; this.MinimumAllowedInterval = 360;
this.DefaultAllowedDays = new List<DayOfWeek>{
DayOfWeek.Sunday,
DayOfWeek.Monday,
DayOfWeek.Tuesday,
DayOfWeek.Wednesday,
DayOfWeek.Thursday,
DayOfWeek.Friday,
DayOfWeek.Saturday
};
this.DefaultAllowedStartHours = 0;
this.DefaultAllowedStartMinutes = 0;
this.DefaultAllowedEndHours = 23;
this.DefaultAllowedEndMinutes = 59;
break; break;
case ProcessQueue.QueueItemType.OrganiseLibrary: case ProcessQueue.QueueItemType.OrganiseLibrary:
this._UserManageable = true;
this.DefaultInterval = 1440; this.DefaultInterval = 1440;
this.MinimumAllowedValue = 120; this.MinimumAllowedInterval = 120;
this.DefaultAllowedDays = new List<DayOfWeek>{
DayOfWeek.Sunday,
DayOfWeek.Monday,
DayOfWeek.Tuesday,
DayOfWeek.Wednesday,
DayOfWeek.Thursday,
DayOfWeek.Friday,
DayOfWeek.Saturday
};
this.DefaultAllowedStartHours = 0;
this.DefaultAllowedStartMinutes = 0;
this.DefaultAllowedEndHours = 23;
this.DefaultAllowedEndMinutes = 59;
this._Blocks = new List<ProcessQueue.QueueItemType>{
ProcessQueue.QueueItemType.LibraryScan,
ProcessQueue.QueueItemType.LibraryScanWorker,
ProcessQueue.QueueItemType.TitleIngestor,
ProcessQueue.QueueItemType.Rematcher
};
break; break;
case ProcessQueue.QueueItemType.LibraryScan: case ProcessQueue.QueueItemType.LibraryScan:
this._UserManageable = true;
this.DefaultInterval = 1440; this.DefaultInterval = 1440;
this.MinimumAllowedValue = 120; this.MinimumAllowedInterval = 120;
this.DefaultAllowedDays = new List<DayOfWeek>{
DayOfWeek.Sunday,
DayOfWeek.Monday,
DayOfWeek.Tuesday,
DayOfWeek.Wednesday,
DayOfWeek.Thursday,
DayOfWeek.Friday,
DayOfWeek.Saturday
};
this.DefaultAllowedStartHours = 0;
this.DefaultAllowedStartMinutes = 0;
this.DefaultAllowedEndHours = 23;
this.DefaultAllowedEndMinutes = 59;
this._Blocks = new List<ProcessQueue.QueueItemType>{
ProcessQueue.QueueItemType.OrganiseLibrary,
ProcessQueue.QueueItemType.Rematcher
};
break; break;
case ProcessQueue.QueueItemType.Rematcher: case ProcessQueue.QueueItemType.Rematcher:
this._UserManageable = true;
this.DefaultInterval = 1440; this.DefaultInterval = 1440;
this.MinimumAllowedValue = 360; this.MinimumAllowedInterval = 360;
this.DefaultAllowedDays = new List<DayOfWeek>{
DayOfWeek.Sunday,
DayOfWeek.Monday,
DayOfWeek.Tuesday,
DayOfWeek.Wednesday,
DayOfWeek.Thursday,
DayOfWeek.Friday,
DayOfWeek.Saturday
};
this.DefaultAllowedStartHours = 0;
this.DefaultAllowedStartMinutes = 0;
this.DefaultAllowedEndHours = 23;
this.DefaultAllowedEndMinutes = 59;
this._Blocks = new List<ProcessQueue.QueueItemType>{
ProcessQueue.QueueItemType.OrganiseLibrary,
ProcessQueue.QueueItemType.LibraryScan,
ProcessQueue.QueueItemType.LibraryScanWorker
};
break; break;
case ProcessQueue.QueueItemType.Maintainer: case ProcessQueue.QueueItemType.DailyMaintainer:
this._UserManageable = true;
this.DefaultInterval = 1440;
this.MinimumAllowedInterval = 1440;
this.DefaultAllowedDays = new List<DayOfWeek>{
DayOfWeek.Sunday,
DayOfWeek.Monday,
DayOfWeek.Tuesday,
DayOfWeek.Wednesday,
DayOfWeek.Thursday,
DayOfWeek.Friday,
DayOfWeek.Saturday
};
this.DefaultAllowedStartHours = 1;
this.DefaultAllowedStartMinutes = 0;
this.DefaultAllowedEndHours = 5;
this.DefaultAllowedEndMinutes = 59;
this._Blocks = new List<ProcessQueue.QueueItemType>{
ProcessQueue.QueueItemType.All
};
break;
case ProcessQueue.QueueItemType.WeeklyMaintainer:
this._UserManageable = true;
this.DefaultInterval = 10080; this.DefaultInterval = 10080;
this.MinimumAllowedValue = 10080; this.MinimumAllowedInterval = 10080;
this.DefaultAllowedDays = new List<DayOfWeek>{
DayOfWeek.Monday
};
this.DefaultAllowedStartHours = 1;
this.DefaultAllowedStartMinutes = 0;
this.DefaultAllowedEndHours = 5;
this.DefaultAllowedEndMinutes = 59;
this._Blocks = new List<ProcessQueue.QueueItemType>{
ProcessQueue.QueueItemType.All
};
break;
case ProcessQueue.QueueItemType.BackgroundDatabaseUpgrade:
this._UserManageable = false;
this.DefaultInterval = 1;
this.MinimumAllowedInterval = 1;
this.DefaultAllowedDays = new List<DayOfWeek>{
DayOfWeek.Sunday,
DayOfWeek.Monday,
DayOfWeek.Tuesday,
DayOfWeek.Wednesday,
DayOfWeek.Thursday,
DayOfWeek.Friday,
DayOfWeek.Saturday
};
this.DefaultAllowedStartHours = 0;
this.DefaultAllowedStartMinutes = 0;
this.DefaultAllowedEndHours = 23;
this.DefaultAllowedEndMinutes = 59;
this._Blocks.Add(ProcessQueue.QueueItemType.All);
break;
case ProcessQueue.QueueItemType.TempCleanup:
this._UserManageable = true;
this.DefaultInterval = 1;
this.MinimumAllowedInterval = 1;
this.DefaultAllowedDays = new List<DayOfWeek>{
DayOfWeek.Sunday,
DayOfWeek.Monday,
DayOfWeek.Tuesday,
DayOfWeek.Wednesday,
DayOfWeek.Thursday,
DayOfWeek.Friday,
DayOfWeek.Saturday
};
this.DefaultAllowedStartHours = 0;
this.DefaultAllowedStartMinutes = 0;
this.DefaultAllowedEndHours = 23;
this.DefaultAllowedEndMinutes = 59;
break; break;
default: default:
throw new Exception("Invalid task"); this._UserManageable = false;
this.DefaultAllowedDays = new List<DayOfWeek>{
DayOfWeek.Sunday,
DayOfWeek.Monday,
DayOfWeek.Tuesday,
DayOfWeek.Wednesday,
DayOfWeek.Thursday,
DayOfWeek.Friday,
DayOfWeek.Saturday
};
this.DefaultAllowedStartHours = 0;
this.DefaultAllowedStartMinutes = 0;
this.DefaultAllowedEndHours = 23;
this.DefaultAllowedEndMinutes = 59;
break;
} }
} }
public string Task { get; set; } public string Task { get; set; }
public int Interval { public ProcessQueue.QueueItemType TaskEnum { get; set; }
public bool Enabled
{
get 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 DefaultInterval { get; set; }
public int MinimumAllowedValue { get; set; } public int MinimumAllowedInterval { get; set; }
public List<DayOfWeek> AllowedDays
{
get
{
string jsonDefaultAllowedDays = Newtonsoft.Json.JsonConvert.SerializeObject(DefaultAllowedDays);
return Newtonsoft.Json.JsonConvert.DeserializeObject<List<DayOfWeek>>(Config.ReadSetting<string>("AllowedDays_" + Task, jsonDefaultAllowedDays));
}
}
public int AllowedStartHours
{
get
{
return int.Parse(Config.ReadSetting<string>("AllowedStartHours_" + Task, DefaultAllowedStartHours.ToString()));
}
}
public int AllowedStartMinutes
{
get
{
return int.Parse(Config.ReadSetting<string>("AllowedStartMinutes_" + Task, DefaultAllowedStartMinutes.ToString()));
}
}
public int AllowedEndHours
{
get
{
return int.Parse(Config.ReadSetting<string>("AllowedEndHours_" + Task, DefaultAllowedEndHours.ToString()));
}
}
public int AllowedEndMinutes
{
get
{
return int.Parse(Config.ReadSetting<string>("AllowedEndMinutes_" + Task, DefaultAllowedEndMinutes.ToString()));
}
}
public List<DayOfWeek> DefaultAllowedDays { get; set; }
public int DefaultAllowedStartHours { get; set; }
public int DefaultAllowedStartMinutes { get; set; }
public int DefaultAllowedEndHours { get; set; }
public int DefaultAllowedEndMinutes { get; set; }
private List<ProcessQueue.QueueItemType> _Blocks = new List<ProcessQueue.QueueItemType>();
public List<ProcessQueue.QueueItemType> Blocks
{
get
{
if (_Blocks.Contains(ProcessQueue.QueueItemType.All))
{
List<ProcessQueue.QueueItemType> blockList = new List<ProcessQueue.QueueItemType>();
List<ProcessQueue.QueueItemType> skipBlockItems = new List<ProcessQueue.QueueItemType>{
ProcessQueue.QueueItemType.All,
ProcessQueue.QueueItemType.NotConfigured,
this.TaskEnum
};
foreach (ProcessQueue.QueueItemType blockType in Enum.GetValues(typeof(ProcessQueue.QueueItemType)))
{
if (!skipBlockItems.Contains(blockType))
{
blockList.Add(blockType);
}
}
return blockList;
}
else
{
return _Blocks;
}
}
}
public List<ProcessQueue.QueueItemType> BlockedBy
{
get
{
List<ProcessQueue.QueueItemType> blockedBy = new List<ProcessQueue.QueueItemType>();
List<BackgroundTaskItem> backgroundTaskItems = new List<BackgroundTaskItem>();
foreach (ProcessQueue.QueueItemType blockType in Enum.GetValues(typeof(ProcessQueue.QueueItemType)))
{
if (blockType != this.TaskEnum)
{
BackgroundTaskItem taskItem = new BackgroundTaskItem(blockType);
if (taskItem.Blocks.Contains(this.TaskEnum))
{
if (!blockedBy.Contains(blockType))
{
blockedBy.Add(blockType);
}
}
}
}
return blockedBy;
}
}
}
public class BackgroundTaskSettingsItem
{
public string Task { get; set; }
public bool Enabled { get; set; }
public int Interval { get; set; }
public List<DayOfWeek> AllowedDays { get; set; }
public int AllowedStartHours { get; set; }
public int AllowedStartMinutes { get; set; }
public int AllowedEndHours { get; set; }
public int AllowedEndMinutes { get; set; }
} }
public class SystemSettingsModel public class SystemSettingsModel
{ {
public bool AlwaysLogToDisk { get; set; } public bool AlwaysLogToDisk { get; set; }
public int MinimumLogRetentionPeriod { get; set; } public int MinimumLogRetentionPeriod { get; set; }
public bool EmulatorDebugMode { get; set; }
public SignatureSourceItem SignatureSource { get; set; }
public class SignatureSourceItem
{
public HasheousClient.Models.MetadataModel.SignatureSources Source { get; set; }
public string HasheousHost { get; set; }
public string HasheousAPIKey { get; set; }
public bool HasheousSubmitFixes { get; set; }
}
} }
} }

View File

@@ -0,0 +1,80 @@
using System;
using System.Collections;
using System.Collections.Generic;
using System.Data;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Threading.Tasks;
using gaseous_server.Classes;
using gaseous_server.Classes.Metadata;
using IGDB.Models;
using Microsoft.AspNetCore.Authorization;
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
{
[Route("api/v{version:apiVersion}/[controller]")]
[ApiVersion("1.1")]
[ApiController]
public class FileSystemController : ControllerBase
{
[MapToApiVersion("1.1")]
[HttpGet]
[Authorize(Roles = "Admin")]
[ProducesResponseType(typeof(FileContentResult), StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status404NotFound)]
public ActionResult GetFileSystem(string path, bool showFiles = false)
{
try
{
if (path.Contains(".."))
{
return NotFound();
}
if (Directory.Exists(path))
{
Dictionary<string, List<Dictionary<string, string>>> allFiles = new Dictionary<string, List<Dictionary<string, string>>>();
List<Dictionary<string, string>> directories = new List<Dictionary<string, string>>();
string[] dirs = Directory.GetDirectories(path);
Array.Sort(dirs);
foreach (string dir in dirs)
{
DirectoryInfo directoryInfo = new DirectoryInfo(dir);
directories.Add(new Dictionary<string, string> { { "name", directoryInfo.Name }, { "path", directoryInfo.FullName } });
}
allFiles.Add("directories", directories);
if (showFiles == true)
{
List<Dictionary<string, string>> files = new List<Dictionary<string, string>>();
string[] filePaths = Directory.GetFiles(path);
Array.Sort(filePaths);
foreach (string file in filePaths)
{
FileInfo fileInfo = new FileInfo(file);
files.Add(new Dictionary<string, string> { { "name", fileInfo.Name }, { "path", fileInfo.FullName } });
}
allFiles.Add("files", files);
}
return Ok(allFiles);
}
else
{
return NotFound();
}
}
catch (Exception ex)
{
return NotFound();
}
}
}
}

View File

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

View File

@@ -17,6 +17,8 @@ using Microsoft.AspNetCore.Mvc;
using Microsoft.CodeAnalysis.CSharp.Syntax; using Microsoft.CodeAnalysis.CSharp.Syntax;
using Microsoft.CodeAnalysis.Scripting; using Microsoft.CodeAnalysis.Scripting;
using static gaseous_server.Classes.Metadata.AgeRatings; using static gaseous_server.Classes.Metadata.AgeRatings;
using Asp.Versioning;
using Humanizer;
namespace gaseous_server.Controllers.v1_1 namespace gaseous_server.Controllers.v1_1
{ {
@@ -24,11 +26,11 @@ namespace gaseous_server.Controllers.v1_1
[ApiVersion("1.1")] [ApiVersion("1.1")]
[ApiController] [ApiController]
[Authorize] [Authorize]
public class GamesController: ControllerBase public class GamesController : ControllerBase
{ {
private readonly UserManager<ApplicationUser> _userManager; private readonly UserManager<ApplicationUser> _userManager;
private readonly SignInManager<ApplicationUser> _signInManager; private readonly SignInManager<ApplicationUser> _signInManager;
public GamesController( public GamesController(
UserManager<ApplicationUser> userManager, UserManager<ApplicationUser> userManager,
SignInManager<ApplicationUser> signInManager) SignInManager<ApplicationUser> signInManager)
@@ -104,7 +106,8 @@ namespace gaseous_server.Controllers.v1_1
if (user != null) if (user != null)
{ {
string IncludeUnrated = ""; string IncludeUnrated = "";
if (user.SecurityProfile.AgeRestrictionPolicy.IncludeUnrated == true) { if (user.SecurityProfile.AgeRestrictionPolicy.IncludeUnrated == true)
{
IncludeUnrated = " OR view_Games.AgeGroupId IS NULL"; IncludeUnrated = " OR view_Games.AgeGroupId IS NULL";
} }
@@ -141,11 +144,14 @@ namespace gaseous_server.Controllers.v1_1
public List<string> GameMode { get; set; } public List<string> GameMode { get; set; }
public List<string> PlayerPerspective { get; set; } public List<string> PlayerPerspective { get; set; }
public List<string> Theme { get; set; } public List<string> Theme { get; set; }
public int MinimumReleaseYear { get; set; } = -1;
public int MaximumReleaseYear { get; set; } = -1;
public GameRatingItem GameRating { get; set; } = new GameRatingItem(); public GameRatingItem GameRating { get; set; } = new GameRatingItem();
public GameAgeRatingItem GameAgeRating { get; set; } = new GameAgeRatingItem(); public GameAgeRatingItem GameAgeRating { get; set; } = new GameAgeRatingItem();
public GameSortingItem Sorting { get; set; } = new GameSortingItem(); public GameSortingItem Sorting { get; set; } = new GameSortingItem();
public bool HasSavedGame { get; set; } public bool HasSavedGame { get; set; }
public bool IsFavourite { get; set; }
public class GameRatingItem public class GameRatingItem
{ {
@@ -158,7 +164,7 @@ namespace gaseous_server.Controllers.v1_1
public class GameAgeRatingItem public class GameAgeRatingItem
{ {
public List<AgeGroups.AgeRestrictionGroupings> AgeGroupings { get; set; } = new List<AgeGroups.AgeRestrictionGroupings>{ public List<AgeGroups.AgeRestrictionGroupings> AgeGroupings { get; set; } = new List<AgeGroups.AgeRestrictionGroupings>{
AgeGroups.AgeRestrictionGroupings.Child, AgeGroups.AgeRestrictionGroupings.Child,
AgeGroups.AgeRestrictionGroupings.Teen, AgeGroups.AgeRestrictionGroupings.Teen,
AgeGroups.AgeRestrictionGroupings.Mature, AgeGroups.AgeRestrictionGroupings.Mature,
@@ -181,7 +187,7 @@ namespace gaseous_server.Controllers.v1_1
} }
} }
} }
public static GameReturnPackage GetGames(GameSearchModel model, string userid, int pageNumber = 0, int pageSize = 0) public static GameReturnPackage GetGames(GameSearchModel model, string userid, int pageNumber = 0, int pageSize = 0)
{ {
string whereClause = ""; string whereClause = "";
@@ -210,6 +216,26 @@ namespace gaseous_server.Controllers.v1_1
whereClauses.Add(hasSavesTemp); whereClauses.Add(hasSavesTemp);
} }
if (model.IsFavourite == true)
{
string isFavTemp = "Favourite = 1";
havingClauses.Add(isFavTemp);
}
if (model.MinimumReleaseYear != -1)
{
string releaseTempMinVal = "FirstReleaseDate >= @minreleasedate";
whereParams.Add("minreleasedate", new DateTime(model.MinimumReleaseYear, 1, 1));
havingClauses.Add(releaseTempMinVal);
}
if (model.MaximumReleaseYear != -1)
{
string releaseTempMaxVal = "FirstReleaseDate <= @maxreleasedate";
whereParams.Add("maxreleasedate", new DateTime(model.MaximumReleaseYear, 12, 31, 23, 59, 59));
havingClauses.Add(releaseTempMaxVal);
}
if (model.GameRating != null) if (model.GameRating != null)
{ {
List<string> ratingClauses = new List<string>(); List<string> ratingClauses = new List<string>();
@@ -277,7 +303,7 @@ namespace gaseous_server.Controllers.v1_1
string platformWhereClause = ""; string platformWhereClause = "";
if (model.Platform.Count > 0) if (model.Platform.Count > 0)
{ {
tempVal = " AND Games_Roms.PlatformId IN ("; tempVal = " AND view_Games_Roms.PlatformId IN (";
for (int i = 0; i < model.Platform.Count; i++) for (int i = 0; i < model.Platform.Count; i++)
{ {
if (i > 0) if (i > 0)
@@ -421,7 +447,7 @@ namespace gaseous_server.Controllers.v1_1
string orderByOrder = "ASC"; string orderByOrder = "ASC";
if (model.Sorting != null) if (model.Sorting != null)
{ {
switch(model.Sorting.SortBy) switch (model.Sorting.SortBy)
{ {
case GameSearchModel.GameSortingItem.SortField.NameThe: case GameSearchModel.GameSortingItem.SortField.NameThe:
orderByField = "NameThe"; orderByField = "NameThe";
@@ -452,12 +478,15 @@ namespace gaseous_server.Controllers.v1_1
string orderByClause = "ORDER BY `" + orderByField + "` " + orderByOrder; string orderByClause = "ORDER BY `" + orderByField + "` " + orderByOrder;
Database db = new Database(Database.databaseType.MySql, Config.DatabaseConfiguration.ConnectionString); Database db = new Database(Database.databaseType.MySql, Config.DatabaseConfiguration.ConnectionString);
string sql = @" string sql = @"
SET SESSION sql_mode=(SELECT REPLACE(@@sql_mode,'ONLY_FULL_GROUP_BY',''));
SELECT DISTINCT SELECT DISTINCT
Game.Id, Game.Id,
Game.`Name`, Game.`Name`,
Game.NameThe, Game.NameThe,
Game.Slug,
Game.Summary,
Game.PlatformId, Game.PlatformId,
Game.TotalRating, Game.TotalRating,
Game.TotalRatingCount, Game.TotalRatingCount,
@@ -470,7 +499,11 @@ SELECT DISTINCT
Game.AgeGroupId, Game.AgeGroupId,
Game.RomCount, Game.RomCount,
RomSavedStates.RomSaveCount, RomSavedStates.RomSaveCount,
RomGroupSavedStates.MediaGroupSaveCount RomGroupSavedStates.MediaGroupSaveCount,
CASE
WHEN Favourites.UserId IS NULL THEN 0
ELSE 1
END AS Favourite
FROM FROM
(SELECT DISTINCT (SELECT DISTINCT
Game.*, Game.*,
@@ -478,26 +511,26 @@ FROM
WHEN Game.`Name` LIKE 'The %' THEN CONCAT(TRIM(SUBSTR(Game.`Name` FROM 4)), ', The') WHEN Game.`Name` LIKE 'The %' THEN CONCAT(TRIM(SUBSTR(Game.`Name` FROM 4)), ', The')
ELSE Game.`Name` ELSE Game.`Name`
END AS NameThe, END AS NameThe,
Games_Roms.PlatformId, view_Games_Roms.PlatformId,
AgeGroup.AgeGroupId, AgeGroup.AgeGroupId,
COUNT(Games_Roms.Id) AS RomCount COUNT(view_Games_Roms.Id) AS RomCount
FROM FROM
Game Game
LEFT JOIN AgeGroup ON Game.Id = AgeGroup.GameId LEFT JOIN AgeGroup ON Game.Id = AgeGroup.GameId
LEFT JOIN Games_Roms ON Game.Id = Games_Roms.GameId" + platformWhereClause + @" LEFT JOIN view_Games_Roms ON Game.Id = view_Games_Roms.GameId" + platformWhereClause + @"
LEFT JOIN AlternativeName ON Game.Id = AlternativeName.Game " + nameWhereClause + @" LEFT JOIN AlternativeName ON Game.Id = AlternativeName.Game " + nameWhereClause + @"
GROUP BY Game.Id GROUP BY Game.Id
HAVING RomCount > 0) Game HAVING RomCount > 0) Game
LEFT JOIN LEFT JOIN
(SELECT (SELECT
Games_Roms.GameId, COUNT(GameState.Id) AS RomSaveCount view_Games_Roms.GameId, COUNT(GameState.Id) AS RomSaveCount
FROM FROM
GameState GameState
JOIN Games_Roms ON GameState.RomId = Games_Roms.Id JOIN view_Games_Roms ON GameState.RomId = view_Games_Roms.Id
WHERE WHERE
GameState.IsMediaGroup = 0 GameState.IsMediaGroup = 0
AND GameState.UserId = @userid AND GameState.UserId = @userid
GROUP BY Games_Roms.GameId) RomSavedStates ON Game.Id = RomSavedStates.GameId GROUP BY view_Games_Roms.GameId) RomSavedStates ON Game.Id = RomSavedStates.GameId
LEFT JOIN LEFT JOIN
(SELECT (SELECT
RomMediaGroup.GameId, RomMediaGroup.GameId,
@@ -515,25 +548,31 @@ FROM
LEFT JOIN LEFT JOIN
Relation_Game_PlayerPerspectives ON Game.Id = Relation_Game_PlayerPerspectives.GameId Relation_Game_PlayerPerspectives ON Game.Id = Relation_Game_PlayerPerspectives.GameId
LEFT JOIN LEFT JOIN
Relation_Game_Themes ON Game.Id = Relation_Game_Themes.GameId " + whereClause + " " + havingClause + " " + orderByClause; Relation_Game_Themes ON Game.Id = Relation_Game_Themes.GameId
LEFT JOIN
Favourites ON Game.Id = Favourites.GameId AND Favourites.UserId = @userid " + whereClause + " " + havingClause + " " + orderByClause;
List<Games.MinimalGameItem> RetVal = new List<Games.MinimalGameItem>(); List<Games.MinimalGameItem> RetVal = new List<Games.MinimalGameItem>();
DataTable dbResponse = db.ExecuteCMD(sql, whereParams); DataTable dbResponse = db.ExecuteCMD(sql, whereParams, new Database.DatabaseMemoryCacheOptions(CacheEnabled: true, ExpirationSeconds: 60));
// get count // get count
int RecordCount = dbResponse.Rows.Count; int RecordCount = dbResponse.Rows.Count;
// compile data for return // compile data for return
int pageOffset = pageSize * (pageNumber - 1); int pageOffset = pageSize * (pageNumber - 1);
for (int i = pageOffset; i < dbResponse.Rows.Count; i++) for (int i = pageOffset; i < dbResponse.Rows.Count; i++)
{ {
if (i >= (pageOffset + pageSize)) if (pageNumber != 0 && pageSize != 0)
{ {
break; if (i >= (pageOffset + pageSize))
{
break;
}
} }
Game retGame = Storage.BuildCacheObject<Game>(new Game() , dbResponse.Rows[i]); Game retGame = Storage.BuildCacheObject<Game>(new Game(), dbResponse.Rows[i]);
Games.MinimalGameItem retMinGame = new Games.MinimalGameItem(retGame); Games.MinimalGameItem retMinGame = new Games.MinimalGameItem(retGame);
retMinGame.Index = i;
if (dbResponse.Rows[i]["RomSaveCount"] != DBNull.Value || dbResponse.Rows[i]["MediaGroupSaveCount"] != DBNull.Value) if (dbResponse.Rows[i]["RomSaveCount"] != DBNull.Value || dbResponse.Rows[i]["MediaGroupSaveCount"] != DBNull.Value)
{ {
retMinGame.HasSavedGame = true; retMinGame.HasSavedGame = true;
@@ -542,14 +581,66 @@ FROM
{ {
retMinGame.HasSavedGame = false; retMinGame.HasSavedGame = false;
} }
if ((int)dbResponse.Rows[i]["Favourite"] == 0)
{
retMinGame.IsFavourite = false;
}
else
{
retMinGame.IsFavourite = true;
}
RetVal.Add(retMinGame); RetVal.Add(retMinGame);
} }
// build alpha list
Dictionary<string, int> AlphaList = new Dictionary<string, int>();
if (orderByField == "NameThe" || orderByField == "Name")
{
int CurrentPage = 1;
int NextPageIndex = pageSize;
string alphaSearchField;
if (orderByField == "NameThe")
{
alphaSearchField = "NameThe";
}
else
{
alphaSearchField = "Name";
}
for (int i = 0; i < dbResponse.Rows.Count; i++)
{
if (NextPageIndex == i + 1)
{
NextPageIndex += pageSize;
CurrentPage += 1;
}
string firstChar = dbResponse.Rows[i][alphaSearchField].ToString().Substring(0, 1).ToUpperInvariant();
if ("ABCDEFGHIJKLMNOPQRSTUVWXYZ".Contains(firstChar))
{
if (!AlphaList.ContainsKey(firstChar))
{
AlphaList.Add(firstChar, CurrentPage);
}
}
else
{
if (!AlphaList.ContainsKey("#"))
{
AlphaList.Add("#", 1);
}
}
}
}
GameReturnPackage gameReturn = new GameReturnPackage GameReturnPackage gameReturn = new GameReturnPackage
{ {
Count = RecordCount, Count = RecordCount,
Games = RetVal Games = RetVal,
AlphaList = AlphaList
}; };
return gameReturn; return gameReturn;
@@ -577,6 +668,7 @@ FROM
public int Count { get; set; } public int Count { get; set; }
public List<Games.MinimalGameItem> Games { get; set; } = new List<Games.MinimalGameItem>(); public List<Games.MinimalGameItem> Games { get; set; } = new List<Games.MinimalGameItem>();
public Dictionary<string, int> AlphaList { get; set; }
} }
} }
} }

View File

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

View File

@@ -5,6 +5,9 @@ using gaseous_server.Classes;
using Authentication; using Authentication;
using Microsoft.AspNetCore.Identity; using Microsoft.AspNetCore.Identity;
using System.Data; using System.Data;
using Asp.Versioning;
using System.IO.Compression;
using gaseous_server.Classes.Metadata;
namespace gaseous_server.Controllers.v1_1 namespace gaseous_server.Controllers.v1_1
{ {
@@ -12,7 +15,7 @@ namespace gaseous_server.Controllers.v1_1
[ApiVersion("1.0")] [ApiVersion("1.0")]
[ApiVersion("1.1")] [ApiVersion("1.1")]
[ApiController] [ApiController]
public class StateManagerController: ControllerBase public class StateManagerController : ControllerBase
{ {
private readonly UserManager<ApplicationUser> _userManager; private readonly UserManager<ApplicationUser> _userManager;
private readonly SignInManager<ApplicationUser> _signInManager; private readonly SignInManager<ApplicationUser> _signInManager;
@@ -36,9 +39,11 @@ namespace gaseous_server.Controllers.v1_1
public async Task<ActionResult> SaveStateAsync(long RomId, UploadStateModel uploadState, bool IsMediaGroup = false) public async Task<ActionResult> SaveStateAsync(long RomId, UploadStateModel uploadState, bool IsMediaGroup = false)
{ {
var user = await _userManager.GetUserAsync(User); var user = await _userManager.GetUserAsync(User);
byte[] CompressedState = Common.Compress(uploadState.StateByteArray);
Database db = new Database(Database.databaseType.MySql, Config.DatabaseConfiguration.ConnectionString); Database db = new Database(Database.databaseType.MySql, Config.DatabaseConfiguration.ConnectionString);
string sql = "INSERT INTO GameState (UserId, RomId, IsMediaGroup, StateDateTime, Name, Screenshot, State) VALUES (@userid, @romid, @ismediagroup, @statedatetime, @name, @screenshot, @state); SELECT LAST_INSERT_ID();"; string sql = "INSERT INTO GameState (UserId, RomId, IsMediaGroup, StateDateTime, Name, Screenshot, State, Zipped) VALUES (@userid, @romid, @ismediagroup, @statedatetime, @name, @screenshot, @state, @zipped); SELECT LAST_INSERT_ID();";
Dictionary<string, object> dbDict = new Dictionary<string, object> Dictionary<string, object> dbDict = new Dictionary<string, object>
{ {
{ "userid", user.Id }, { "userid", user.Id },
@@ -47,10 +52,20 @@ namespace gaseous_server.Controllers.v1_1
{ "statedatetime", DateTime.UtcNow }, { "statedatetime", DateTime.UtcNow },
{ "name", "" }, { "name", "" },
{ "screenshot", uploadState.ScreenshotByteArray }, { "screenshot", uploadState.ScreenshotByteArray },
{ "state", uploadState.StateByteArray } { "state", CompressedState },
{ "zipped", true }
}; };
DataTable data = db.ExecuteCMD(sql, dbDict); DataTable data = db.ExecuteCMD(sql, dbDict);
if (IsMediaGroup == false)
{
Logging.Log(Logging.LogType.Information, "Save State", "Saved state for rom id " + RomId + ". State size: " + uploadState.StateByteArrayBase64.Length + " Compressed size: " + CompressedState.Length);
}
else
{
Logging.Log(Logging.LogType.Information, "Save State", "Saved state for media group id " + RomId + ". State size: " + uploadState.StateByteArrayBase64.Length + " Compressed size: " + CompressedState.Length);
}
return Ok(await GetStateAsync(RomId, (long)(ulong)data.Rows[0][0], IsMediaGroup)); return Ok(await GetStateAsync(RomId, (long)(ulong)data.Rows[0][0], IsMediaGroup));
} }
@@ -73,7 +88,7 @@ namespace gaseous_server.Controllers.v1_1
{ "ismediagroup", IsMediaGroup } { "ismediagroup", IsMediaGroup }
}; };
DataTable data = db.ExecuteCMD(sql, dbDict); DataTable data = db.ExecuteCMD(sql, dbDict);
List<Models.GameStateItem> gameStates = new List<GameStateItem>(); List<Models.GameStateItem> gameStates = new List<GameStateItem>();
foreach (DataRow row in data.Rows) foreach (DataRow row in data.Rows)
{ {
@@ -103,7 +118,7 @@ namespace gaseous_server.Controllers.v1_1
{ "ismediagroup", IsMediaGroup } { "ismediagroup", IsMediaGroup }
}; };
DataTable data = db.ExecuteCMD(sql, dbDict); DataTable data = db.ExecuteCMD(sql, dbDict);
if (data.Rows.Count == 0) if (data.Rows.Count == 0)
{ {
// invalid match - return not found // invalid match - return not found
@@ -137,7 +152,7 @@ namespace gaseous_server.Controllers.v1_1
{ "ismediagroup", IsMediaGroup } { "ismediagroup", IsMediaGroup }
}; };
db.ExecuteNonQuery(sql, dbDict); db.ExecuteNonQuery(sql, dbDict);
return Ok(); return Ok();
} }
@@ -162,7 +177,7 @@ namespace gaseous_server.Controllers.v1_1
{ "name", model.Name } { "name", model.Name }
}; };
db.ExecuteNonQuery(sql, dbDict); db.ExecuteNonQuery(sql, dbDict);
return Ok(); return Ok();
} }
@@ -187,7 +202,7 @@ namespace gaseous_server.Controllers.v1_1
{ "ismediagroup", IsMediaGroup } { "ismediagroup", IsMediaGroup }
}; };
DataTable data = db.ExecuteCMD(sql, dbDict); DataTable data = db.ExecuteCMD(sql, dbDict);
if (data.Rows.Count == 0) if (data.Rows.Count == 0)
{ {
// invalid match - return not found // invalid match - return not found
@@ -220,11 +235,11 @@ namespace gaseous_server.Controllers.v1_1
[ProducesResponseType(StatusCodes.Status404NotFound)] [ProducesResponseType(StatusCodes.Status404NotFound)]
[Route("{RomId}/{StateId}/State/")] [Route("{RomId}/{StateId}/State/")]
[Route("{RomId}/{StateId}/State/savestate.state")] [Route("{RomId}/{StateId}/State/savestate.state")]
public async Task<ActionResult> GetStateDataAsync(long RomId, long StateId, bool IsMediaGroup = false) public async Task<ActionResult> GetStateDataAsync(long RomId, long StateId, bool IsMediaGroup = false, bool StateOnly = false)
{ {
var user = await _userManager.GetUserAsync(User); var user = await _userManager.GetUserAsync(User);
Database db = new Database(Database.databaseType.MySql, Config.DatabaseConfiguration.ConnectionString); Database db = new Database(Database.databaseType.MySql, Config.DatabaseConfiguration.ConnectionString);
string sql = "SELECT State FROM GameState WHERE Id = @id AND RomId = @romid AND IsMediaGroup = @ismediagroup AND UserId = @userid;"; string sql = "SELECT * FROM GameState WHERE Id = @id AND RomId = @romid AND IsMediaGroup = @ismediagroup AND UserId = @userid;";
Dictionary<string, object> dbDict = new Dictionary<string, object> Dictionary<string, object> dbDict = new Dictionary<string, object>
{ {
{ "id", StateId }, { "id", StateId },
@@ -233,7 +248,7 @@ namespace gaseous_server.Controllers.v1_1
{ "ismediagroup", IsMediaGroup } { "ismediagroup", IsMediaGroup }
}; };
DataTable data = db.ExecuteCMD(sql, dbDict); DataTable data = db.ExecuteCMD(sql, dbDict);
if (data.Rows.Count == 0) if (data.Rows.Count == 0)
{ {
// invalid match - return not found // invalid match - return not found
@@ -241,9 +256,115 @@ namespace gaseous_server.Controllers.v1_1
} }
else else
{ {
string filename = "savestate.state"; // get rom data
byte[] bytes = (byte[])data.Rows[0][0]; string romName = "";
string contentType = "application/octet-stream"; string romMd5 = "";
string romSha1 = "";
if (IsMediaGroup == false)
{
Roms.GameRomItem romItem = Roms.GetRom(RomId);
romName = romItem.Name;
romMd5 = romItem.Md5;
romSha1 = romItem.Sha1;
}
else
{
RomMediaGroup.GameRomMediaGroupItem mediaGroupItem = RomMediaGroup.GetMediaGroup(RomId);
IGDB.Models.Game game = Games.GetGame(mediaGroupItem.GameId, false, false, false);
Classes.Common.hashObject hashObject = new Classes.Common.hashObject(Path.Combine(Config.LibraryConfiguration.LibraryMediaGroupDirectory, mediaGroupItem.Id.ToString() + ".zip"));
romName = game.Name;
romMd5 = hashObject.md5hash;
romSha1 = hashObject.sha1hash;
}
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(romName);
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", romName },
{ "StateDateTime", data.Rows[0]["StateDateTime"] },
{ "StateName", data.Rows[0]["Name"] }
};
if ((int)data.Rows[0]["IsMediaGroup"] == 0)
{
RomInfo.Add("MD5", romMd5);
RomInfo.Add("SHA1", romSha1);
RomInfo.Add("Type", "ROM");
}
else
{
RomInfo.Add("Type", "Media Group");
RomInfo.Add("MediaGroupId", (long)data.Rows[0]["RomId"]);
}
string RomInfoString = Newtonsoft.Json.JsonConvert.SerializeObject(RomInfo, Newtonsoft.Json.Formatting.Indented, new Newtonsoft.Json.JsonSerializerSettings { NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore });
// compile zip file
using (var compressedFileStream = new MemoryStream())
{
List<Dictionary<string, object>> Attachments = new List<Dictionary<string, object>>();
Attachments.Add(new Dictionary<string, object>
{
{ "Name", "savestate.state" },
{ "Body", bytes }
});
// check if value is dbnull
if (data.Rows[0]["Screenshot"] != DBNull.Value)
{
Attachments.Add(new Dictionary<string, object>
{
{ "Name", "screenshot.jpg" },
{ "Body", (byte[])data.Rows[0]["Screenshot"] }
});
}
Attachments.Add(new Dictionary<string, object>
{
{ "Name", "rominfo.json" },
{ "Body", System.Text.Encoding.UTF8.GetBytes(RomInfoString) }
});
//Create an archive and store the stream in memory.
using (var zipArchive = new ZipArchive(compressedFileStream, ZipArchiveMode.Create, false))
{
foreach (var Attachment in Attachments)
{
//Create a zip entry for each attachment
var zipEntry = zipArchive.CreateEntry(Attachment["Name"].ToString());
//Get the stream of the attachment
using (var originalFileStream = new MemoryStream((byte[])Attachment["Body"]))
using (var zipEntryStream = zipEntry.Open())
{
//Copy the attachment stream to the zip entry stream
originalFileStream.CopyTo(zipEntryStream);
}
}
}
//return new FileContentResult(compressedFileStream.ToArray(), "application/zip") { FileDownloadName = filename };
bytes = compressedFileStream.ToArray();
}
}
var cd = new System.Net.Mime.ContentDisposition var cd = new System.Net.Mime.ContentDisposition
{ {
@@ -258,6 +379,156 @@ namespace gaseous_server.Controllers.v1_1
} }
} }
[MapToApiVersion("1.0")]
[MapToApiVersion("1.1")]
[HttpPost]
[Authorize]
[ProducesResponseType(StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status404NotFound)]
[RequestSizeLimit(long.MaxValue)]
[Consumes("multipart/form-data")]
[DisableRequestSizeLimit, RequestFormLimits(MultipartBodyLengthLimit = long.MaxValue, ValueLengthLimit = int.MaxValue)]
[Route("Upload")]
public async Task<ActionResult> UploadStateDataAsync(IFormFile file, long RomId = 0, bool IsMediaGroup = false)
{
// get user
var user = await _userManager.GetUserAsync(User);
if (file.Length > 0)
{
MemoryStream fileContent = new MemoryStream();
file.CopyTo(fileContent);
// test if file is a zip file
try
{
using (var zipArchive = new ZipArchive(fileContent, ZipArchiveMode.Read, false))
{
foreach (var entry in zipArchive.Entries)
{
if (entry.FullName == "rominfo.json")
{
using (var stream = entry.Open())
using (var reader = new StreamReader(stream))
{
string RomInfoString = reader.ReadToEnd();
Dictionary<string, object> RomInfo = Newtonsoft.Json.JsonConvert.DeserializeObject<Dictionary<string, object>>(RomInfoString);
// get rom data
Roms.GameRomItem romItem;
try
{
romItem = Roms.GetRom((string)RomInfo["MD5"]);
}
catch (Roms.InvalidRomHash)
{
return NotFound();
}
// get state data
byte[] StateData = null;
byte[] ScreenshotData = null;
string StateName = RomInfo["StateName"].ToString();
DateTime StateDateTime = DateTime.Parse(RomInfo["StateDateTime"].ToString());
IsMediaGroup = RomInfo["Type"].ToString() == "Media Group" ? true : false;
if (zipArchive.GetEntry("savestate.state") != null)
{
using (var stateStream = zipArchive.GetEntry("savestate.state").Open())
using (var stateReader = new MemoryStream())
{
stateStream.CopyTo(stateReader);
StateData = stateReader.ToArray();
}
}
if (zipArchive.GetEntry("screenshot.jpg") != null)
{
using (var screenshotStream = zipArchive.GetEntry("screenshot.jpg").Open())
using (var screenshotReader = new MemoryStream())
{
screenshotStream.CopyTo(screenshotReader);
ScreenshotData = screenshotReader.ToArray();
}
}
// save state
Database db = new Database(Database.databaseType.MySql, Config.DatabaseConfiguration.ConnectionString);
string sql = "INSERT INTO GameState (UserId, RomId, IsMediaGroup, StateDateTime, Name, Screenshot, State, Zipped) VALUES (@userid, @romid, @ismediagroup, @statedatetime, @name, @screenshot, @state, @zipped); SELECT LAST_INSERT_ID();";
Dictionary<string, object> dbDict = new Dictionary<string, object>
{
{ "userid", user.Id },
{ "romid", romItem.Id },
{ "ismediagroup", IsMediaGroup },
{ "statedatetime", StateDateTime },
{ "name", StateName },
{ "screenshot", ScreenshotData },
{ "state", Common.Compress(StateData) },
{ "zipped", true }
};
DataTable data = db.ExecuteCMD(sql, dbDict);
RomInfo.Add("RomId", romItem.Id);
RomInfo.Add("Management", "Managed");
return Ok(RomInfo);
}
}
}
}
return BadRequest("File is not a valid Gaseous state file.");
}
catch
{
// not a zip file
if (RomId != 0)
{
// get rom data
Roms.GameRomItem romItem;
try
{
romItem = Roms.GetRom(RomId);
}
catch (Roms.InvalidRomHash)
{
return NotFound();
}
// save state
Database db = new Database(Database.databaseType.MySql, Config.DatabaseConfiguration.ConnectionString);
string sql = "INSERT INTO GameState (UserId, RomId, IsMediaGroup, StateDateTime, Name, Screenshot, State, Zipped) VALUES (@userid, @romid, @ismediagroup, @statedatetime, @name, @screenshot, @state, @zipped); SELECT LAST_INSERT_ID();";
Dictionary<string, object> dbDict = new Dictionary<string, object>
{
{ "userid", user.Id },
{ "romid", RomId },
{ "ismediagroup", IsMediaGroup },
{ "statedatetime", DateTime.UtcNow },
{ "name", "" },
{ "screenshot", null },
{ "state", Common.Compress(fileContent.ToArray()) },
{ "zipped", true }
};
DataTable data = db.ExecuteCMD(sql, dbDict);
return Ok(new Dictionary<string, object>
{
{ "RomId", RomId },
{ "Management", "Unmanaged" }
});
}
else
{
return BadRequest("No rom id provided.");
}
}
}
else
{
return BadRequest("File is empty.");
}
}
private Models.GameStateItem BuildGameStateItem(DataRow dr) private Models.GameStateItem BuildGameStateItem(DataRow dr)
{ {
bool HasScreenshot = true; bool HasScreenshot = true;

View File

@@ -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}/{PlatformId}/{RomId}")]
public async Task<ActionResult> NewRecordStatistics(long GameId, long PlatformId, long RomId, bool IsMediaGroup)
{
var user = await _userManager.GetUserAsync(User);
if (user != null)
{
Statistics statistics = new Statistics();
return Ok(statistics.RecordSession(Guid.Empty, GameId, PlatformId, RomId, IsMediaGroup, 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}/{PlatformId}/{RomId}/{SessionId}")]
public async Task<ActionResult> SubsequentRecordStatistics(long GameId, long PlatformId, long RomId, Guid SessionId, bool IsMediaGroup)
{
var user = await _userManager.GetUserAsync(User);
if (user != null)
{
Statistics statistics = new Statistics();
return Ok(statistics.RecordSession(SessionId, GameId, PlatformId, RomId, IsMediaGroup, 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,137 @@
using Asp.Versioning;
using Authentication;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Identity;
using Microsoft.AspNetCore.Mvc;
namespace gaseous_server.Controllers
{
[ApiController]
[Route("api/v{version:apiVersion}/[controller]")]
[ApiVersion("1.0")]
[ApiVersion("1.1")]
[Authorize]
public class UserProfileController : ControllerBase
{
private readonly UserManager<ApplicationUser> _userManager;
private readonly SignInManager<ApplicationUser> _signInManager;
public UserProfileController(
UserManager<ApplicationUser> userManager,
SignInManager<ApplicationUser> signInManager
)
{
_userManager = userManager;
_signInManager = signInManager;
}
[MapToApiVersion("1.0")]
[MapToApiVersion("1.1")]
[HttpGet]
[Route("{UserId}")]
[ProducesResponseType(typeof(Models.UserProfile), StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status401Unauthorized)]
[ResponseCache(CacheProfileName = "Default30")]
public ActionResult GetUserProfile(string UserId)
{
Classes.UserProfile profile = new Classes.UserProfile();
Models.UserProfile RetVal = profile.GetUserProfile(UserId);
return Ok(RetVal);
}
[MapToApiVersion("1.0")]
[MapToApiVersion("1.1")]
[HttpPut]
[Route("{UserId}")]
[ProducesResponseType(StatusCodes.Status200OK)]
public async Task<ActionResult> UpdateUserProfileAsync(string UserId, Models.UserProfile profile)
{
var user = await _userManager.GetUserAsync(User);
if (user.ProfileId.ToString() != UserId)
{
return Unauthorized();
}
Classes.UserProfile userProfile = new Classes.UserProfile();
userProfile.UpdateUserProfile(user.Id, profile);
return Ok();
}
[MapToApiVersion("1.0")]
[MapToApiVersion("1.1")]
[HttpPut]
[ProducesResponseType(typeof(List<IFormFile>), StatusCodes.Status200OK)]
[RequestSizeLimit(long.MaxValue)]
[Consumes("multipart/form-data")]
[DisableRequestSizeLimit, RequestFormLimits(MultipartBodyLengthLimit = long.MaxValue, ValueLengthLimit = int.MaxValue)]
[Route("{UserId}/{ProfileImageType}")]
public async Task<ActionResult> UpdateAvatarAsync(string UserId, Classes.UserProfile.ImageType ProfileImageType, IFormFile file)
{
var user = await _userManager.GetUserAsync(User);
if (user.ProfileId.ToString() != UserId)
{
return Unauthorized();
}
if (file.Length > 0)
{
using (var ms = new MemoryStream())
{
file.CopyTo(ms);
byte[] fileBytes = ms.ToArray();
Classes.UserProfile userProfile = new Classes.UserProfile();
userProfile.UpdateImage(ProfileImageType, UserId, user.Id, file.FileName, fileBytes);
}
}
return Ok();
}
[MapToApiVersion("1.0")]
[MapToApiVersion("1.1")]
[HttpGet]
[Route("{UserId}/{ProfileImageType}")]
[ProducesResponseType(typeof(FileStreamResult), StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status404NotFound)]
[ResponseCache(CacheProfileName = "5Minute")]
public async Task<ActionResult> GetAvatarAsync(string UserId, Classes.UserProfile.ImageType ProfileImageType)
{
Classes.UserProfile userProfile = new Classes.UserProfile();
Models.ImageItem image = userProfile.GetImage(ProfileImageType, UserId);
if (image == null)
{
return NotFound();
}
else
{
return File(image.content, image.mimeType, UserId + image.extension);
}
}
[MapToApiVersion("1.0")]
[MapToApiVersion("1.1")]
[HttpDelete]
[Route("{UserId}/{ProfileImageType}")]
[ProducesResponseType(StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status401Unauthorized)]
public async Task<ActionResult> DeleteAvatarAsync(string UserId, Classes.UserProfile.ImageType ProfileImageType)
{
var user = await _userManager.GetUserAsync(User);
if (user.ProfileId.ToString() != UserId)
{
return Unauthorized();
}
Classes.UserProfile userProfile = new Classes.UserProfile();
userProfile.DeleteImage(ProfileImageType, user.Id);
return Ok();
}
}
}

View File

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

View File

@@ -0,0 +1,9 @@
namespace gaseous_server.Models
{
public class ImageItem
{
public byte[] content { get; set; }
public string mimeType { get; set; }
public string extension { get; set; }
}
}

View File

@@ -13,10 +13,8 @@ using Newtonsoft.Json;
namespace gaseous_server.Models namespace gaseous_server.Models
{ {
public class PlatformMapping public class PlatformMapping
{ {
private static Dictionary<string, PlatformMapItem> PlatformMapCache = new Dictionary<string, PlatformMapItem>();
/// <summary> /// <summary>
/// Updates the platform map from the embedded platform map resource /// Updates the platform map from the embedded platform map resource
/// </summary> /// </summary>
@@ -27,7 +25,8 @@ namespace gaseous_server.Models
{ {
string rawJson = reader.ReadToEnd(); string rawJson = reader.ReadToEnd();
List<PlatformMapItem> platforms = new List<PlatformMapItem>(); List<PlatformMapItem> platforms = new List<PlatformMapItem>();
Newtonsoft.Json.JsonSerializerSettings jsonSerializerSettings = new JsonSerializerSettings{ Newtonsoft.Json.JsonSerializerSettings jsonSerializerSettings = new JsonSerializerSettings
{
MaxDepth = 64 MaxDepth = 64
}; };
platforms = Newtonsoft.Json.JsonConvert.DeserializeObject<List<PlatformMapItem>>(rawJson, jsonSerializerSettings); platforms = Newtonsoft.Json.JsonConvert.DeserializeObject<List<PlatformMapItem>>(rawJson, jsonSerializerSettings);
@@ -42,6 +41,7 @@ namespace gaseous_server.Models
// exists // exists
if (ResetToDefault == false) if (ResetToDefault == false)
{ {
WriteAvailableEmulators(mapItem);
Logging.Log(Logging.LogType.Information, "Platform Map", "Skipping import of " + mapItem.IGDBName + " - already in database."); Logging.Log(Logging.LogType.Information, "Platform Map", "Skipping import of " + mapItem.IGDBName + " - already in database.");
} }
else else
@@ -72,8 +72,8 @@ namespace gaseous_server.Models
foreach (PlatformMapItem mapItem in platforms) foreach (PlatformMapItem mapItem in platforms)
{ {
// get the IGDB platform data // insert dummy platform data - it'll be cleaned up on the first metadata refresh
Platform platform = Platforms.GetPlatform(mapItem.IGDBId); Platform platform = CreateDummyPlatform(mapItem);
try try
{ {
@@ -91,74 +91,83 @@ namespace gaseous_server.Models
} }
} }
} }
private static IGDB.Models.Platform CreateDummyPlatform(PlatformMapItem mapItem)
{
IGDB.Models.Platform platform = new IGDB.Models.Platform
{
Id = mapItem.IGDBId,
Name = mapItem.IGDBName,
Slug = mapItem.IGDBSlug,
AlternativeName = mapItem.AlternateNames.FirstOrDefault()
};
if (Storage.GetCacheStatus("Platform", mapItem.IGDBId) == Storage.CacheStatus.NotPresent)
{
Storage.NewCacheValue(platform);
}
return platform;
}
public static List<PlatformMapItem> PlatformMap public static List<PlatformMapItem> PlatformMap
{ {
get get
{ {
// if (Database.DatabaseMemoryCache.GetCacheObject("PlatformMap") != null)
// {
// return (List<PlatformMapItem>)Database.DatabaseMemoryCache.GetCacheObject("PlatformMap");
// }
Database db = new Database(Database.databaseType.MySql, Config.DatabaseConfiguration.ConnectionString); Database db = new Database(Database.databaseType.MySql, Config.DatabaseConfiguration.ConnectionString);
string sql = "SELECT * FROM PlatformMap"; string sql = "SELECT * FROM PlatformMap";
DataTable data = db.ExecuteCMD(sql); DataTable data = db.ExecuteCMD(sql); //, new Database.DatabaseMemoryCacheOptions(true, (int)TimeSpan.FromSeconds(5).Ticks));
List<PlatformMapItem> platformMaps = new List<PlatformMapItem>(); List<PlatformMapItem> platformMaps = new List<PlatformMapItem>();
foreach (DataRow row in data.Rows) foreach (DataRow row in data.Rows)
{ {
long mapId = (long)row["Id"]; long mapId = (long)row["Id"];
if (PlatformMapCache.ContainsKey(mapId.ToString()))
PlatformMapItem mapItem = BuildPlatformMapItem(row);
if (mapItem != null)
{ {
PlatformMapItem mapItem = PlatformMapCache[mapId.ToString()]; platformMaps.Add(mapItem);
if (mapItem != null)
{
platformMaps.Add(mapItem);
}
}
else
{
PlatformMapItem mapItem = BuildPlatformMapItem(row);
if (mapItem != null)
{
platformMaps.Add(mapItem);
}
} }
} }
platformMaps.Sort((x, y) => x.IGDBName.CompareTo(y.IGDBName)); platformMaps.Sort((x, y) => x.IGDBName.CompareTo(y.IGDBName));
//Database.DatabaseMemoryCache.SetCacheObject("PlatformMap", platformMaps, 600);
return platformMaps; return platformMaps;
} }
} }
public static PlatformMapItem GetPlatformMap(long Id) public static PlatformMapItem GetPlatformMap(long Id)
{ {
if (PlatformMapCache.ContainsKey(Id.ToString())) Database db = new Database(Database.databaseType.MySql, Config.DatabaseConfiguration.ConnectionString);
string sql = "SELECT * FROM PlatformMap WHERE Id = @Id";
Dictionary<string, object> dbDict = new Dictionary<string, object>();
dbDict.Add("Id", Id);
DataTable data = db.ExecuteCMD(sql, dbDict);
if (data.Rows.Count > 0)
{ {
return PlatformMapCache[Id.ToString()]; PlatformMapItem platformMap = BuildPlatformMapItem(data.Rows[0]);
return platformMap;
} }
else else
{ {
Database db = new Database(Database.databaseType.MySql, Config.DatabaseConfiguration.ConnectionString); Exception exception = new Exception("Platform Map Id " + Id + " does not exist.");
string sql = "SELECT * FROM PlatformMap WHERE Id = @Id"; Logging.Log(Logging.LogType.Critical, "Platform Map", "Platform Map Id " + Id + " does not exist.", exception);
Dictionary<string, object> dbDict = new Dictionary<string, object>(); throw exception;
dbDict.Add("Id", Id);
DataTable data = db.ExecuteCMD(sql, dbDict);
if (data.Rows.Count > 0)
{
PlatformMapItem platformMap = BuildPlatformMapItem(data.Rows[0]);
return platformMap;
}
else
{
Exception exception = new Exception("Platform Map Id " + Id + " does not exist.");
Logging.Log(Logging.LogType.Critical, "Platform Map", "Platform Map Id " + Id + " does not exist.", exception);
throw exception;
}
} }
} }
public static void WritePlatformMap(PlatformMapItem item, bool Update, bool AllowAvailableEmulatorOverwrite) public static void WritePlatformMap(PlatformMapItem item, bool Update, bool AllowAvailableEmulatorOverwrite)
{ {
CreateDummyPlatform(item);
Database db = new Database(Database.databaseType.MySql, Config.DatabaseConfiguration.ConnectionString); Database db = new Database(Database.databaseType.MySql, Config.DatabaseConfiguration.ConnectionString);
string sql = ""; string sql = "";
Dictionary<string, object> dbDict = new Dictionary<string, object>(); Dictionary<string, object> dbDict = new Dictionary<string, object>();
@@ -237,20 +246,53 @@ namespace gaseous_server.Models
{ {
foreach (PlatformMapItem.EmulatorBiosItem biosItem in item.Bios) foreach (PlatformMapItem.EmulatorBiosItem biosItem in item.Bios)
{ {
sql = "INSERT INTO PlatformMap_Bios (Id, Filename, Description, Hash) VALUES (@Id, @Filename, @Description, @Hash);"; bool isEnabled = false;
if (item.EnabledBIOSHashes == null)
{
item.EnabledBIOSHashes = new List<string>();
}
if (item.EnabledBIOSHashes.Contains(biosItem.hash))
{
isEnabled = true;
}
sql = "INSERT INTO PlatformMap_Bios (Id, Filename, Description, Hash, Enabled) VALUES (@Id, @Filename, @Description, @Hash, @Enabled);";
dbDict.Clear(); dbDict.Clear();
dbDict.Add("Id", item.IGDBId); dbDict.Add("Id", item.IGDBId);
dbDict.Add("Filename", biosItem.filename); dbDict.Add("Filename", biosItem.filename);
dbDict.Add("Description", biosItem.description); dbDict.Add("Description", biosItem.description);
dbDict.Add("Hash", biosItem.hash); dbDict.Add("Hash", biosItem.hash);
dbDict.Add("Enabled", isEnabled);
db.ExecuteCMD(sql, dbDict); db.ExecuteCMD(sql, dbDict);
} }
} }
if (PlatformMapCache.ContainsKey(item.IGDBId.ToString())) // clear cache
Database.DatabaseMemoryCache.RemoveCacheObject("PlatformMap");
}
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)
{ {
PlatformMapCache.Remove(item.IGDBId.ToString()); dbDict.Add("WebEmulator_Type", item.WebEmulator.Type);
dbDict.Add("WebEmulator_Core", item.WebEmulator.Core);
dbDict.Add("AvailableWebEmulators", Newtonsoft.Json.JsonConvert.SerializeObject(item.WebEmulator.AvailableWebEmulators));
} }
else
{
dbDict.Add("WebEmulator_Type", "");
dbDict.Add("WebEmulator_Core", "");
dbDict.Add("AvailableWebEmulators", "");
}
db.ExecuteCMD(sql, dbDict);
} }
static PlatformMapItem BuildPlatformMapItem(DataRow row) static PlatformMapItem BuildPlatformMapItem(DataRow row)
@@ -261,7 +303,16 @@ namespace gaseous_server.Models
string sql = ""; string sql = "";
// get platform data // get platform data
IGDB.Models.Platform? platform = Platforms.GetPlatform(IGDBId); // IGDB.Models.Platform? platform = Platforms.GetPlatform(IGDBId, false);
IGDB.Models.Platform? platform = null;
if (Storage.GetCacheStatus("Platform", IGDBId) == Storage.CacheStatus.NotPresent)
{
//platform = Platforms.GetPlatform(IGDBId, false);
}
else
{
platform = (IGDB.Models.Platform)Storage.GetCacheValue<IGDB.Models.Platform>(new Platform(), "id", IGDBId);
}
if (platform != null) if (platform != null)
{ {
@@ -327,6 +378,7 @@ namespace gaseous_server.Models
DataTable biosTable = db.ExecuteCMD(sql, dbDict); DataTable biosTable = db.ExecuteCMD(sql, dbDict);
List<PlatformMapItem.EmulatorBiosItem> bioss = new List<PlatformMapItem.EmulatorBiosItem>(); List<PlatformMapItem.EmulatorBiosItem> bioss = new List<PlatformMapItem.EmulatorBiosItem>();
List<string> enabledBios = new List<string>();
foreach (DataRow biosRow in biosTable.Rows) foreach (DataRow biosRow in biosTable.Rows)
{ {
PlatformMapItem.EmulatorBiosItem bios = new PlatformMapItem.EmulatorBiosItem PlatformMapItem.EmulatorBiosItem bios = new PlatformMapItem.EmulatorBiosItem
@@ -336,6 +388,11 @@ namespace gaseous_server.Models
hash = ((string)Common.ReturnValueIfNull(biosRow["Hash"], "")).ToLower() hash = ((string)Common.ReturnValueIfNull(biosRow["Hash"], "")).ToLower()
}; };
bioss.Add(bios); bioss.Add(bios);
if ((bool)Common.ReturnValueIfNull(biosRow["Enabled"], true) == true)
{
enabledBios.Add(bios.hash);
}
} }
// build item // build item
@@ -344,26 +401,20 @@ namespace gaseous_server.Models
mapItem.IGDBName = platform.Name; mapItem.IGDBName = platform.Name;
mapItem.IGDBSlug = platform.Slug; mapItem.IGDBSlug = platform.Slug;
mapItem.AlternateNames = alternateNames; mapItem.AlternateNames = alternateNames;
mapItem.Extensions = new PlatformMapItem.FileExtensions{ mapItem.Extensions = new PlatformMapItem.FileExtensions
{
SupportedFileExtensions = knownExtensions, SupportedFileExtensions = knownExtensions,
UniqueFileExtensions = uniqueExtensions UniqueFileExtensions = uniqueExtensions
}; };
mapItem.RetroPieDirectoryName = (string)Common.ReturnValueIfNull(row["RetroPieDirectoryName"], ""); mapItem.RetroPieDirectoryName = (string)Common.ReturnValueIfNull(row["RetroPieDirectoryName"], "");
mapItem.WebEmulator = new PlatformMapItem.WebEmulatorItem{ mapItem.WebEmulator = new PlatformMapItem.WebEmulatorItem
{
Type = (string)Common.ReturnValueIfNull(row["WebEmulator_Type"], ""), Type = (string)Common.ReturnValueIfNull(row["WebEmulator_Type"], ""),
Core = (string)Common.ReturnValueIfNull(row["WebEmulator_Core"], ""), Core = (string)Common.ReturnValueIfNull(row["WebEmulator_Core"], ""),
AvailableWebEmulators = Newtonsoft.Json.JsonConvert.DeserializeObject<List<PlatformMapItem.WebEmulatorItem.AvailableWebEmulatorItem>>((string)Common.ReturnValueIfNull(row["AvailableWebEmulators"], "[]")) AvailableWebEmulators = Newtonsoft.Json.JsonConvert.DeserializeObject<List<PlatformMapItem.WebEmulatorItem.AvailableWebEmulatorItem>>((string)Common.ReturnValueIfNull(row["AvailableWebEmulators"], "[]"))
}; };
mapItem.Bios = bioss; mapItem.Bios = bioss;
mapItem.EnabledBIOSHashes = enabledBios;
if (PlatformMapCache.ContainsKey(IGDBId.ToString()))
{
PlatformMapCache[IGDBId.ToString()] = mapItem;
}
else
{
PlatformMapCache.Add(IGDBId.ToString(), mapItem);
}
return mapItem; return mapItem;
} }
@@ -430,13 +481,81 @@ namespace gaseous_server.Models
} }
} }
public PlatformMapItem GetUserPlatformMap(string UserId, long PlatformId, long GameId)
{
// get the system enabled bios hashes
Models.PlatformMapping.PlatformMapItem platformMapItem = PlatformMapping.GetPlatformMap(PlatformId);
// get the user enabled bios hashes
PlatformMapping.UserEmulatorConfiguration userEmulatorConfiguration = GetUserEmulator(UserId, GameId, PlatformId);
if (userEmulatorConfiguration != null)
{
platformMapItem.WebEmulator.Type = userEmulatorConfiguration.EmulatorType;
platformMapItem.WebEmulator.Core = userEmulatorConfiguration.Core;
platformMapItem.EnabledBIOSHashes = userEmulatorConfiguration.EnableBIOSFiles;
}
return platformMapItem;
}
public UserEmulatorConfiguration GetUserEmulator(string UserId, long GameId, long PlatformId)
{
Database db = new Database(Database.databaseType.MySql, Config.DatabaseConfiguration.ConnectionString);
string sql = "SELECT Mapping FROM User_PlatformMap WHERE id = @UserId AND GameId = @GameId AND PlatformId = @PlatformId;";
Dictionary<string, object> dbDict = new Dictionary<string, object>
{
{ "UserId", UserId },
{ "GameId", GameId },
{ "PlatformId", PlatformId }
};
DataTable data = db.ExecuteCMD(sql, dbDict);
if (data.Rows.Count > 0)
{
UserEmulatorConfiguration emulator = Newtonsoft.Json.JsonConvert.DeserializeObject<UserEmulatorConfiguration>((string)data.Rows[0]["Mapping"]);
return emulator;
}
else
{
return null;
}
}
public void SetUserEmulator(string UserId, long GameId, long PlatformId, UserEmulatorConfiguration Mapping)
{
Database db = new Database(Database.databaseType.MySql, Config.DatabaseConfiguration.ConnectionString);
string sql = "INSERT INTO User_PlatformMap (id, GameId, PlatformId, Mapping) VALUES (@UserId, @GameId, @PlatformId, @Mapping) ON DUPLICATE KEY UPDATE Mapping = @Mapping;";
Dictionary<string, object> dbDict = new Dictionary<string, object>
{
{ "UserId", UserId },
{ "GameId", GameId },
{ "PlatformId", PlatformId },
{ "Mapping", Newtonsoft.Json.JsonConvert.SerializeObject(Mapping) }
};
db.ExecuteCMD(sql, dbDict);
}
public void DeleteUserEmulator(string UserId, long GameId, long PlatformId)
{
Database db = new Database(Database.databaseType.MySql, Config.DatabaseConfiguration.ConnectionString);
string sql = "DELETE FROM User_PlatformMap WHERE id = @UserId AND GameId = @GameId AND PlatformId = @PlatformId;";
Dictionary<string, object> dbDict = new Dictionary<string, object>
{
{ "UserId", UserId },
{ "GameId", GameId },
{ "PlatformId", PlatformId }
};
db.ExecuteCMD(sql, dbDict);
}
public class PlatformMapItem public class PlatformMapItem
{ {
public long IGDBId { get; set; } public long IGDBId { get; set; }
public string IGDBName { get; set; } public string IGDBName { get; set; }
public string IGDBSlug { get; set; } public string IGDBSlug { get; set; }
public List<string> AlternateNames { get; set; } = new List<string>(); public List<string> AlternateNames { get; set; } = new List<string>();
public FileExtensions Extensions { get; set; } public FileExtensions Extensions { get; set; }
public class FileExtensions public class FileExtensions
{ {
@@ -477,7 +596,16 @@ namespace gaseous_server.Models
public string description { get; set; } public string description { get; set; }
public string filename { get; set; } public string filename { get; set; }
} }
public List<string> EnabledBIOSHashes { get; set; }
} }
}
public class UserEmulatorConfiguration
{
public string EmulatorType { get; set; }
public string Core { get; set; }
public List<string> EnableBIOSFiles { get; set; } = new List<string>();
}
}
} }

View File

@@ -4,12 +4,36 @@ using gaseous_signature_parser.models.RomSignatureObject;
namespace gaseous_server.Models namespace gaseous_server.Models
{ {
public class Signatures_Games : HasheousClient.Models.LookupResponseModel public class Signatures_Games : HasheousClient.Models.SignatureModel
{ {
public Signatures_Games() public Signatures_Games()
{ {
} }
[JsonIgnore]
public int Score
{
get
{
int _score = 0;
if (Game != null)
{
_score = _score + Game.Score;
}
if (Rom != null)
{
_score = _score + Rom.Score;
}
return _score;
}
}
public GameItem Game = new GameItem();
public RomItem Rom = new RomItem();
public SignatureFlags Flags = new SignatureFlags(); public SignatureFlags Flags = new SignatureFlags();
public class SignatureFlags public class SignatureFlags
@@ -18,6 +42,213 @@ namespace gaseous_server.Models
public string IGDBPlatformName { get; set; } public string IGDBPlatformName { get; set; }
public long IGDBGameId { get; set; } public long IGDBGameId { get; set; }
} }
public class GameItem : HasheousClient.Models.SignatureModel.GameItem
{
public GameItem()
{
}
[JsonIgnore]
public int Score
{
get
{
// calculate a score based on the availablility of data
int _score = 0;
var properties = this.GetType().GetProperties();
foreach (var prop in properties)
{
if (prop.GetGetMethod() != null)
{
switch (prop.Name.ToLower())
{
case "id":
case "score":
break;
case "name":
case "year":
case "publisher":
case "system":
if (prop.PropertyType == typeof(string))
{
if (prop.GetValue(this) != null)
{
string propVal = prop.GetValue(this).ToString();
if (propVal.Length > 0)
{
_score = _score + 10;
}
}
}
break;
default:
if (prop.PropertyType == typeof(string))
{
if (prop.GetValue(this) != null)
{
string propVal = prop.GetValue(this).ToString();
if (propVal.Length > 0)
{
_score = _score + 1;
}
}
}
break;
}
}
}
return _score;
}
}
}
public class RomItem : HasheousClient.Models.SignatureModel.RomItem
{
[JsonIgnore]
public int Score
{
get
{
// calculate a score based on the availablility of data
int _score = 0;
var properties = this.GetType().GetProperties();
foreach (var prop in properties)
{
if (prop.GetGetMethod() != null)
{
switch (prop.Name.ToLower())
{
case "name":
case "size":
case "crc":
case "developmentstatus":
case "flags":
case "attributes":
case "romtypemedia":
case "medialabel":
if (prop.PropertyType == typeof(string) || prop.PropertyType == typeof(Int64) || prop.PropertyType == typeof(List<string>))
{
if (prop.GetValue(this) != null)
{
string propVal = prop.GetValue(this).ToString();
if (propVal.Length > 0)
{
_score = _score + 10;
}
}
}
break;
default:
if (prop.PropertyType == typeof(string))
{
if (prop.GetValue(this) != null)
{
string propVal = prop.GetValue(this).ToString();
if (propVal.Length > 0)
{
_score = _score + 1;
}
}
}
break;
}
}
}
return _score;
}
}
public class MediaType
{
public MediaType(SignatureSourceType Source, string MediaTypeString)
{
switch (Source)
{
case RomItem.SignatureSourceType.TOSEC:
string[] typeString = MediaTypeString.Split(" ");
string inType = "";
foreach (string typeStringVal in typeString)
{
if (inType == "")
{
switch (typeStringVal.ToLower())
{
case "disk":
Media = RomItem.RomTypes.Disk;
inType = typeStringVal;
break;
case "disc":
Media = RomItem.RomTypes.Disc;
inType = typeStringVal;
break;
case "file":
Media = RomItem.RomTypes.File;
inType = typeStringVal;
break;
case "part":
Media = RomItem.RomTypes.Part;
inType = typeStringVal;
break;
case "tape":
Media = RomItem.RomTypes.Tape;
inType = typeStringVal;
break;
case "of":
inType = typeStringVal;
break;
case "side":
inType = typeStringVal;
break;
}
}
else {
switch (inType.ToLower())
{
case "disk":
case "disc":
case "file":
case "part":
case "tape":
Number = int.Parse(typeStringVal);
break;
case "of":
Count = int.Parse(typeStringVal);
break;
case "side":
Side = typeStringVal;
break;
}
inType = "";
}
}
break;
default:
break;
}
}
public RomItem.RomTypes? Media { get; set; }
public int? Number { get; set; }
public int? Count { get; set; }
public string? Side { get; set; }
}
}
} }
} }

View File

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

View File

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

@@ -0,0 +1,26 @@
using IGDB.Models;
namespace gaseous_server.Models
{
public class UserProfile
{
public Guid UserId { get; set; }
public string DisplayName { get; set; }
public string Quip { get; set; }
public NowPlayingItem? NowPlaying { get; set; }
public class NowPlayingItem
{
public Game Game { get; set; }
public Platform Platform { get; set; }
public long Duration { get; set; }
}
public ProfileImageItem? Avatar { get; set; }
public ProfileImageItem? ProfileBackground { get; set; }
public Dictionary<string, object> Data { get; set; }
public class ProfileImageItem
{
public string MimeType { get; set; }
public string Extension { get; set; }
}
}
}

View File

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

View File

@@ -3,18 +3,14 @@ using System.Text.Json.Serialization;
using gaseous_server; using gaseous_server;
using gaseous_server.Classes; using gaseous_server.Classes;
using gaseous_server.Models; using gaseous_server.Models;
using gaseous_server.SignatureIngestors.XML;
using Microsoft.AspNetCore.Http.Features; using Microsoft.AspNetCore.Http.Features;
using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.Versioning;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.AspNetCore.Server.Kestrel.Core; using Microsoft.AspNetCore.Server.Kestrel.Core;
using Microsoft.OpenApi.Models; using Microsoft.OpenApi.Models;
using Authentication; using Authentication;
using Microsoft.AspNetCore.Identity; using Microsoft.AspNetCore.Identity;
using Microsoft.AspNetCore.Identity.UI.Services;
using IGDB.Models;
using gaseous_server.Classes.Metadata; using gaseous_server.Classes.Metadata;
using Asp.Versioning;
Logging.WriteToDiskOnly = true; Logging.WriteToDiskOnly = true;
Logging.Log(Logging.LogType.Information, "Startup", "Starting Gaseous Server " + Assembly.GetExecutingAssembly().GetName().Version); Logging.Log(Logging.LogType.Information, "Startup", "Starting Gaseous Server " + Assembly.GetExecutingAssembly().GetName().Version);
@@ -40,6 +36,9 @@ db = new Database(Database.databaseType.MySql, Config.DatabaseConfiguration.Conn
// set up db // set up db
db.InitDB(); db.InitDB();
// create relation tables if they don't exist
Storage.CreateRelationsTables<IGDB.Models.Game>();
Storage.CreateRelationsTables<IGDB.Models.Platform>();
// populate db with static data for lookups // populate db with static data for lookups
AgeRatings.PopulateAgeMap(); AgeRatings.PopulateAgeMap();
@@ -49,21 +48,15 @@ Config.InitSettings();
// write updated settings back to the config file // write updated settings back to the config file
Config.UpdateConfig(); Config.UpdateConfig();
// update default library path
GameLibrary.UpdateDefaultLibraryPath();
// set api metadata source from config // set api metadata source from config
Communications.MetadataSource = Config.MetadataConfiguration.MetadataSource; Communications.MetadataSource = Config.MetadataConfiguration.MetadataSource;
// set up hasheous client // set up hasheous client
HasheousClient.WebApp.HttpHelper.BaseUri = Config.MetadataConfiguration.HasheousHost; HasheousClient.WebApp.HttpHelper.BaseUri = Config.MetadataConfiguration.HasheousHost;
// set initial values
Guid APIKey = Guid.NewGuid();
if (Config.ReadSetting("API Key", "Test API Key") == "Test API Key")
{
// it's a new api key save it
Logging.Log(Logging.LogType.Information, "Startup", "Setting initial API key");
Config.SetSetting("API Key", APIKey.ToString());
}
// clean up storage // clean up storage
if (Directory.Exists(Config.LibraryConfiguration.LibraryTempDirectory)) if (Directory.Exists(Config.LibraryConfiguration.LibraryTempDirectory))
{ {
@@ -77,13 +70,14 @@ if (Directory.Exists(Config.LibraryConfiguration.LibraryUploadDirectory))
// kick off any delayed upgrade tasks // kick off any delayed upgrade tasks
// run 1002 background updates in the background on every start // run 1002 background updates in the background on every start
DatabaseMigration.BackgroundUpgradeTargetSchemaVersions.Add(1002); DatabaseMigration.BackgroundUpgradeTargetSchemaVersions.Add(1002);
DatabaseMigration.BackgroundUpgradeTargetSchemaVersions.Add(1023);
// start the task // start the task
ProcessQueue.QueueItem queueItem = new ProcessQueue.QueueItem( ProcessQueue.QueueItem queueItem = new ProcessQueue.QueueItem(
ProcessQueue.QueueItemType.BackgroundDatabaseUpgrade, ProcessQueue.QueueItemType.BackgroundDatabaseUpgrade,
1, 1,
new List<ProcessQueue.QueueItemType> new List<ProcessQueue.QueueItemType>
{ {
ProcessQueue.QueueItemType.All ProcessQueue.QueueItemType.SignatureIngestor
}, },
false, false,
true true
@@ -140,12 +134,7 @@ builder.Services.AddApiVersioning(config =>
config.ApiVersionReader = ApiVersionReader.Combine(new UrlSegmentApiVersionReader(), config.ApiVersionReader = ApiVersionReader.Combine(new UrlSegmentApiVersionReader(),
new HeaderApiVersionReader("x-api-version"), new HeaderApiVersionReader("x-api-version"),
new MediaTypeApiVersionReader("x-api-version")); new MediaTypeApiVersionReader("x-api-version"));
}); }).AddApiExplorer(setup =>
// builder.Services.AddApiVersioning(setup =>
// {
// setup.ApiVersionReader = new UrlSegmentApiVersionReader();
// });
builder.Services.AddVersionedApiExplorer(setup =>
{ {
setup.GroupNameFormat = "'v'VVV"; setup.GroupNameFormat = "'v'VVV";
setup.SubstituteApiVersionInUrl = true; setup.SubstituteApiVersionInUrl = true;
@@ -242,15 +231,6 @@ builder.Services.ConfigureApplicationCookie(options =>
options.Cookie.SecurePolicy = CookieSecurePolicy.SameAsRequest; options.Cookie.SecurePolicy = CookieSecurePolicy.SameAsRequest;
options.Cookie.SameSite = SameSiteMode.Strict; options.Cookie.SameSite = SameSiteMode.Strict;
}); });
// builder.Services.AddIdentityCore<ApplicationUser>(options => {
// options.SignIn.RequireConfirmedAccount = false;
// options.User.RequireUniqueEmail = true;
// options.Password.RequireDigit = false;
// options.Password.RequiredLength = 10;
// options.Password.RequireNonAlphanumeric = false;
// options.Password.RequireUppercase = false;
// options.Password.RequireLowercase = false;
// });
builder.Services.AddScoped<UserStore>(); builder.Services.AddScoped<UserStore>();
builder.Services.AddScoped<RoleStore>(); builder.Services.AddScoped<RoleStore>();
@@ -277,8 +257,16 @@ var app = builder.Build();
app.UseSwagger(); app.UseSwagger();
app.UseSwaggerUI(options => app.UseSwaggerUI(options =>
{ {
options.SwaggerEndpoint($"/swagger/v1/swagger.json", "v1.0"); // options.SwaggerEndpoint($"/swagger/v1/swagger.json", "v1.0");
options.SwaggerEndpoint($"/swagger/v1.1/swagger.json", "v1.1"); // options.SwaggerEndpoint($"/swagger/v1.1/swagger.json", "v1.1");
var descriptions = app.DescribeApiVersions();
foreach (var description in descriptions)
{
var url = $"/swagger/{description.GroupName}/swagger.json";
var name = description.GroupName.ToUpperInvariant();
options.SwaggerEndpoint(url, name);
}
} }
); );
//} //}
@@ -292,7 +280,7 @@ using (var scope = app.Services.CreateScope())
{ {
var roleManager = scope.ServiceProvider.GetRequiredService<RoleStore>(); var roleManager = scope.ServiceProvider.GetRequiredService<RoleStore>();
var roles = new[] { "Admin", "Gamer", "Player" }; var roles = new[] { "Admin", "Gamer", "Player" };
foreach (var role in roles) foreach (var role in roles)
{ {
if (await roleManager.FindByNameAsync(role, CancellationToken.None) == null) if (await roleManager.FindByNameAsync(role, CancellationToken.None) == null)
@@ -322,11 +310,11 @@ app.Use(async (context, next) =>
string correlationId = Guid.NewGuid().ToString(); string correlationId = Guid.NewGuid().ToString();
CallContext.SetData("CorrelationId", correlationId); CallContext.SetData("CorrelationId", correlationId);
CallContext.SetData("CallingProcess", context.Request.Method + ": " + context.Request.Path); CallContext.SetData("CallingProcess", context.Request.Method + ": " + context.Request.Path);
string userIdentity; string userIdentity;
try try
{ {
userIdentity = context.User.Claims.Where(x=>x.Type==System.Security.Claims.ClaimTypes.NameIdentifier).FirstOrDefault().Value; userIdentity = context.User.Claims.Where(x => x.Type == System.Security.Claims.ClaimTypes.NameIdentifier).FirstOrDefault().Value;
} }
catch catch
{ {
@@ -338,72 +326,6 @@ app.Use(async (context, next) =>
await next(); await next();
}); });
// emergency password recovery if environment variable is set
// process:
// - set the environment variable "recoveraccount" to the email address of the account to be recovered
// - when the server starts the password will be reset to a random string and saved in the library
// directory with the name RecoverAccount.txt
// - user should copy this password and remove the "recoveraccount" environment variable and the
// RecoverAccount.txt file
// - 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
Logging.Log(Logging.LogType.Critical, "Server Startup", "Unable to start while recoveraccount environment varibale is set and RecoverAccount.txt file exists.", null, true);
Environment.Exit(0);
}
else
{
// generate and save the password to disk
int length = 10;
string chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789!@#$%^&*()_+";
var random = new Random();
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())
{
var userManager = scope.ServiceProvider.GetRequiredService<UserStore>();
if (await userManager.FindByNameAsync(Environment.GetEnvironmentVariable("recoveraccount"), CancellationToken.None) != null)
{
ApplicationUser User = await userManager.FindByEmailAsync(Environment.GetEnvironmentVariable("recoveraccount"), CancellationToken.None);
//set user password
PasswordHasher<ApplicationUser> ph = new PasswordHasher<ApplicationUser>();
User.PasswordHash = ph.HashPassword(User, password);
await userManager.SetPasswordHashAsync(User, User.PasswordHash, CancellationToken.None);
Logging.Log(Logging.LogType.Information, "Server Startup", "Password reset complete, remove the recoveraccount environment variable and RecoverAccount.text file to allow server start.", null, true);
Environment.Exit(0);
}
else
{
Logging.Log(Logging.LogType.Critical, "Server Startup", "Account to recover not found.", null, true);
Environment.Exit(0);
}
}
}
}
else
{
// check if RecoverAccount.text file is present
if (File.Exists(PasswordRecoveryFile))
{
// cannot start while password recovery file exists
Logging.Log(Logging.LogType.Critical, "Server Startup", "Unable to start while RecoverAccount.txt file exists. Remove the file and try again.", null, true);
Environment.Exit(0);
}
}
// setup library directories // setup library directories
Config.LibraryConfiguration.InitLibrary(); Config.LibraryConfiguration.InitLibrary();
@@ -416,74 +338,43 @@ gaseous_server.Classes.Metadata.Platforms.AssignAllPlatformsToGameIdZero();
// extract platform map if not present // extract platform map if not present
PlatformMapping.ExtractPlatformMap(); PlatformMapping.ExtractPlatformMap();
// force load platform map into cache // migrate old firmware directory structure to new style
var platformMap = PlatformMapping.PlatformMap; Bios.MigrateToNewFolderStructure();
// add background tasks // add background tasks
ProcessQueue.QueueItems.Add(new ProcessQueue.QueueItem( ProcessQueue.QueueItems.Add(new ProcessQueue.QueueItem(
ProcessQueue.QueueItemType.SignatureIngestor, ProcessQueue.QueueItemType.SignatureIngestor)
int.Parse(Config.ReadSetting("Interval_SignatureIngestor", "60"))
)
); );
ProcessQueue.QueueItems.Add(new ProcessQueue.QueueItem( ProcessQueue.QueueItems.Add(new ProcessQueue.QueueItem(
ProcessQueue.QueueItemType.TitleIngestor, ProcessQueue.QueueItemType.TitleIngestor)
int.Parse(Config.ReadSetting("Interval_TitleIngestor", "1")),
new List<ProcessQueue.QueueItemType>
{
ProcessQueue.QueueItemType.OrganiseLibrary,
ProcessQueue.QueueItemType.LibraryScan
})
); );
ProcessQueue.QueueItems.Add(new ProcessQueue.QueueItem( ProcessQueue.QueueItems.Add(new ProcessQueue.QueueItem(
ProcessQueue.QueueItemType.MetadataRefresh, ProcessQueue.QueueItemType.MetadataRefresh)
int.Parse(Config.ReadSetting("Interval_MetadataRefresh", "360"))
)
); );
ProcessQueue.QueueItems.Add(new ProcessQueue.QueueItem( ProcessQueue.QueueItems.Add(new ProcessQueue.QueueItem(
ProcessQueue.QueueItemType.OrganiseLibrary, ProcessQueue.QueueItemType.OrganiseLibrary)
int.Parse(Config.ReadSetting("Interval_OrganiseLibrary", "1440")),
new List<ProcessQueue.QueueItemType>
{
ProcessQueue.QueueItemType.LibraryScan,
ProcessQueue.QueueItemType.TitleIngestor,
ProcessQueue.QueueItemType.Rematcher
})
); );
ProcessQueue.QueueItems.Add(new ProcessQueue.QueueItem( ProcessQueue.QueueItems.Add(new ProcessQueue.QueueItem(
ProcessQueue.QueueItemType.LibraryScan, ProcessQueue.QueueItemType.LibraryScan)
int.Parse(Config.ReadSetting("Interval_LibraryScan", "1440")),
new List<ProcessQueue.QueueItemType>
{
ProcessQueue.QueueItemType.OrganiseLibrary,
ProcessQueue.QueueItemType.Rematcher
})
); );
ProcessQueue.QueueItems.Add(new ProcessQueue.QueueItem( ProcessQueue.QueueItems.Add(new ProcessQueue.QueueItem(
ProcessQueue.QueueItemType.Rematcher, ProcessQueue.QueueItemType.Rematcher)
int.Parse(Config.ReadSetting("Interval_Rematcher", "1440")),
new List<ProcessQueue.QueueItemType>
{
ProcessQueue.QueueItemType.OrganiseLibrary,
ProcessQueue.QueueItemType.LibraryScan
})
);
ProcessQueue.QueueItems.Add(new ProcessQueue.QueueItem(
ProcessQueue.QueueItemType.Maintainer,
int.Parse(Config.ReadSetting("Interval_Maintainer", "10080")),
new List<ProcessQueue.QueueItemType>
{
ProcessQueue.QueueItemType.All
})
); );
ProcessQueue.QueueItem tempCleanup = new ProcessQueue.QueueItem( // maintenance tasks
ProcessQueue.QueueItemType.TempCleanup, ProcessQueue.QueueItem dailyMaintenance = new ProcessQueue.QueueItem(
1, ProcessQueue.QueueItemType.DailyMaintainer
new List<ProcessQueue.QueueItemType>(), );
false, ProcessQueue.QueueItems.Add(dailyMaintenance);
false
ProcessQueue.QueueItem weeklyMaintenance = new ProcessQueue.QueueItem(
ProcessQueue.QueueItemType.WeeklyMaintainer
);
ProcessQueue.QueueItems.Add(weeklyMaintenance);
ProcessQueue.QueueItem tempCleanup = new ProcessQueue.QueueItem(
ProcessQueue.QueueItemType.TempCleanup
); );
tempCleanup.ForceExecute();
ProcessQueue.QueueItems.Add(tempCleanup); ProcessQueue.QueueItems.Add(tempCleanup);
Logging.WriteToDiskOnly = false; Logging.WriteToDiskOnly = false;

View File

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

View File

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

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